feat: Securisation du Backend

This commit is contained in:
Luc SORIGNET
2026-02-27 10:45:36 +01:00
parent 2fef6d61a4
commit fa843097ba
55 changed files with 2898 additions and 910 deletions

View File

@ -0,0 +1,101 @@
import { getSession } from 'next-auth/react';
import {
requestResponseHandler,
errorHandler,
triggerSignOut,
} from '@/app/actions/actionsHandlers';
import logger from '@/utils/logger';
// Déduplique les appels concurrents à getSession() :
// si plusieurs fetchWithAuth() partent en même temps (chargement de page),
// ils partagent la même promesse au lieu de déclencher N refreshs JWT en parallèle.
let _pendingSessionPromise = null;
const getSessionOnce = () => {
if (!_pendingSessionPromise) {
_pendingSessionPromise = getSession().finally(() => {
_pendingSessionPromise = null;
});
}
return _pendingSessionPromise;
};
/**
* Récupère le token JWT Bearer depuis la session NextAuth.
* @returns {Promise<string|null>}
*/
export const getAuthToken = async () => {
const session = await getSessionOnce();
if (!session) {
logger.warn('getAuthToken: session nulle, aucun token envoyé');
return null;
}
if (session?.error === 'RefreshTokenError') {
logger.warn(
'getAuthToken: RefreshTokenError détecté, déconnexion en cours'
);
await triggerSignOut();
return null;
}
if (!session?.user?.token) {
logger.warn('getAuthToken: session présente mais token absent', {
session,
});
return null;
}
return session.user.token;
};
/**
* Wrapper de fetch qui injecte automatiquement le header Authorization Bearer
* depuis la session NextAuth, puis passe la réponse dans requestResponseHandler.
*
* - Ajoute Content-Type: application/json par défaut (sauf si le body est FormData)
* - Ajoute credentials: 'include' par défaut
* - Les options.headers passées en paramètre surchargent les défauts (ex: X-CSRFToken)
*
* @param {string} url
* @param {RequestInit} options
* @returns {Promise<any>} Corps de la réponse désérialisé
*/
export const fetchWithAuth = async (url, options = {}) => {
const token = await getAuthToken();
const isFormData = options.body instanceof FormData;
const headers = {
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
...options.headers,
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
return fetch(url, {
credentials: 'include',
...options,
headers,
})
.then(requestResponseHandler)
.catch(errorHandler);
};
/**
* Variante de fetchWithAuth qui retourne la Response brute sans passer
* par requestResponseHandler. Utile quand l'appelant gère lui-même response.ok.
*
* @param {string} url
* @param {RequestInit} options
* @returns {Promise<Response>}
*/
export const fetchWithAuthRaw = async (url, options = {}) => {
const token = await getAuthToken();
const headers = {
...options.headers,
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
return fetch(url, {
credentials: 'include',
...options,
headers,
});
};