import { NextAuthOptions } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import AzureADProvider from "next-auth/providers/azure-ad"; import AppleProvider from "next-auth/providers/apple"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import CredentialsProvider from "next-auth/providers/credentials"; import { compare } from "bcryptjs"; import { prisma } from "@/lib/prisma"; export const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma), providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", allowDangerousEmailAccountLinking: true, authorization: { params: { prompt: "consent", access_type: "offline", response_type: "code", scope: "openid profile email https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/tasks" } } }), AppleProvider({ clientId: process.env.APPLE_ID || "", clientSecret: process.env.APPLE_SECRET || "", allowDangerousEmailAccountLinking: true, }), AzureADProvider({ clientId: process.env.MICROSOFT_CLIENT_ID || "", clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "", tenantId: "common", allowDangerousEmailAccountLinking: true, authorization: { params: { prompt: "consent", scope: "openid profile email offline_access user.read Calendars.ReadWrite Tasks.ReadWrite" } }, }), CredentialsProvider({ name: "credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { throw new Error("Invalid credentials"); } const user = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!user || !user.passwordHash) { throw new Error("Invalid credentials"); } // Block login if email is not verified if (!user.emailVerified) { throw new Error("email_not_verified"); } const isPasswordValid = await compare( credentials.password, user.passwordHash ); if (!isPasswordValid) { throw new Error("Invalid credentials"); } return { id: user.id, email: user.email, name: user.name || null, image: user.image || null, }; } }) ], debug: true, session: { strategy: "jwt" }, pages: { signIn: "/auth/login", signOut: "/auth/login", error: "/auth/login", }, callbacks: { async signIn({ user, account }) { // For OAuth providers: ensure user ID is set on the token // allowDangerousEmailAccountLinking handles auto-linking if (account?.provider !== "credentials" && user.email) { const existingUser = await prisma.user.findUnique({ where: { email: user.email }, }); if (existingUser) { // Ensure the JWT gets the correct DB user ID user.id = existingUser.id; } } return true; }, async jwt({ token, user }) { if (user) { token.id = user.id; } return token; }, async session({ session, token }) { if (session.user) { (session.user as any).id = token.id; } return session; } }, secret: process.env.NEXTAUTH_SECRET };