NicolasCantu bf680ab6dd ci: docker_tag=pbkdf2-credentials
🔐 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
2025-10-23 12:51:49 +02:00

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;
}
}
}