fix(ihm_client): génération et persistance locale d'une clé HMAC si VITE_JWT_SECRET_KEY absent
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
This commit is contained in:
parent
a6f54dc8f5
commit
4f7d51e9b2
@ -21,9 +21,40 @@ export default class TokenService {
|
|||||||
return TokenService.instance;
|
return TokenService.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getOrCreateSecret(): Uint8Array {
|
||||||
|
// Priorité à la variable d'environnement si définie et non vide
|
||||||
|
if (this.SECRET_KEY && String(this.SECRET_KEY).trim().length > 0) {
|
||||||
|
return new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, on persiste une clé aléatoire locale (stable entre sessions) côté navigateur
|
||||||
|
try {
|
||||||
|
const storageKey = 'ihm_jwt_secret_key_v1';
|
||||||
|
let secretB64 = localStorage.getItem(storageKey);
|
||||||
|
if (!secretB64) {
|
||||||
|
const random = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(random);
|
||||||
|
// Stocker en base64 pour être textuel
|
||||||
|
secretB64 = btoa(String.fromCharCode(...random));
|
||||||
|
localStorage.setItem(storageKey, secretB64);
|
||||||
|
}
|
||||||
|
// Décoder base64 → Uint8Array
|
||||||
|
const binary = atob(secretB64);
|
||||||
|
const bytes = new Uint8Array(binary.length);
|
||||||
|
for (let i = 0; i < binary.length; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
} catch (_e) {
|
||||||
|
// Fallback minimal si localStorage indisponible (mode très restrictif)
|
||||||
|
const fallback = 'fallback-secret-key-please-persist';
|
||||||
|
return new Uint8Array(this.encoder.encode(fallback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async generateSessionToken(origin: string): Promise<TokenPair> {
|
async generateSessionToken(origin: string): Promise<TokenPair> {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = this.getOrCreateSecret();
|
||||||
|
|
||||||
const accessToken = await new jose.SignJWT({ origin, type: 'access' })
|
const accessToken = await new jose.SignJWT({ origin, type: 'access' })
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
.setProtectedHeader({ alg: 'HS256' })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
@ -41,16 +72,16 @@ export default class TokenService {
|
|||||||
|
|
||||||
async validateToken(token: string, origin: string): Promise<boolean> {
|
async validateToken(token: string, origin: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = this.getOrCreateSecret();
|
||||||
const { payload } = await jose.jwtVerify(token, secret);
|
const { payload } = await jose.jwtVerify(token, secret);
|
||||||
|
|
||||||
return payload.origin === origin;
|
return payload.origin === origin;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.code === 'ERR_JWT_EXPIRED') {
|
if (error?.code === 'ERR_JWT_EXPIRED') {
|
||||||
console.log('Token expiré');
|
console.log('Token expiré');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Erreur de validation du token:', error);
|
console.error('Erreur de validation du token:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -65,7 +96,7 @@ export default class TokenService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier le type du token
|
// Vérifier le type du token
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = this.getOrCreateSecret();
|
||||||
const { payload } = await jose.jwtVerify(refreshToken, secret);
|
const { payload } = await jose.jwtVerify(refreshToken, secret);
|
||||||
if (payload.type !== 'refresh') {
|
if (payload.type !== 'refresh') {
|
||||||
return null;
|
return null;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user