feat(portal): /api/me/capabilities + cross-app menu links + real role label
PR 3 of 3 — final piece of the user-menu / profile / cross-app
chantier.
BFF
* New `MeModule` ships `GET /api/me/capabilities`, a curated view
derived from the active user-portal session. Returns
`{ canAccessAdmin: boolean }` today; the shape is deliberately
binary so the SPA cannot reconstruct the raw `roles` claim. 401
on missing session, consistent posture with `/auth/me`.
* ADR-0009 amended: new "Curated public view" section codifies
the "no raw roles on `/auth/me`, capabilities is the release
valve" stance; the routes table grows a row for the endpoint.
portal-shell
* New `CapabilitiesService` (app-local) calls the BFF on auth-state
flip and exposes `canAccessAdmin` as a signal. Anonymous
sessions short-circuit to the all-false default without firing
a request.
* Header's `userMenuItems` is now a `computed` — the "Open Portal
Admin" entry appears in the dropdown only when
`canAccessAdmin()` is true, pointing at
`environment.adminAppUrl`.
* Sidebar role widget reads the auth + capabilities signals to
render one of three labels ("Anonymous" / "User" /
"Administrator") in place of the hardcoded "Anonymous".
portal-admin
* Symmetric: unconditional "Open Portal Shell" entry in the admin
user menu, pointing at `environment.shellAppUrl`. Anyone who
can reach portal-admin can reach portal-shell, so no
capabilities check needed.
i18n
* `header.userMenu.openAdmin`, `sidebar.role.{administrator,user}`,
`sidebar.role.aria` reshaped to take the role as a placeholder.
This commit is contained in:
@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/c
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { AuthService, type CurrentUser } from 'feature-auth';
|
||||
import { UserMenu, type UserMenuItem } from 'shared-ui';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
/**
|
||||
* Admin-portal header. Deliberately leaner than the user-portal
|
||||
@@ -43,12 +44,13 @@ export class AdminHeader {
|
||||
});
|
||||
|
||||
// Static menu rows. Same shape as the portal-shell menu so a user
|
||||
// who learned one finds the other familiar; the cross-app
|
||||
// "Open Portal Shell" entry lands with PR 3 once the role +
|
||||
// environment plumbing is in place.
|
||||
// who learned one finds the other familiar. The "Open Portal
|
||||
// Shell" entry is unconditional here — the user-portal is public,
|
||||
// any admin can jump back to it.
|
||||
protected readonly userMenuItems: readonly UserMenuItem[] = [
|
||||
{ label: 'Profile', icon: 'user', routerLink: '/profile' },
|
||||
{ label: 'Settings', icon: 'settings', disabled: true, badge: 'Soon' },
|
||||
{ label: 'Open Portal Shell', icon: 'home', href: environment.shellAppUrl },
|
||||
];
|
||||
|
||||
protected readonly signedInAsLabel = 'Signed in as';
|
||||
|
||||
@@ -34,4 +34,13 @@ export const environment = {
|
||||
* OTLP/HTTP traces endpoint — same OTel collector as portal-shell.
|
||||
*/
|
||||
otlpEndpoint: 'http://localhost:4318/v1/traces',
|
||||
|
||||
/**
|
||||
* Origin of the sibling `portal-shell` SPA — drives the "Open
|
||||
* Portal Shell" entry in the admin user menu. Symmetric with
|
||||
* `portal-shell`'s `adminAppUrl`; per ADR-0020 the two SPAs live
|
||||
* on distinct origins, so this is a raw cross-origin `<a href>`,
|
||||
* not an Angular route.
|
||||
*/
|
||||
shellAppUrl: 'http://localhost:4200',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user