Files
n3wt-school/Front-End/src/pages/api/auth/[...nextauth].js
2026-03-15 10:07:20 +01:00

159 lines
4.9 KiB
JavaScript

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import jwt_decode from 'jsonwebtoken';
import logger from '@/utils/logger';
const options = {
secret: process.env.AUTH_SECRET,
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
// URL calculée ici (pas au niveau module) pour garantir que NEXT_PUBLIC_API_URL est chargé
const loginUrl = `${process.env.NEXT_PUBLIC_API_URL}/Auth/login`;
try {
const res = await fetch(loginUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Connection: close évite le SocketError undici lié au keep-alive vers Daphne
Connection: 'close',
},
body: JSON.stringify({
email: credentials.email,
password: credentials.password,
}),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body?.errorMessage || 'Identifiants invalides');
}
const user = await res.json();
return user || null;
} catch (error) {
logger.error('Authorize error:', error.message);
throw new Error(error.message || 'Invalid credentials');
}
},
}),
],
session: {
strategy: 'jwt',
maxAge: 60 * 60, // 1 Hour
// 0 = réécrire le cookie à chaque fois que le token change (indispensable avec
// un access token Django de 15 min, sinon le cookie expiré reste en place)
updateAge: 0,
},
cookies: {
sessionToken: {
name: 'n3wtschool_session_token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
callbacks: {
async jwt({ token, user }) {
// Si c'est la première connexion
if (user && user?.token) {
return {
...token,
token: user.token,
refresh: user.refresh,
tokenExpires: jwt_decode.decode(user.token).exp * 1000,
};
}
// Vérifier si le token n'est pas expiré
if (Date.now() < token.tokenExpires) {
return token;
}
// Token Django expiré (lifetime = 15 min), essayer de le rafraîchir
logger.info('JWT: access token expiré, tentative de refresh');
if (!token.refresh) {
logger.error('JWT: refresh token absent dans la session');
return { ...token, error: 'RefreshTokenError' };
}
const refreshUrl = `${process.env.NEXT_PUBLIC_API_URL}/Auth/refreshJWT`;
if (!process.env.NEXT_PUBLIC_API_URL) {
logger.error('JWT: NEXT_PUBLIC_API_URL non défini, refresh impossible');
return { ...token, error: 'RefreshTokenError' };
}
try {
const res = await fetch(refreshUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Connection: close évite le SocketError undici lié au keep-alive vers Daphne
Connection: 'close',
},
body: JSON.stringify({ refresh: token.refresh }),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
logger.error('JWT: refresh échoué', { status: res.status, body });
throw new Error(`Refresh HTTP ${res.status}`);
}
const response = await res.json();
if (!response?.token) {
logger.error('JWT: réponse refresh sans token', { response });
throw new Error('Réponse refresh invalide');
}
logger.info('JWT: refresh réussi');
return {
...token,
token: response.token,
refresh: response.refresh,
tokenExpires: jwt_decode.decode(response.token).exp * 1000,
error: undefined,
};
} catch (error) {
logger.error('JWT: refresh token failed', { message: error.message });
return { ...token, error: 'RefreshTokenError' };
}
},
async session({ session, token }) {
if (token?.error === 'RefreshTokenError') {
session.error = 'RefreshTokenError';
return session;
}
if (token && token?.token) {
const { user_id, email, roles, roleIndexLoginDefault } =
jwt_decode.decode(token.token);
session.user = {
...session.user,
token: token.token,
refresh: token.refresh,
user_id: user_id,
email: email,
roles: roles,
roleIndexLoginDefault: roleIndexLoginDefault,
};
}
return session;
},
},
pages: {
signIn: '/[locale]/users/login',
},
csrf: true,
};
export default (req, res) => NextAuth(req, res, options);