2025-12-22 21:56:37 +00:00
|
|
|
import { apiClient } from './api/client';
|
2026-01-07 10:15:48 +00:00
|
|
|
import { logger } from '@/utils/logger';
|
2025-12-03 21:56:50 +00:00
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
/**
|
|
|
|
|
* CSRF Service
|
|
|
|
|
* Gère la récupération et le stockage des tokens CSRF
|
|
|
|
|
*/
|
|
|
|
|
class CSRFService {
|
|
|
|
|
private token: string | null = null;
|
|
|
|
|
private refreshPromise: Promise<string> | null = null;
|
2025-12-03 21:56:50 +00:00
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
/**
|
|
|
|
|
* Récupère un nouveau token CSRF depuis le backend
|
|
|
|
|
*/
|
|
|
|
|
async refreshToken(): Promise<string> {
|
|
|
|
|
// Éviter les appels multiples simultanés
|
|
|
|
|
if (this.refreshPromise) {
|
|
|
|
|
return this.refreshPromise;
|
2025-12-03 21:56:50 +00:00
|
|
|
}
|
2025-12-22 21:56:37 +00:00
|
|
|
|
|
|
|
|
this.refreshPromise = (async () => {
|
|
|
|
|
try {
|
2026-01-13 18:47:57 +00:00
|
|
|
const response = await apiClient.get<{ csrf_token: string }>(
|
|
|
|
|
'/csrf-token',
|
|
|
|
|
);
|
2025-12-22 21:56:37 +00:00
|
|
|
// apiClient unwrap déjà le format { success, data }
|
|
|
|
|
const data = response.data;
|
|
|
|
|
this.token = data.csrf_token;
|
|
|
|
|
return this.token;
|
|
|
|
|
} catch (error) {
|
2026-01-07 10:15:48 +00:00
|
|
|
logger.error('Failed to fetch CSRF token:', { error });
|
2025-12-22 21:56:37 +00:00
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
this.refreshPromise = null;
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
return this.refreshPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retourne le token CSRF actuel
|
|
|
|
|
* Si aucun token n'est disponible, retourne null
|
|
|
|
|
*/
|
|
|
|
|
getToken(): string | null {
|
|
|
|
|
return this.token;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-25 21:28:46 +00:00
|
|
|
/**
|
|
|
|
|
* Vérifie si un token est disponible, sinon en récupère un nouveau
|
|
|
|
|
* INT-AUTH-001: Ensure token is available before mutations
|
|
|
|
|
*/
|
|
|
|
|
async ensureToken(): Promise<string> {
|
|
|
|
|
if (this.token) {
|
|
|
|
|
return this.token;
|
|
|
|
|
}
|
|
|
|
|
return this.refreshToken();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
/**
|
|
|
|
|
* Réinitialise le token (utile après logout)
|
|
|
|
|
*/
|
|
|
|
|
clearToken(): void {
|
|
|
|
|
this.token = null;
|
|
|
|
|
this.refreshPromise = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-26 08:16:27 +00:00
|
|
|
* Alias pour compatibilité (legacy)
|
|
|
|
|
* INT-CLEANUP-001: Kept for backward compatibility
|
2025-12-22 21:56:37 +00:00
|
|
|
*/
|
|
|
|
|
clearCsrfToken(): void {
|
|
|
|
|
this.clearToken();
|
2025-12-03 21:56:50 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
/**
|
2025-12-26 08:16:27 +00:00
|
|
|
* Alias pour compatibilité (legacy)
|
|
|
|
|
* INT-CLEANUP-001: Kept for backward compatibility
|
2025-12-22 21:56:37 +00:00
|
|
|
*/
|
|
|
|
|
async refreshCsrfToken(): Promise<string> {
|
|
|
|
|
return this.refreshToken();
|
2025-12-03 21:56:50 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
/**
|
|
|
|
|
* Retourne les headers CSRF pour les requêtes fetch
|
|
|
|
|
*/
|
|
|
|
|
getCsrfHeaders(): Record<string, string> {
|
|
|
|
|
const token = this.getToken();
|
|
|
|
|
if (!token) {
|
2025-12-03 21:56:50 +00:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return {
|
2025-12-22 21:56:37 +00:00
|
|
|
'X-CSRF-Token': token,
|
2025-12-03 21:56:50 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 21:56:37 +00:00
|
|
|
// Instance singleton
|
|
|
|
|
export const csrfService = new CSRFService();
|