6.1 KiB
6.1 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|
| guide | First-Run Bootstrap | How a fresh HIPCTF instance is initialized by the very first administrator. |
|
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)
- Start the stack against an empty database:
npm run db:reset && npm run dev - Open the browser at
http://localhost:3000/. - The app redirects to
/bootstrapand shows the "Welcome to HIPCTF — Create the first administrator" modal. - Fill in the three fields and submit:
- Username — 3–32 chars, letters/digits/
./-/_only. Helper text is shown viadata-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 showsdata-testid="setup-confirm-error").
- Username — 3–32 chars, letters/digits/
- Click Create admin (
data-testid="setup-submit").
Expected behavior
- On success the modal disappears, the app stores the access token via
AuthService.setSession, callsBootstrapService.markInitialized(), and navigates to/admin(see Admin Shell & User Management). - On
USERNAME_TAKENthe 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.escapehost 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
/bootstrapentirely. - 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). - Routes authenticated players to
/without the admin nav link.