import * as jose from 'jose'; interface TokenPair { accessToken: string; refreshToken: string; } export default class TokenService { private static instance: TokenService; private readonly SECRET_KEY = ((): string => { // Récupération de la clé depuis l'environnement Vite (via eval) ou fallback Node (tests) const viteEnv = (() => { try { // évite l'erreur de parsing Jest en CJS return (0, eval)('import.meta')?.env; } catch { return undefined; } })() as any | undefined; const viteKey = viteEnv?.VITE_JWT_SECRET_KEY as string | undefined; const nodeKey = (globalThis as any)?.process?.env?.VITE_JWT_SECRET_KEY as string | undefined; const resolved = viteKey || nodeKey || ''; return resolved; })(); private readonly ACCESS_TOKEN_EXPIRATION = '30s'; private readonly REFRESH_TOKEN_EXPIRATION = '7d'; private readonly encoder = new TextEncoder(); private constructor() {} static async getInstance(): Promise { if (!TokenService.instance) { TokenService.instance = new TokenService(); } return TokenService.instance; } async generateSessionToken(origin: string): Promise { const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const accessToken = await new jose.SignJWT({ origin, type: 'access' }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(this.ACCESS_TOKEN_EXPIRATION) .sign(secret); const refreshToken = await new jose.SignJWT({ origin, type: 'refresh' }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(this.REFRESH_TOKEN_EXPIRATION) .sign(secret); return { accessToken, refreshToken }; } async validateToken(token: string, origin: string): Promise { try { const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const { payload } = await jose.jwtVerify(token, secret); return payload.origin === origin; } catch (error: any) { if (error?.code === 'ERR_JWT_EXPIRED') { console.log('Token expiré'); return false; } console.error('Erreur de validation du token:', error); return false; } } async refreshAccessToken(refreshToken: string, origin: string): Promise { try { // Vérifier si le refresh token est valide const isValid = await this.validateToken(refreshToken, origin); if (!isValid) { return null; } // Vérifier le type du token const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const { payload } = await jose.jwtVerify(refreshToken, secret); if (payload.type !== 'refresh') { return null; } // Générer un nouveau access token const newAccessToken = await new jose.SignJWT({ origin, type: 'access' }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(this.ACCESS_TOKEN_EXPIRATION) .sign(secret); return newAccessToken; } catch (error) { console.error('Erreur lors du refresh du token:', error); return null; } } }