Files
HIPCTF2/docs/guides/admin-shell.md
T
2026-07-21 22:26:44 +00:00

6.5 KiB

type, title, description, tags, timestamp
type title description tags timestamp
guide Admin Shell & User Management How an authenticated admin navigates the post-login shell and reaches the admin user-management area.
guide
admin
shell
navigation
tester
2026-07-21T22:19:08Z

When this view is available

The admin shell is the post-login UI for users with role === 'admin'. It is gated by:

Layer File Check
Client route frontend/src/app/app.routes.ts /admin uses adminGuard.
Client nav link frontend/src/app/features/home/home.component.ts Renders only when showAdminNav() is true.
Client predicate frontend/src/app/features/home/home.shell.ts (shouldShowAdminNav) isAuthenticated && role === 'admin'.
Server route backend/src/modules/admin/admin.controller.ts (mounted under @UseGuards(AdminGuard) + @Roles('admin')) Requires admin JWT.

A non-admin (or unauthenticated) request to /admin is intercepted by the client guard before any HTTP call is made, so the page never renders.

How to access (tester steps)

  1. Ensure the instance is initialized (/api/v1/bootstrap returns initialized: true). If not, follow First-Run Bootstrap.
  2. Sign in as a user with role === 'admin' via /login.
  3. The browser lands on / and renders the Home shell with a header containing the page title and a sign-in status line.
  4. Click the Admin nav link (data-testid="nav-admin") in the shell header, or navigate directly to /admin.
  5. The admin area renders inside the shell:
    • Heading: "Admin area"
    • Loading state: Loading users... (data-testid="admin-loading") while the GET is in flight.
    • User list: an unordered list (data-testid="admin-user-list") showing one <li> per user with the username in bold and the role in parentheses, e.g. root (admin).
    • Error state: a red message (data-testid="admin-error") when the request fails (e.g. backend down or auth expired).

Expected behavior

User role Visiting /admin
Unauthenticated Redirected to /login by adminGuard.
Authenticated player Redirected to / by adminGuard.
Authenticated admin Page renders, fetches GET /api/v1/admin/users, lists users.
  • When BootstrapService.initialized() is still false the guard redirects to /bootstrap instead of /login, so the first admin flow is honored.
  • The Admin nav link is hidden for non-admin users — there is no visible UI affordance to reach /admin without the role.
  • The list endpoint uses the existing authInterceptor + csrfInterceptor, so no extra plumbing is required.

Visual elements

Element Selector Purpose
Shell header .shell-header Page title + signed-in identity.
Admin nav link [data-testid="nav-admin"] RouterLink to /admin; shown to admins only.
Shell body .shell-body Holds <router-outlet> for child routes.
Admin heading h2 inside .admin-area "Admin area".
Loading message [data-testid="admin-loading"] "Loading users...".
Error message [data-testid="admin-error"] Red text; renders error().error.message or fallback.
User list [data-testid="admin-user-list"] Renders one <li> per AdminUser.

Architecture map

Step Where What happens
1 frontend/src/app/app.routes.ts / is a parent route with authGuard; /admin child uses adminGuard + lazy-loads AdminUsersComponent.
2 frontend/src/app/core/guards/admin.guard.ts adminGuard calls decideAdminGuard(...) with bootstrap + auth state.
3 frontend/src/app/core/guards/admin.guard.decision.ts Pure function returning {kind: 'allow'} or {kind: 'redirect', path}.
4 frontend/src/app/features/home/home.component.ts Shell template renders header, conditional nav (*ngIf="showAdminNav()"), and <router-outlet>.
5 frontend/src/app/features/home/home.shell.ts shouldShowAdminNav({isAuthenticated, role}) predicate (exported and unit-tested).
6 frontend/src/app/features/admin/admin-users.component.ts On init, calls AdminService.listUsers() and populates signals (loading, error, users).
7 frontend/src/app/core/services/admin.service.ts GET /api/v1/admin/users (credentials included); typed as AdminUser[].
8 backend/src/modules/admin/admin.controller.ts AdminController.list returns AdminService.listUsers({limit, cursor, role}).

Notes

  • The shell deliberately keeps HomeComponent as a thin layout owner — child routes (currently just /admin) render into its <router-outlet>. New admin sub-pages can be added as additional children without changing the shell.
  • The guard decision function (decideAdminGuard) is a pure module so it is straightforward to unit-test without Angular DI. See tests/frontend/admin-shell.spec.ts and tests/frontend/admin-navigation.spec.ts.

See also