66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import NextAuth from "next-auth";
|
|
import Credentials from "next-auth/providers/credentials";
|
|
import bcrypt from "bcryptjs";
|
|
|
|
import { prisma } from "@/lib/db";
|
|
import { LoginSchema } from "@/lib/validation/auth";
|
|
import { Role } from "@/lib/generated/prisma/enums";
|
|
import { PendingAccountSignin } from "@/lib/auth-errors";
|
|
|
|
// Credentials-only, JWT sessions, no database adapter: with a single
|
|
// Credentials provider and no OAuth, there's nothing for a DB-backed
|
|
// Session/Account table to do, so we skip the Prisma adapter entirely.
|
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
session: { strategy: "jwt" },
|
|
// Self-hosted behind whatever host/port the operator points at it (not a
|
|
// platform Auth.js auto-detects, like Vercel) -- without this, Auth.js
|
|
// rejects every request with an UntrustedHost error.
|
|
trustHost: true,
|
|
pages: {
|
|
signIn: "/login",
|
|
},
|
|
providers: [
|
|
Credentials({
|
|
credentials: {
|
|
email: {},
|
|
password: {},
|
|
},
|
|
async authorize(rawCredentials) {
|
|
const parsed = LoginSchema.safeParse(rawCredentials);
|
|
if (!parsed.success) return null;
|
|
const { email, password } = parsed.data;
|
|
|
|
const user = await prisma.user.findUnique({ where: { email } });
|
|
if (!user) return null;
|
|
|
|
const passwordsMatch = await bcrypt.compare(password, user.passwordHash);
|
|
if (!passwordsMatch) return null;
|
|
|
|
// PENDING accounts are waiting on admin approval (or, eventually,
|
|
// email confirmation) and can't log in yet -- see lib/settings.ts.
|
|
if (user.role === Role.PENDING) throw new PendingAccountSignin();
|
|
|
|
return { id: user.id, email: user.email, name: user.name, role: user.role };
|
|
},
|
|
}),
|
|
],
|
|
callbacks: {
|
|
async jwt({ token, user }) {
|
|
if (user?.id) {
|
|
token.sub = user.id;
|
|
token.role = user.role;
|
|
}
|
|
return token;
|
|
},
|
|
async session({ session, token }) {
|
|
if (session.user && token.sub) session.user.id = token.sub;
|
|
// Cached in the JWT at login time -- a role change made through the
|
|
// Admin page won't take effect for that user until their next login.
|
|
// No session-invalidation mechanism yet; fine for now, revisit if
|
|
// role changes need to apply immediately.
|
|
if (session.user && token.role) session.user.role = token.role;
|
|
return session;
|
|
},
|
|
},
|
|
});
|