import { Injectable } from '@angular/core'; import { Result, ok, err } from '@shared/kernel/fp'; import { currentRole } from '@shared/infrastructure/role'; import { problemDetail } from '@shared/infrastructure/api-error'; import { environment } from '@shared/environments/environment'; const REVEAL_FAILED = $localize`:@@brief.reveal.failed:Het BIG-nummer kon niet worden getoond.`; /** * Field-level PII reveal (PRD-0002 §5c). The case screen ships the BIG-nummer masked; * this unmasks it, gated server-side by the reveal capability AND a step-up. The * step-up is stubbed as the `X-Step-Up` header — the caller sends it only after the * user's confirm gesture, so a plain call (or a role without the capability) 403s. * * Hand-written fetch (not the `ApiClient`) because the call needs a per-request header; * `.ExcludeFromDescription()` on the endpoint keeps the generated client JSON-only, the * same seam as `/brief/preview` and uploads — which also means `X-Role` is set here. */ @Injectable({ providedIn: 'root' }) export class RevealBigNummerAdapter { async reveal(): Promise> { let res: Response; try { res = await fetch(`${environment.apiBaseUrl}/api/v1/brief/reveal-bignummer`, { method: 'POST', headers: { 'X-Role': currentRole(), 'X-Step-Up': 'true' }, }); } catch { return err(REVEAL_FAILED); } if (!res.ok) return err(await errorMessage(res)); const body: unknown = await res.json().catch(() => null); // Trust boundary: validate the shape before handing back a plain string. if ( typeof body === 'object' && body !== null && typeof (body as { bigNummer?: unknown }).bigNummer === 'string' ) { return ok((body as { bigNummer: string }).bigNummer); } return err(REVEAL_FAILED); } } async function errorMessage(res: Response): Promise { try { return problemDetail(await res.json(), REVEAL_FAILED); } catch { return REVEAL_FAILED; } }