docs: update documentation to OKF v0.1 format
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
---
|
||||
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-21T14:43: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** — 3–32 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 `/challenges`.
|
||||
* 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 `/challenges`. |
|
||||
| 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 users to `/` (Home) which itself redirects to
|
||||
`/challenges` once admin features are wired.
|
||||
|
||||
# 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`)
|
||||
Reference in New Issue
Block a user