101 lines
3.1 KiB
TypeScript

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<TokenService> {
if (!TokenService.instance) {
TokenService.instance = new TokenService();
}
return TokenService.instance;
}
async generateSessionToken(origin: string): Promise<TokenPair> {
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<boolean> {
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<string | null> {
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;
}
}
}