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);
};