🔐 Implémentation PBKDF2 avec credentials navigateur ✅ Fonctionnalités ajoutées: - SecureCredentialsService avec PBKDF2 (100k itérations) - Chiffrement AES-GCM des clés spend/scan - Interface utilisateur complète pour gestion credentials - Tests unitaires complets - Architecture modulaire avec EventBus - Gestion mémoire optimisée - Performance monitoring - Web Workers pour encodage asynchrone 🛡️ Sécurité: - Dérivation PBKDF2 avec salt unique - Chiffrement AES-GCM des clés sensibles - Validation force mot de passe - Stockage sécurisé IndexedDB + WebAuthn - Logging sécurisé sans exposition données 🔧 Corrections: - Erreur 500 résolue (clé dupliquée package.json) - Configuration Vite simplifiée - Dépendances manquantes corrigées 📊 Améliorations: - Architecture découplée avec repositories - Services spécialisés (PairingService, etc.) - Monitoring performance et mémoire - Tests avec couverture complète - Documentation technique détaillée
88 lines
2.6 KiB
TypeScript
88 lines
2.6 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 = import.meta.env.VITE_JWT_SECRET_KEY;
|
|
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;
|
|
}
|
|
}
|
|
}
|