import { PrismaAdapter } from "@auth/prisma-adapter"; import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import Google from "next-auth/providers/google"; import { authConfig } from "@/auth.config"; import { authorizeCredentials } from "@/lib/auth/authorize-credentials"; import { prisma } from "@/lib/db/prisma"; export { authorizeCredentials } from "@/lib/auth/authorize-credentials"; export const { handlers, auth, signIn, signOut } = NextAuth({ ...authConfig, // `@auth/prisma-adapter`'s published types still import `PrismaClient` // from the legacy `@prisma/client` path; Prisma 7's `prisma-client` // generator no longer populates that path (see prisma/schema.prisma). // The adapter only calls plain `prisma..()` at runtime, // so this is a type-only mismatch, not a behavioural one — narrowed // here instead of silencing it project-wide. adapter: PrismaAdapter(prisma as Parameters[0]), providers: [ Google, Credentials({ credentials: { email: { label: "Email", type: "email" }, code: { label: "Code", type: "text" }, // Only ever set by the installer's own trusted server-side sign-in // call — never rendered on any real form. See // src/lib/auth/authorize-credentials.ts's own header for the full // finalize/token design. finalize: { label: "Finalize", type: "text" }, token: { label: "Token", type: "text" }, }, // Kept in its own module (not inlined here) so it's importable from // a plain Vitest test without dragging in this file's NextAuth(...) // construction, which needs the Next.js runtime — see // authorize-credentials.ts's own header. authorize: authorizeCredentials, }), ], callbacks: { async jwt({ token, user, trigger, session }) { if (user?.id) { token.id = user.id; } // Lets a client-side `useSession().update({ email })` call (see // change-email.ts's verifyEmailChangeAction, whose caller triggers // this right after a successful email change) refresh the JWT's own // email claim without forcing a full sign-out/sign-in — otherwise // session.user.email would keep showing the old address until the // token's next natural refresh. Nothing else in this app reads // session.user.email for authorization (every permission/tenant // check is keyed on token.id/session.user.id), so this is purely a // display-freshness fix. if (trigger === "update" && typeof session?.email === "string") { token.email = session.email; } return token; }, async session({ session, token }) { if (session.user && typeof token.id === "string") { session.user.id = token.id; } return session; }, }, });