import NextAuth, { NextAuthOptions } from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; import { prisma } from '@/lib/prisma'; declare module "next-auth" { interface Session { user: { id: string; name?: string | null; email?: string | null; image?: string | null; username: string; first_name: string; last_name: string; role: string[]; }; accessToken: string; } interface JWT { accessToken: string; refreshToken: string; accessTokenExpires: number; role: string[]; username: string; first_name: string; last_name: string; } } function getRequiredEnvVar(name: string): string { const value = process.env[name]; if (!value) { throw new Error(`Missing required environment variable: ${name}`); } return value; } export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ clientId: getRequiredEnvVar("KEYCLOAK_CLIENT_ID"), clientSecret: getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"), issuer: getRequiredEnvVar("KEYCLOAK_ISSUER"), profile(profile) { return { id: profile.sub, name: profile.name ?? profile.preferred_username, email: profile.email, first_name: profile.given_name ?? '', last_name: profile.family_name ?? '', username: profile.preferred_username ?? profile.email?.split('@')[0] ?? '', role: profile.groups ?? [], } }, }), ], session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 days }, cookies: { sessionToken: { name: process.env.NODE_ENV === 'production' ? `__Secure-next-auth.session-token` : `next-auth.session-token`, options: { httpOnly: true, sameSite: 'lax', path: '/', secure: process.env.NODE_ENV === 'production' } }, callbackUrl: { name: process.env.NODE_ENV === 'production' ? `__Secure-next-auth.callback-url` : `next-auth.callback-url`, options: { httpOnly: true, sameSite: 'lax', path: '/', secure: process.env.NODE_ENV === 'production' } }, csrfToken: { name: process.env.NODE_ENV === 'production' ? `__Host-next-auth.csrf-token` : `next-auth.csrf-token`, options: { httpOnly: true, sameSite: 'lax', path: '/', secure: process.env.NODE_ENV === 'production' } } }, callbacks: { async signIn({ user, account, profile }) { if (!user.email) { console.error('No email provided in user profile'); return false; } try { console.log('Attempting to create/update user:', { id: user.id, email: user.email }); // First check if user exists const existingUser = await prisma.user.findUnique({ where: { id: user.id } }); console.log('Existing user check:', existingUser); // Create or update user in local database const result = await prisma.user.upsert({ where: { id: user.id }, update: { email: user.email, password: '', // We don't store password for Keycloak users }, create: { id: user.id, email: user.email, password: '', // We don't store password for Keycloak users }, }); console.log('User upsert result:', result); return true; } catch (error) { console.error('Error in signIn callback:', error); return false; } }, async session({ session, token }) { if (session?.user && token.sub) { session.user.id = token.sub; } return session; }, async jwt({ token, user, account }) { if (user) { token.sub = user.id; } return token; } }, pages: { signIn: '/signin', error: '/signin', }, debug: process.env.NODE_ENV === 'development', }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };