Files
HIPCTF2/docs/guides/bootstrap.md
T
2026-07-21 17:18:52 +00:00

114 lines
6.8 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-21T17:18:08Z
---
# 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 endpoint becomes a 409 `SYSTEM_INITIALIZED` 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
`409 SYSTEM_INITIALIZED` once any admin row exists, so the flow is safe
even if the SPA is bypassed. Concurrent first-admin requests are
serialized inside the Nest process so that two simultaneous submissions
cannot both succeed — the loser receives the same `SYSTEM_INITIALIZED`
409 (see [Setup Endpoint — Concurrency](/api/setup.md)).
# 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. The
username field displays inline validation after it is touched.
- **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; mismatch displays
an inline error.
5. Click **Create admin** (`data-testid="setup-submit"`). The button is
disabled only while a submission is in progress and shows **Creating...**
with a spinner during that request.
6. To verify client-side validation, submit with empty or partial fields. The
overlay stays open, the relevant field errors appear, and no administrator
is created or setup request is sent.
# 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; shows inline error on touch when empty. |
| Password error | `[data-testid="setup-password-error"]` | Renders `passwordError()` (currently `required`). |
| Confirm input | `[data-testid="setup-password-confirm"]` | Reactive control. |
| Submit button | `[data-testid="setup-submit"]` | Triggers validation even when invalid; disabled only while submitting. |
| 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`)