Skip to content

Instantly share code, notes, and snippets.

@SecondThundeR
Last active November 22, 2023 18:07
Show Gist options
  • Save SecondThundeR/1814f8c4aae6936b6e801da8bd9628d8 to your computer and use it in GitHub Desktop.
Save SecondThundeR/1814f8c4aae6936b6e801da8bd9628d8 to your computer and use it in GitHub Desktop.
Custom Shikimori NextAuth provider for T3 Stack
// Usage example with T3 Stack (NextAuth + Prisma)
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { type GetServerSidePropsContext } from "next";
import {
getServerSession,
type NextAuthOptions,
type DefaultSession,
} from "next-auth";
import ShikimoriProvider from "@/providers/shikimori";
import { env } from "@/env.mjs";
import { prisma } from "@/server/db";
/**
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
* object and keep type safety.
*
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
*/
declare module "next-auth" {
interface Session extends DefaultSession {
user: {
id: string;
// ...other properties
// role: UserRole;
} & DefaultSession["user"];
}
// interface User {
// // ...other properties
// // role: UserRole;
// }
}
/**
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
*
* @see https://next-auth.js.org/configuration/options
*/
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
},
}),
},
adapter: PrismaAdapter(prisma),
providers: [
ShikimoriProvider({
clientId: env.SHIKIMORI_CLIENT_ID,
clientSecret: env.SHIKIMORI_CLIENT_SECRET,
}),
],
};
/**
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
*
* @see https://next-auth.js.org/configuration/nextjs
*/
export const getServerAuthSession = (ctx: {
req: GetServerSidePropsContext["req"];
res: GetServerSidePropsContext["res"];
}) => {
return getServerSession(ctx.req, ctx.res, authOptions);
};
// Default T3 Stack env.mjs with Shikimori credentials setup
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/**
* Specify your server-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars.
*/
server: {
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(["development", "test", "production"]),
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production"
? z.string().min(1)
: z.string().min(1).optional(),
NEXTAUTH_URL: z.preprocess(
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
// Since NextAuth.js automatically uses the VERCEL_URL if present.
(str) => process.env.VERCEL_URL ?? str,
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
process.env.VERCEL ? z.string().min(1) : z.string().url()
),
// Add `.min(1) on ID and SECRET if you want to make sure they're not empty
SHIKIMORI_CLIENT_ID: z.string().min(1),
SHIKIMORI_CLIENT_SECRET: z.string().min(1),
},
/**
* Specify your client-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars. To expose them to the client, prefix them with
* `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
},
/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
SHIKIMORI_CLIENT_ID: process.env.SHIKIMORI_CLIENT_ID,
SHIKIMORI_CLIENT_SECRET: process.env.SHIKIMORI_CLIENT_SECRET,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
* This is especially useful for Docker builds.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});
import {
type OAuthConfig,
type OAuthUserConfig,
} from "next-auth/providers/oauth";
export interface ShikimoriUserInfo {
id: number;
nickname: string;
avatar: string;
image: {
x160: string;
x148: string;
x80: string;
x64: string;
x48: string;
x32: string;
x16: string;
};
last_online_at: Date;
url: string;
name: string | null;
sex: string | null;
website: string;
full_years: number | null;
birth_on: Date | null;
locale: string | null;
}
export interface ShikimoriUser
extends ShikimoriUserInfo,
Record<string, unknown> {
last_online: string;
location: string | null;
banned: boolean;
about: string;
about_html: string;
common_info: string[] | null;
show_comments: boolean;
in_friends: boolean | null;
is_ignored: boolean;
style_id: number;
}
export default function ShikimoriProvider<P extends ShikimoriUser>(
options: OAuthUserConfig<P>,
): OAuthConfig<P> {
return {
id: "shikimori",
name: "Shikimori",
type: "oauth",
token: "https://shikimori.one/oauth/token",
authorization: "https://shikimori.one/oauth/authorize?scope=",
userinfo: "https://shikimori.one/api/users/whoami",
profile(profile) {
return {
id: String(profile.id),
email: null,
name: profile.nickname,
image: profile.image.x160,
};
},
options,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment