import { Injectable, computed, effect, inject, signal } from '@angular/core'; import { Result } from '@shared/kernel/fp'; import { Session } from '../domain/session'; import { DigidAdapter } from '../infrastructure/digid.adapter'; const STORAGE_KEY = 'session-v1'; /** Restore a persisted session (best-effort; corrupt entry → logged out). G2: validate the shape before trusting it. G1: the BSN is never persisted (see the effect below), so a restored session carries an empty one — it is unused after login; only `naam` is shown in the chrome. */ function restore(): Session | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw) as Partial; return typeof parsed?.naam === 'string' ? { bsn: '', naam: parsed.naam } : null; } catch { return null; } } /** * Holds the current session for the whole app. Because it is providedIn:'root' * there is exactly one instance — every component that injects it sees the same * session signal, so logging in is instantly visible everywhere (the guard, the * header, etc.). The session is mirrored to localStorage so a refresh, a deep-link, * or the full-page navigation the language switch performs (nl at `/` ⇄ en at `/en/`, * separate bundles) keeps you logged in. ponytail: localStorage, not sessionStorage — * sessionStorage's per-tab clearing dropped the login on the cross-bundle language * switch. Trade-off: the demo session now survives tab close; a real portal keeps auth * in an httpOnly cookie/token, not web storage. */ @Injectable({ providedIn: 'root' }) export class SessionStore { private digid = inject(DigidAdapter); private _session = signal(restore()); readonly session = this._session.asReadonly(); readonly isAuthenticated = computed(() => this._session() !== null); constructor() { effect(() => { const s = this._session(); // G1: persist only `naam` — never write the BSN (national ID) to storage. if (s) localStorage.setItem(STORAGE_KEY, JSON.stringify({ naam: s.naam })); else localStorage.removeItem(STORAGE_KEY); }); } /** Effectful command: authenticate, then store the session on success. */ async login(bsn: string): Promise> { const r = await this.digid.authenticate(bsn); if (r.ok) this._session.set(r.value); return r; } logout() { this._session.set(null); } }