veza/apps/web/src/services/csrf.ts

99 lines
2.2 KiB
TypeScript

import { apiClient } from './api/client';
import { logger } from '@/utils/logger';
/**
* 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;
/**
* 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;
}
this.refreshPromise = (async () => {
try {
const response = await apiClient.get<{ csrf_token: string }>(
'/csrf-token',
);
// apiClient unwrap déjà le format { success, data }
const data = response.data;
this.token = data.csrf_token;
return this.token;
} catch (error) {
logger.error('Failed to fetch CSRF token:', { error });
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;
}
/**
* 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();
}
/**
* Réinitialise le token (utile après logout)
*/
clearToken(): void {
this.token = null;
this.refreshPromise = null;
}
/**
* Alias pour compatibilité (legacy)
* INT-CLEANUP-001: Kept for backward compatibility
*/
clearCsrfToken(): void {
this.clearToken();
}
/**
* Alias pour compatibilité (legacy)
* INT-CLEANUP-001: Kept for backward compatibility
*/
async refreshCsrfToken(): Promise<string> {
return this.refreshToken();
}
/**
* Retourne les headers CSRF pour les requêtes fetch
*/
getCsrfHeaders(): Record<string, string> {
const token = this.getToken();
if (!token) {
return {};
}
return {
'X-CSRF-Token': token,
};
}
}
// Instance singleton
export const csrfService = new CSRFService();