feat(portal-admin): spa auth wiring + admin shell skeleton #134
Reference in New Issue
Block a user
Delete Branch "feat/portal-admin-spa-auth-shell"
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
Phase-3a step per ADR-0020 §"Confirmation" item 4 (entry route + admin shell). Wires the existing
feature-authlibrary against the distinct admin OIDC routes (/api/admin/auth/*) the BFF exposes since PR #129, ships a lean header/sidebar/footer chrome with an "Admin" badge so an internal user can never mistake the surface for portal-shell, and gives the landing page a self-test panel that confirms the auth chain end-to-end as soon as anadminEntra role gets assigned.What lands
Lib change —
AUTH_PATH_PREFIXinjection tokenlibs/feature/authgains one injection token. AuthService composes URLs as${bffBaseUrl}${pathPrefix}/{me,login,logout}instead of hard-coding/auth/.... Default factory returns/auth, so portal-shell-shaped consumers keep working without an explicit provider — no churn on existing call sites. Admin hosts override with/admin/auth.The interceptors (
bffCredentialsInterceptor,csrfInterceptor,bffUnauthorizedInterceptor) are unchanged — they only care about the BFF base URL, not the path prefix.portal-admin wiring
HttpClientwith the standard interceptor chain (credentials → csrf → unauthorized),AUTH_BFF_BASE_URLfrom env,AUTH_PATH_PREFIX = '/admin/auth',AUTH_CSRF_COOKIE_NAMEfrom env.Admin shell
aria-disabledplaceholders with a "Soon" badge so the navigation shape is visible even before they ship.Home — auth self-test panel
apps/portal-admin/src/app/pages/home/:
displayName,username,tenant,oidin a monospaced detail list.AuthService.login()→ 302 through/api/admin/auth/login.Known limitations (v1, documented)
portal_csrfon session creation. A user with both portals open will see overwrites on the second sign-in; mutating actions on the first surface will then 403 until refresh. Splitting toportal_admin_csrfis a follow-up if the pattern becomes common.$localizeplumbing is wired (matches portal-shell), but adding markers to the shell here would require regeneratingmessages.fr.xlfandi18nMissingTranslation=errorfails the prod build on every gap. Full admin i18n is its own follow-up.LayoutStateServicenot yet consumed. No collapse toggle in v1. Theme preference still threads through because both apps read the samelocalStoragekey — toggle in portal-shell, see it honoured in portal-admin.ci:perfonly runs against portal-shell. Admin perf budgets are enforced at build time byapps/portal-admin/project.json(maximumError == maximumWarningper ADR-0020's relaxed thresholds: 500 KB initial JS / 1.5 MB initial total). Adding admin topnpm ci:perf's gzip + Lighthouse chain is a follow-up.Test plan
pnpm nx test feature-auth— 29 specs pass (was 28; +1 for theAUTH_PATH_PREFIXoverride).pnpm nx test portal-admin— 15 specs pass (was 1; +14: AdminHeader 5, AdminSidebar 3, AdminFooter 2, Home 4, App 1 expanded).pnpm exec nx affected -t format:check lint test build --base=origin/main— clean.dist/apps/portal-admin/browser: 86.65 KB initial / 300 KB budget, 4.46 KB CSS / 150 KB budget, 2.15 KB largest lazy / 100 KB per-chunk budget. Bothen+frbundles checked thanks to PR #133's multi-locale detection.adminrole assignment. Once available:pnpm nx serve portal-admin, visithttp://localhost:4300, see "No admin session detected", click "Sign in via Entra", complete the round-trip, land back on/with the signed-in payload +portal_admin_sessioncookie set.Notes for the reviewer
AdminHeaderis intentionally narrower thanHeaderfrom portal-shell — no shared base class. The two are likely to evolve in different directions (admin-specific banner with system-status badges, audit-trail link, etc.) and a premature abstraction would be expensive to undo.AdminSidebar's "Soon" badge is a deliberate signal — without it, an admin who clicked a placeholder link and got a route-not-found would assume the app is broken. The badge sets expectations.:host-context(.dark)for dark-mode SCSS instead of:host(.dark)— same pattern as portal-shell, since the.darkclass lives on<html>outside the component's view encapsulation.