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

101 lines
3 KiB
TypeScript
Raw Normal View History

import axios, { AxiosInstance } from 'axios';
import { TokenStorage } from './tokenStorage';
// T0177: Créer un client axios séparé pour le refresh pour éviter les interceptors
// Lazy initialization pour faciliter les tests
let refreshClient: AxiosInstance | null = null;
function getRefreshClient(): AxiosInstance {
if (!refreshClient) {
2025-12-17 13:07:35 +00:00
const baseURL = (() => {
const url = import.meta.env.VITE_API_URL;
if (!url) {
if (import.meta.env.PROD) {
throw new Error('VITE_API_URL must be defined in production');
}
// Fallback uniquement en développement
return 'http://127.0.0.1:8080/api/v1';
}
return url;
})();
refreshClient = axios.create({
2025-12-17 13:07:35 +00:00
baseURL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
}
return refreshClient;
}
/**
* TokenRefresh - Service de rafraîchissement des tokens d'authentification
* T0176: Service pour rafraîchir les tokens via l'endpoint /auth/refresh
*
* Format de réponse attendu du backend :
* {
* "success": true,
* "data": {
* "access_token": "...",
* "refresh_token": "...",
* "expires_in": 3600
* }
* }
*/
export interface RefreshTokenResponse {
access_token: string;
refresh_token: string;
expires_in: number;
}
/**
* Rafraîchit le token d'accès en utilisant le refresh token
* T0176: Appelle l'endpoint POST /api/v1/auth/refresh et met à jour les tokens
* @returns Promise qui se résout quand le token est rafraîchi
* @throws Error si le refresh token n'est pas disponible ou si le refresh échoue
*/
export async function refreshToken(): Promise<void> {
// T0176: Récupérer le refresh token depuis le stockage
const refreshToken = TokenStorage.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
try {
// T0176: Appeler l'endpoint POST /auth/refresh
// T0177: Utiliser refreshClient pour éviter les interceptors (qui causeraient une boucle)
const client = getRefreshClient();
const response = await client.post<{
success: boolean;
data: RefreshTokenResponse;
}>('/auth/refresh', {
refresh_token: refreshToken,
});
// Le backend retourne toujours { success: true, data: { access_token, refresh_token, expires_in } }
if (!response.data?.success || !response.data?.data) {
throw new Error(
`Invalid refresh response format. Expected { success: true, data: { access_token, refresh_token, expires_in } }, got: ${JSON.stringify(response.data)}`,
);
2025-12-22 21:00:50 +00:00
}
const { access_token, refresh_token } = response.data.data;
if (!access_token || !refresh_token) {
throw new Error(
'Invalid refresh response: missing access_token or refresh_token',
);
2025-12-22 21:00:50 +00:00
}
// T0176: Mettre à jour les tokens stockés
TokenStorage.setTokens(access_token, refresh_token);
} catch (error) {
// T0176: Gérer les erreurs - supprimer les tokens en cas d'échec
TokenStorage.clearTokens();
throw error;
}
}