121 lines
4.8 KiB
JavaScript
121 lines
4.8 KiB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
import { Injectable, computed, signal } from '@angular/core';
|
|
import { JwtService } from './jwt.service';
|
|
const TOKEN_STORAGE_KEY = 'ed25519AdminToken';
|
|
const REFRESH_STORAGE_KEY = 'ed25519AdminRefreshToken';
|
|
/** Refresh this long before actual expiry, so a request never races an expiring token. */
|
|
const REFRESH_SKEW_MS = 60_000;
|
|
/**
|
|
* Holds the Ed25519-flow JWT/refresh-token pair and derived claims. Separate
|
|
* from the telegram module's AdminAuthService (Telegram-session state) by
|
|
* design - the two auth mechanisms are not merged until both ship on the
|
|
* same backend and a migration decision is made.
|
|
*/
|
|
let SessionService = class SessionService {
|
|
constructor() {
|
|
this.jwt = new JwtService();
|
|
this.tokenSignal = signal(null);
|
|
this.refreshTokenSignal = signal(null);
|
|
this.claimsSignal = signal(null);
|
|
this.statusSignal = signal('unknown');
|
|
this.token = this.tokenSignal.asReadonly();
|
|
this.claims = this.claimsSignal.asReadonly();
|
|
this.status = this.statusSignal.asReadonly();
|
|
this.isAuthenticated = computed(() => this.statusSignal() === 'authenticated');
|
|
this.role = computed(() => this.claimsSignal()?.role ?? null);
|
|
}
|
|
/** Called once by AuthService on init to wire up the refresh trigger without a circular DI dependency. */
|
|
onRefreshDue(callback) {
|
|
this.refreshCallback = callback;
|
|
}
|
|
/** Restores session state from persisted storage. Returns true if a (possibly expired) session was found. */
|
|
restore() {
|
|
this.statusSignal.set('restoring');
|
|
const token = this.readStorage(TOKEN_STORAGE_KEY);
|
|
const refreshToken = this.readStorage(REFRESH_STORAGE_KEY);
|
|
if (!token || !refreshToken) {
|
|
this.statusSignal.set('unauthenticated');
|
|
return false;
|
|
}
|
|
const claims = this.jwt.decode(token);
|
|
if (!claims) {
|
|
this.clear();
|
|
return false;
|
|
}
|
|
this.tokenSignal.set(token);
|
|
this.refreshTokenSignal.set(refreshToken);
|
|
this.claimsSignal.set(claims);
|
|
if (this.jwt.isExpired(claims)) {
|
|
this.statusSignal.set('expired');
|
|
}
|
|
else {
|
|
this.statusSignal.set('authenticated');
|
|
this.scheduleRefresh(claims);
|
|
}
|
|
return true;
|
|
}
|
|
activate(tokens) {
|
|
const claims = this.jwt.decode(tokens.token);
|
|
if (!claims) {
|
|
throw new Error('Received a malformed JWT from the auth backend.');
|
|
}
|
|
this.tokenSignal.set(tokens.token);
|
|
this.refreshTokenSignal.set(tokens.refreshToken);
|
|
this.claimsSignal.set(claims);
|
|
this.statusSignal.set('authenticated');
|
|
this.writeStorage(TOKEN_STORAGE_KEY, tokens.token);
|
|
this.writeStorage(REFRESH_STORAGE_KEY, tokens.refreshToken);
|
|
this.scheduleRefresh(claims);
|
|
}
|
|
getRefreshToken() {
|
|
return this.refreshTokenSignal();
|
|
}
|
|
markExpired() {
|
|
this.statusSignal.set('expired');
|
|
this.clearRefreshTimer();
|
|
}
|
|
clear() {
|
|
this.tokenSignal.set(null);
|
|
this.refreshTokenSignal.set(null);
|
|
this.claimsSignal.set(null);
|
|
this.statusSignal.set('unauthenticated');
|
|
this.removeStorage(TOKEN_STORAGE_KEY);
|
|
this.removeStorage(REFRESH_STORAGE_KEY);
|
|
this.clearRefreshTimer();
|
|
}
|
|
scheduleRefresh(claims) {
|
|
this.clearRefreshTimer();
|
|
const expiresInMs = claims.exp * 1000 - Date.now();
|
|
const refreshInMs = Math.max(expiresInMs - REFRESH_SKEW_MS, 5_000);
|
|
this.refreshTimer = setTimeout(() => this.refreshCallback?.(), refreshInMs);
|
|
}
|
|
clearRefreshTimer() {
|
|
if (this.refreshTimer) {
|
|
clearTimeout(this.refreshTimer);
|
|
this.refreshTimer = undefined;
|
|
}
|
|
}
|
|
readStorage(key) {
|
|
return typeof localStorage === 'undefined' ? null : localStorage.getItem(key);
|
|
}
|
|
writeStorage(key, value) {
|
|
if (typeof localStorage !== 'undefined') {
|
|
localStorage.setItem(key, value);
|
|
}
|
|
}
|
|
removeStorage(key) {
|
|
if (typeof localStorage !== 'undefined') {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
};
|
|
SessionService = __decorate([
|
|
Injectable({ providedIn: 'root' })
|
|
], SessionService);
|
|
export { SessionService };
|