Files
HIPCTF2/docs/architecture/frontend-structure.md
T

7.4 KiB

type, title, description, tags, timestamp
type title description tags timestamp
architecture Frontend Structure Angular routes, components, services, guards, and interceptors.
architecture
frontend
angular
2026-07-21T16:48:20Z

Routes

Routes live in frontend/src/app/app.routes.ts:

Path Component Guard Notes
/bootstrap SetupCreateAdminComponent Lazy-loaded first-admin creation route. Non-dismissible modal overlay while initialized === false; successful creation navigates to /admin.
/login LoginComponent Standard login form.
/ HomeComponent (shell) authGuard Requires auth + initialized. Hosts a <router-outlet> for child routes.
/admin AdminUsersComponent adminGuard Child of /; requires role === 'admin'.
** Redirect to / Wildcard fallback.

Components

All components are standalone (no NgModules). Each component imports FormsModule directly and uses Angular signals for state.

Component Path Purpose
AppComponent frontend/src/app/app.component.ts Root; renders <router-outlet>, calls BootstrapService.load() then AuthService.restoreSession().
HomeComponent frontend/src/app/features/home/home.component.ts Authenticated shell with header, conditional admin nav, and <router-outlet> for children.
AdminUsersComponent frontend/src/app/features/admin/admin-users.component.ts Admin-only user list rendered inside the home shell.
LoginComponent frontend/src/app/features/auth/login.component.ts Username/password form.
SetupCreateAdminComponent frontend/src/app/features/setup/setup-create-admin.component.ts First-admin bootstrap modal with typed reactive form, inline validation messages (via the field-error helpers), retry handling, session initialization, and redirect to /admin.

Services

Service Path Purpose
AuthService frontend/src/app/core/services/auth.service.ts Signal-backed access token + current user; persists session to sessionStorage and exposes restoreSession() + waitUntilHydrated().
BootstrapService frontend/src/app/core/services/bootstrap.service.ts Fetches /api/v1/bootstrap, applies theme tokens to CSS; exposes ready() that deduplicates the in-flight load.
AuthSessionStorage (helpers) frontend/src/app/core/services/auth.session-storage.ts Pure readStoredSession / writeStoredSession / clearStoredSession against sessionStorage (key hipctf.auth.v1).
SetupCreateAdminService frontend/src/app/features/setup/setup-create-admin.service.ts Preflights the CSRF cookie, calls POST /api/v1/setup/create-admin, and normalizes success/failure responses.

Guards and interceptors

Symbol Path Purpose
authGuard frontend/src/app/core/guards/auth.guard.ts Async CanActivateFn; awaits BootstrapService.ready() and AuthService.waitUntilHydrated(), then delegates to decideAuthRedirect.
decideAuthRedirect frontend/src/app/core/guards/auth.guard.decision.ts Pure decision function mirroring decideAdminGuard; redirects to /bootstrap or /login, returns true otherwise.
adminGuard frontend/src/app/core/guards/admin.guard.ts Async CanActivateFn; awaits bootstrap + auth hydration, then delegates to decideAdminGuard (pure). Redirects to /bootstrap, /login, or / based on state; allows only admins.
decideAdminGuard frontend/src/app/core/guards/admin.guard.decision.ts Pure decision function: returns {kind:'allow'} or {kind:'redirect', path}.
authInterceptor frontend/src/app/core/interceptors/auth.interceptor.ts Attaches Authorization: Bearer <accessToken> header.
csrfInterceptor frontend/src/app/core/interceptors/csrf.interceptor.ts Attaches X-CSRF-Token header on POST/PUT/PATCH/DELETE.

Both interceptors are wired in frontend/src/main.ts via provideHttpClient(withInterceptors([csrfInterceptor, authInterceptor])) (notably CSRF runs first so the token is attached before the auth header).

Bootstrap flow

  1. AppComponent.ngOnInit()BootstrapService.load() then AuthService.restoreSession(). Both are deduplicated so concurrent callers share a single in-flight promise (loadPromise / hydrateWaiters).
  2. BootstrapService calls GET /api/v1/bootstrap (public, with credentials: 'include').
  3. The payload sets initialized (true iff any admin user exists), pageTitle, logo, welcomeMarkdown, the active theme, and the default challenge IP.
  4. Theme tokens are written to CSS custom properties on document.documentElement (--color-primary, --font-family, --radius-*, etc.) consumed by frontend/src/styles.css.
  5. AuthService.restoreSession() rehydrates the in-memory signals from sessionStorage (key hipctf.auth.v1) and POSTs /api/v1/auth/refresh with withCredentials: true to mint a fresh access token. On success it persists the new token; on failure it clears storage. Either way it flips hydrated() so the guards can unblock.
  6. The authGuard (and adminGuard) await BootstrapService.ready() and AuthService.waitUntilHydrated() before reading initialized() / isAuthenticated(), which prevents a flash of /bootstrap or /login on a hard refresh of a deep link.

Home shell flow

After successful auth, HomeComponent renders a thin shell layout:

  1. The header (shell-header) shows the page title and signed-in identity.
  2. When shouldShowAdminNav({isAuthenticated, role}) returns true (see frontend/src/app/features/home/home.shell.ts) the admin nav link (data-testid="nav-admin") is rendered.
  3. Child routes (e.g. /adminAdminUsersComponent) render into the shell's <router-outlet>.
  4. AdminUsersComponent calls AdminService.listUsers() on init; loading, error, and the rendered list are exposed as Angular signals.

See also