Files
HIPCTF2/docs/guides/bootstrap.md
T

6.4 KiB
Raw Blame History

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.
guide
bootstrap
first-admin
onboarding
tester
2026-07-21T16:23:40Z

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. 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).
  • 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"] 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).
  • Routes authenticated players to / without the admin nav link.

See also