Files
HIPCTF2/docs/guides/bootstrap.md
T

107 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: guide
title: First-Run Bootstrap
description: How a fresh HIPCTF instance is initialized by the very first administrator.
tags: [guide, bootstrap, first-admin, onboarding, tester]
timestamp: 2026-07-21T15:05:00Z
---
# When this flow runs
A brand-new deployment shows a non-dismissible modal asking the operator
to create the very first admin user. This is gated by the
`initialized` flag returned from `GET /api/v1/bootstrap`:
| `initialized` | User experience |
|---------------|---------------------------------------------------------------|
| `false` | Every route is redirected to `/bootstrap` until an admin is created. |
| `true` | The setup route becomes a 404 and the modal can never appear again. |
The `authGuard` enforces the redirect on the client side; the backend
independently rejects `/api/v1/setup/create-admin` with `404 NOT_FOUND`
once any admin row exists, so the flow is safe even if the SPA is
bypassed.
# How to access (tester steps)
1. Start the stack against an empty database:
```
npm run db:reset && npm run dev
```
2. Open the browser at `http://localhost:3000/`.
3. The app redirects to `/bootstrap` and shows the
**"Welcome to HIPCTF — Create the first administrator"** modal.
4. Fill in the three fields and submit:
- **Username** — 332 chars, letters/digits/`.`/`-`/`_` only. Helper
text is shown via `data-testid="setup-username-error"` when the
pattern fails.
- **Password** — must satisfy the policy shown directly below the
field (default: 12 chars with upper, lower, digit, symbol).
- **Confirm Password** — must equal the password above (verified by
the group-level `passwordMatchValidator`; mismatch shows
`data-testid="setup-confirm-error"`).
5. Click **Create admin** (`data-testid="setup-submit"`).
# Expected behavior
* On success the modal disappears, the app stores the access token via
`AuthService.setSession`, calls `BootstrapService.markInitialized()`,
and navigates to `/admin` (see [Admin Shell & User Management](/guides/admin-shell.md)).
* On `USERNAME_TAKEN` the form shows a red alert:
`"Username already exists"`. The **Retry** button is intentionally
hidden because the username must be changed first.
* On any other failure (network, `WEAK_PASSWORD`, `VALIDATION_FAILED`,
`RATE_LIMITED`) the alert shows `"Setup failed, please retry"` and a
**Retry** link (`data-testid="setup-retry"`) re-submits the same
payload.
The modal **cannot** be dismissed by the user:
* Clicking the backdrop is a no-op (`swallowBackdrop`).
* Pressing **Esc** is swallowed by the `keydown.escape` host listener.
# Visual elements
| Element | Selector | Purpose |
|--------------------------------|----------------------------------|-----------------------------------------------|
| Modal overlay | `.overlay` | Full-screen backdrop. |
| Modal card | `.modal[role="dialog"]` | Contains the form; stops click propagation. |
| Username input | `[data-testid="setup-username"]` | Reactive control with length/pattern rules. |
| Password input | `[data-testid="setup-password"]` | Reactive control. |
| Confirm input | `[data-testid="setup-password-confirm"]` | Reactive control. |
| Submit button | `[data-testid="setup-submit"]` | Disabled while submitting or invalid. |
| Server error alert | `[data-testid="setup-server-error"]` | Displays `serverError()`. |
| Retry button | `[data-testid="setup-retry"]` | Re-submits; absent when the error is non-retryable. |
| Password policy hint | `.hint` | Live description from `BootstrapService.passwordPolicy()`. |
# Behind the scenes (architecture map)
| Step | Where | What happens |
|------|--------------------------------------------------------|-------------------------------------------------------------------------|
| 1 | `BootstrapService.load()` | Calls `GET /api/v1/bootstrap`, sets `initialized = false`. |
| 2 | `authGuard` | Sees `initialized() === false`, redirects to `/bootstrap`. |
| 3 | `SetupCreateAdminComponent` | Renders the modal; reactive form enforces username + password policy + passwordMatch. |
| 4 | `SetupCreateAdminService.ensureCsrf()` | Preflights `GET /api/v1/auth/csrf` so the response sets the `csrf` cookie. |
| 5 | `POST /api/v1/setup/create-admin` | `SetupService.createAdmin` runs in a TypeORM transaction; creates the user with `role='admin'`; mints session via `AuthService.createSession`. |
| 6 | `setRefreshCookie` | Sets the `rt` HttpOnly cookie. |
| 7 | Component on success | `AuthService.setSession`, `BootstrapService.markInitialized()`, navigate to `/admin`. |
| 8 | Subsequent `authGuard` checks | `initialized === true`; require auth instead. |
# What the operator sees after init
Once the first admin exists, `/api/v1/bootstrap` returns
`initialized: true`. The app now:
* Skips `/bootstrap` entirely.
* Routes unauthenticated users to `/login`.
* Routes authenticated admins to `/` (the Home shell) and renders the
admin nav link. The first admin lands directly on the user list at
`/admin` (see [Admin Shell & User Management](/guides/admin-shell.md)).
* Routes authenticated players to `/` without the admin nav link.
# See also
- [Setup Endpoint](/api/setup.md)
- [Frontend Structure](/architecture/frontend-structure.md)
- [Backend Module Map](/architecture/backend-modules.md)
- [Bootstrap payload source of truth](`/architecture/overview.md`)