feat(portal-bff): extract Entra roles claim onto AuthenticatedUser
#126
Reference in New Issue
Block a user
Delete Branch "feat/portal-bff-extract-roles-claim"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
First step in the
portal-adminaudit-log-viewer workstream (per ADR-0020). The BFF'sAdminRoleGuard(next PR) needs to readsession.user.rolesto enforce admin-only access to/api/admin/*. Today the session carries{ oid, tid, username, displayName, amr }— therolesclaim is dropped on the floor when the ID token comes back from Entra.This PR closes that gap:
roles: readonly string[]to AuthenticatedUser and threads it throughtoAuthenticatedUser().req.session.userautomatically via the existing module-augmentation chain in session.types.ts — no extra wiring.Defensive parsing
Mirrors the existing
amrextraction pattern:["admin", "editor"]["admin", "editor"][]"admin")[]["admin", 42, null, "editor"])["admin", "editor"]Empty array means "user has no app role assigned", not "claim was unparseable" — both collapse to the same value because both are equally non-authoritative for the admin guard.
Why this is its own PR
The
AdminRoleGuard+@RequireAdmin()decorator + first/api/admin/meself-test endpoint will follow in the next PR. Splitting the claim extraction out makes both diffs trivial to read and lets the second PR focus on guard semantics + audit emission without the mechanical fixture updates that came with adding a newAuthenticatedUserfield.Surface impact — none yet
PublicUser(the SPA-facing shape returned byGET /api/auth/me) is deliberately unchanged. Exposingrolesto the SPA happens in the next PR alongside the conditional admin-link rendering — without a consumer in this PR it would be dead code.SignInActorcarries{ oid, amr }only; the audit log doesn't needrolesand won't get it.Test plan
pnpm nx test portal-bff— 203 specs pass (was 199; +4 new specs covering the four parsing cases above).pnpm exec nx affected -t format:check lint test build --base=origin/main— clean (the pre-existing_res/_nextwarnings inrate-limit.middleware.tsare unrelated).roles: [].adminapp role to be declared on the Entra registration and assigned to a test user. Out of scope for this PR; will be validated when theAdminRoleGuardlands and there is a 403 to observe.rolesclaim onto AuthenticatedUser