veza/apps/web/src/services/csrf.ts
senke 841f9b628c fix(web): silence console for expected failures (CSRF, webhooks 5xx)
- csrf: no log when backend returns HTML (wrong server / not running)
- webhookService: no log for 5xx on list webhooks
- api client: no log for 5xx on /webhooks (main + queued request)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 19:51:20 +01:00

105 lines
2.5 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) {
const msg = error instanceof Error ? error.message : String(error);
const isWrongServer = msg.includes('HTML page instead of JSON');
if (isWrongServer) {
// No log: backend not running or wrong server is expected in dev
} else {
logger.error('Failed to fetch CSRF token', { message: msg });
}
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();