import { type GetServerSidePropsContext } from "next"; import { getServerSession, type NextAuthOptions, type DefaultSession, } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { prisma } from "~/server/db"; import { loginSchema } from "~/lib/validation/auth"; import { verify } from "argon2"; import { type Role } from "@prisma/client"; interface SessionUser { id: string; name: string; username: string; role: Role; } /** * 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: SessionUser; } interface User { id: string; name: string; username: string; role: Role; } } declare module "next-auth/jwt" { interface JWT { id: string; name: string; username: string; role: Role; } } /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * * @see https://next-auth.js.org/configuration/options */ export const authOptions: NextAuthOptions = { callbacks: { jwt({ token, user }) { if (user) { token.id = user.id; token.username = user.username; token.name = user.name; token.role = user.role; } return token; }, session({ session, token }) { if (token) { session.user.id = token.id; session.user.username = token.username; session.user.name = token.name; session.user.role = token.role; } return session; }, }, adapter: PrismaAdapter(prisma), providers: [ CredentialsProvider({ // The name to display on the sign in form (e.g. 'Sign in with...') name: "Credentials", // The credentials is used to generate a suitable form on the sign in page. // You can specify whatever fields you are expecting to be submitted. // e.g. domain, username, password, 2FA token, etc. // You can pass any HTML attribute to the tag through the object. credentials: { username: { label: "Username", type: "text" }, password: { label: "Password", type: "password" }, }, async authorize(credentials): Promise { try { // get the username and password from the credientials const { username, password } = await loginSchema.parseAsync( credentials ); // check if username exists in the database const result = await prisma.user.findFirst({ where: { username }, }); if (!result) return null; // check if input password match the hashed password const isValidPassword = await verify(result.password, password); if (!isValidPassword) return null; return { id: result.id, name: result.name, username, role: result.role, }; } catch { return null; } }, }), /** * ...add more providers here. * * Most other providers require a bit more work than the Discord provider. For example, the * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account * model. Refer to the NextAuth.js docs for the provider you want to use. Example: * * @see https://next-auth.js.org/providers/github */ ], jwt: { maxAge: 2 * 60 * 60, // 2 hours }, pages: { signIn: "/admin/login", }, session: { strategy: "jwt", }, }; /** * 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); };