--- type: architecture title: Frontend Structure description: Angular routes, components, services, guards, and interceptors. tags: [architecture, frontend, angular] timestamp: 2026-07-21T15:05:00Z --- # Routes Routes live in `frontend/src/app/app.routes.ts`: | Path | Component | Guard | Notes | |--------------|----------------------------------|---------------|----------------------------------------| | `/bootstrap` | `SetupCreateAdminComponent` | — | Rendered only when `initialized === false`. Non-dismissible modal overlay. | | `/login` | `LoginComponent` | — | Standard login form. | | `/` | `HomeComponent` (shell) | `authGuard` | Requires auth + initialized. Hosts a `` 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 ``, calls `BootstrapService.load()` then `AuthService.restoreSession()`. | | `HomeComponent` | `frontend/src/app/features/home/home.component.ts` | Authenticated shell with header, conditional admin nav, and `` 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 (non-dismissible, typed reactive forms). | # 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`). | | `AdminService` | `frontend/src/app/core/services/admin.service.ts` | `GET /api/v1/admin/users` (typed `AdminUser[]`). | # 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 ` 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. `/admin` → `AdminUsersComponent`) render into the shell's ``. 4. `AdminUsersComponent` calls `AdminService.listUsers()` on init; loading, error, and the rendered list are exposed as Angular signals. # See also - [System Overview](/architecture/overview.md) - [Backend Module Map](/architecture/backend-modules.md) - [First-Run Bootstrap](/guides/bootstrap.md) - [Admin Shell & User Management](/guides/admin-shell.md)