import type { NextAuthOptions } from 'next-auth' import CredentialsProvider from 'next-auth/providers/credentials' import { compare } from 'bcryptjs' import { prisma } from './prisma' import { checkRateLimit, recordFailedAttempt, clearAttempts } from './rate-limit' export const authOptions: NextAuthOptions = { session: { strategy: 'jwt', maxAge: 8 * 60 * 60, // 8 heures }, pages: { signIn: '/login', }, callbacks: { async jwt({ token, user }) { if (user) { // NextAuth v4 : les types de base n'incluent pas les champs custom. // Le module augmentation dans next-auth.d.ts couvre le type Session/JWT // mais pas le paramètre `user` du callback jwt. Cast nécessaire. const u = user as typeof user & { role: string } token.role = u.role token.id = u.id } return token }, async session({ session, token }) { if (session.user) { // session.user est déjà augmenté via next-auth.d.ts (id + role) session.user.id = token.id as string session.user.role = token.role as string } return session }, }, providers: [ CredentialsProvider({ name: 'Credentials', credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Mot de passe', type: 'password' }, }, async authorize(credentials, req) { if (!credentials?.email || !credentials?.password) return null // NextAuth v4 : req est un RequestInternal partiel sans typings complets. // Le cast est nécessaire pour accéder à x-forwarded-for. // eslint-disable-next-line @typescript-eslint/no-explicit-any const ip = (req as any)?.headers?.['x-forwarded-for']?.split(',')[0]?.trim() ?? 'unknown' const key = `${ip}:${credentials.email}` if (!checkRateLimit(key)) { throw new Error('RATE_LIMITED') } const user = await prisma.user.findUnique({ where: { email: credentials.email }, }) if (!user || !(await compare(credentials.password, user.password))) { recordFailedAttempt(key) return null } clearAttempts(key) return { id: user.id, email: user.email, name: user.name, role: user.role } }, }), ], }