import NextAuth, { NextAuthConfig } from "next-auth" import CredentialsProvider from "next-auth/providers/credentials" import { PrismaAdapter } from "@auth/prisma-adapter" import { PrismaClient } from "@prisma/client" import bcrypt from "bcryptjs" const prisma = new PrismaClient() export const authConfig: NextAuthConfig = { adapter: PrismaAdapter(prisma), providers: [ CredentialsProvider({ name: "credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { return null } const user = await prisma.user.findUnique({ where: { email: credentials.email as string } }) if (!user || !user.isActive) { return null } const isPasswordValid = await bcrypt.compare( credentials.password as string, user.password ) if (!isPasswordValid) { return null } return { id: user.id.toString(), email: user.email, name: user.name, role: user.role, } } }) ], session: { strategy: "jwt" }, callbacks: { async jwt({ token, user }) { if (user) { token.role = user.role } return token }, async session({ session, token }) { if (token) { session.user.id = token.sub ?? "" session.user.role = token.role as string } return session } }, pages: { signIn: "/auth/signin", }, // Selbst gehostet: Auth.js vertraut sonst nur Vercel-Hosts und lehnt // Anfragen hinter Reverse-Proxy/Container mit UntrustedHost ab. trustHost: true, logger: { error(error) { // Im Produktions-Build ist der Klassenname minifiziert. Auth.js setzt // zusätzlich ein `type`-Feld aus einem String-Literal, das erhalten // bleibt; die Fehlermeldung verlinkt denselben Bezeichner. const type = (error as { type?: string }).type || error.name const isStaleSession = type === "JWTSessionError" || /#jwtsessionerror/i.test(error.message || "") // Ein Cookie, das mit einem anderen NEXTAUTH_SECRET verschlüsselt wurde, // ist kein Serverfehler, sondern eine veraltete Sitzung. Die Middleware // löscht das Cookie beim nächsten Aufruf; hier reicht eine knappe Zeile // statt eines Stacktraces bei jedem einzelnen Request. if (isStaleSession) { console.warn( "[auth] Veraltetes Sitzungs-Cookie verworfen (mit einem anderen NEXTAUTH_SECRET verschlüsselt) – wird beim nächsten Aufruf entfernt." ) return } console.error(`[auth] ${type}: ${error.message}`) }, warn(code) { console.warn(`[auth] ${code}`) }, debug() {}, }, // Kein Fallback-Secret: fehlt NEXTAUTH_SECRET, soll der Start fehlschlagen, // statt still mit einem öffentlich bekannten Wert zu signieren. secret: process.env.NEXTAUTH_SECRET! } export const { handlers, auth, signIn, signOut } = NextAuth(authConfig)