//! Service CSRF pour la protection contre les attaques Cross-Site Request Forgery //! //! Ce service gère: //! - Récupération des tokens CSRF depuis les cookies //! - Envoi des tokens CSRF dans les requêtes //! - Gestion des erreurs CSRF import { ApiError } from '@/types'; export class CsrfService { private static instance: CsrfService; private csrfToken: string | null = null; private tokenExpiry: number | null = null; private constructor() { this.loadCsrfToken(); } public static getInstance(): CsrfService { if (!CsrfService.instance) { CsrfService.instance = new CsrfService(); } return CsrfService.instance; } /** * Charge le token CSRF depuis les cookies */ private loadCsrfToken(): void { try { // Le token CSRF est stocké dans un cookie httpOnly // On doit le récupérer via une requête spéciale this.fetchCsrfToken(); } catch (error) { console.warn('Erreur lors du chargement du token CSRF:', error); } } /** * Récupère le token CSRF depuis le serveur */ private async fetchCsrfToken(): Promise { try { const response = await fetch('/api/v1/csrf-token', { method: 'GET', credentials: 'include', // Important pour les cookies headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Erreur HTTP: ${response.status}`); } const data = await response.json(); this.csrfToken = data.csrf_token; this.tokenExpiry = data.expires_at ? new Date(data.expires_at).getTime() : null; console.debug('Token CSRF chargé:', this.csrfToken); } catch (error) { console.error('Erreur lors de la récupération du token CSRF:', error); throw new ApiError('Impossible de récupérer le token CSRF', 'CSRF_ERROR'); } } /** * Obtient le token CSRF actuel */ public getCsrfToken(): string | null { // Vérifier si le token est expiré if (this.tokenExpiry && Date.now() > this.tokenExpiry) { console.warn('Token CSRF expiré, rechargement...'); this.csrfToken = null; this.tokenExpiry = null; } return this.csrfToken; } /** * Force le rechargement du token CSRF */ public async refreshCsrfToken(): Promise { this.csrfToken = null; this.tokenExpiry = null; await this.fetchCsrfToken(); } /** * Vérifie si un token CSRF est disponible */ public hasCsrfToken(): boolean { return this.getCsrfToken() !== null; } /** * Obtient les headers CSRF pour les requêtes */ public getCsrfHeaders(): Record { const token = this.getCsrfToken(); if (!token) { return {}; } return { 'X-CSRF-Token': token, }; } /** * Gère les erreurs CSRF */ public handleCsrfError(error: any): void { if (error?.code === 'CSRF_ERROR' || error?.message?.includes('CSRF')) { console.warn('Erreur CSRF détectée, rechargement du token...'); this.refreshCsrfToken(); } } /** * Nettoie le token CSRF (lors de la déconnexion) */ public clearCsrfToken(): void { this.csrfToken = null; this.tokenExpiry = null; } } // Instance singleton export const csrfService = CsrfService.getInstance();