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

5.9 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-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 — 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 /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