71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
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 }
|
|
},
|
|
}),
|
|
],
|
|
}
|