docs: update documentation to OKF v0.1 format

This commit is contained in:
OpenVelo Agent
2026-07-21 17:18:52 +00:00
parent cabd393288
commit 721f58a068
4 changed files with 45 additions and 16 deletions
+34 -8
View File
@@ -3,7 +3,7 @@ type: api
title: Setup Endpoint title: Setup Endpoint
description: Dedicated POST /api/v1/setup/create-admin endpoint used only while no administrator exists. description: Dedicated POST /api/v1/setup/create-admin endpoint used only while no administrator exists.
tags: [api, setup, bootstrap, first-admin] tags: [api, setup, bootstrap, first-admin]
timestamp: 2026-07-21T16:48:20Z timestamp: 2026-07-21T17:18:08Z
--- ---
# Endpoint # Endpoint
@@ -38,13 +38,13 @@ refreshing without storing the raw token in JS.
# Error envelope # Error envelope
| HTTP | `code` | When | | HTTP | `code` | When |
|------|---------------------|----------------------------------------------------------------------| |------|-----------------------|----------------------------------------------------------------------|
| 400 | `VALIDATION_FAILED` | zod body validation failed (length, regex, or `passwordConfirm`). | | 400 | `VALIDATION_FAILED` | zod body validation failed (length, regex, or `passwordConfirm`). |
| 400 | `WEAK_PASSWORD` | Argon2 policy rejects the password (length / mixed-case requirement). | | 400 | `WEAK_PASSWORD` | Argon2 policy rejects the password (length / mixed-case requirement). |
| 404 | `NOT_FOUND` | An admin already exists — the route is hidden once initialized. | | 409 | `SYSTEM_INITIALIZED` | An admin already exists. Returned to any request that arrives after bootstrap completes (also returned to the loser of a concurrent race — see *Concurrency* below). |
| 409 | `USERNAME_TAKEN` | Username collision (reachable only during a race before init). | | 409 | `USERNAME_TAKEN` | Username collision (defensive: reachable only if two concurrent requests with the *same* username somehow slip past the in-process lock). |
| 429 | `RATE_LIMITED` | `RegistrationRateLimitService` blocked the IP. | | 429 | `RATE_LIMITED` | `RegistrationRateLimitService` blocked the IP. |
# Wiring # Wiring
@@ -59,6 +59,32 @@ refreshing without storing the raw token in JS.
(before any cookie exists) is not blocked. The middleware still mints (before any cookie exists) is not blocked. The middleware still mints
a `csrf` cookie on the response so subsequent calls are covered. a `csrf` cookie on the response so subsequent calls are covered.
# Concurrency
`SetupService` serializes bootstrap attempts inside a single Nest process
via an in-process promise chain (`#bootstrapChain` + private
`runBootstrap()`). Every caller runs its transaction exactly once; the
chain never short-circuits, so validation and rate-limit semantics stay
identical to the single-request case.
Behavior under contention:
1. Two simultaneous first-admin POSTs enter `runBootstrap()`.
2. The first request runs its transaction and creates the admin row.
3. The second request waits on the first, then runs its own
transaction; the `adminCount > 0` guard inside the transaction
fires and it throws `SYSTEM_INITIALIZED` (HTTP 409).
4. Exactly one admin row and one refresh-token row exist afterwards.
The lock is **per-process**; it does not coordinate multiple Nest
instances behind a load balancer. Multi-replica deployments still rely
on the unique-index/transaction guard, not on this chain.
The matching test lives at
`tests/backend/setup-create-admin.spec.ts` ("only one of two concurrent
first-admin requests succeeds; the other receives a controlled
conflict").
# Frontend consumer # Frontend consumer
`frontend/src/app/features/setup/setup-create-admin.service.ts` calls `frontend/src/app/features/setup/setup-create-admin.service.ts` calls
+2 -2
View File
@@ -25,7 +25,7 @@ also registers two global providers:
| `SettingsModule` | `backend/src/modules/settings/settings.module.ts` | Exposes `SettingsService` (get/set/getAll over `setting` table). | | `SettingsModule` | `backend/src/modules/settings/settings.module.ts` | Exposes `SettingsService` (get/set/getAll over `setting` table). |
| `AuthModule` | `backend/src/modules/auth/auth.module.ts` | `AuthController` (`/api/v1/auth/*`) + `AuthService` (login/refresh/logout/register-first-admin).| | `AuthModule` | `backend/src/modules/auth/auth.module.ts` | `AuthController` (`/api/v1/auth/*`) + `AuthService` (login/refresh/logout/register-first-admin).|
| `UsersModule` | `backend/src/modules/users/users.module.ts` | `UsersController` (`/api/v1/auth/register-first-admin`) + `UsersService` (last-admin invariant). | | `UsersModule` | `backend/src/modules/users/users.module.ts` | `UsersController` (`/api/v1/auth/register-first-admin`) + `UsersService` (last-admin invariant). |
| `SetupModule` | `backend/src/modules/setup/setup.module.ts` | `SetupController` (`POST /api/v1/setup/create-admin`) + `SetupService`. Returns 404 once any admin exists (route hidden). | | `SetupModule` | `backend/src/modules/setup/setup.module.ts` | `SetupController` (`POST /api/v1/setup/create-admin`) + `SetupService`. Returns 409 `SYSTEM_INITIALIZED` once any admin exists; serializes concurrent first-admin attempts via an in-process promise chain. |
| `SystemModule` | `backend/src/modules/system/system.module.ts` | `SystemController` (`/api/v1/bootstrap`, `/event/status`, SSE streams) + `SystemService`. | | `SystemModule` | `backend/src/modules/system/system.module.ts` | `SystemController` (`/api/v1/bootstrap`, `/event/status`, SSE streams) + `SystemService`. |
| `AdminModule` | `backend/src/modules/admin/admin.module.ts` | `AdminController` (`/api/v1/admin/users*`) + `AdminService`. Gated by `AdminGuard` + `@Roles('admin')`. | | `AdminModule` | `backend/src/modules/admin/admin.module.ts` | `AdminController` (`/api/v1/admin/users*`) + `AdminService`. Gated by `AdminGuard` + `@Roles('admin')`. |
| `UploadsModule` | `backend/src/modules/uploads/uploads.module.ts` | `UploadsController` (`/api/v1/uploads/*`). Admin-only multipart. | | `UploadsModule` | `backend/src/modules/uploads/uploads.module.ts` | `UploadsController` (`/api/v1/uploads/*`). Admin-only multipart. |
@@ -37,7 +37,7 @@ also registers two global providers:
|------------------------|-------------------------|--------------|--------------------------------------------------------------| |------------------------|-------------------------|--------------|--------------------------------------------------------------|
| `AuthController` | `/api/v1/auth` | Mostly public (login, refresh, logout, csrf) | `backend/src/modules/auth/auth.controller.ts` | | `AuthController` | `/api/v1/auth` | Mostly public (login, refresh, logout, csrf) | `backend/src/modules/auth/auth.controller.ts` |
| `UsersController` | `/api/v1/auth/register-first-admin` | Public (bootstrap-only) | `backend/src/modules/users/users.controller.ts` | | `UsersController` | `/api/v1/auth/register-first-admin` | Public (bootstrap-only) | `backend/src/modules/users/users.controller.ts` |
| `SetupController` | `/api/v1/setup/create-admin` | Public (bootstrap-only; 404 once an admin exists) | `backend/src/modules/setup/setup.controller.ts` | | `SetupController` | `/api/v1/setup/create-admin` | Public (bootstrap-only; 409 `SYSTEM_INITIALIZED` once an admin exists) | `backend/src/modules/setup/setup.controller.ts` |
| `AdminController` | `/api/v1/admin` | Admin only | `backend/src/modules/admin/admin.controller.ts` | | `AdminController` | `/api/v1/admin` | Admin only | `backend/src/modules/admin/admin.controller.ts` |
| `SystemController` | `/api/v1` | Public | `backend/src/modules/system/system.controller.ts` | | `SystemController` | `/api/v1` | Public | `backend/src/modules/system/system.controller.ts` |
| `UploadsController` | `/api/v1/uploads` | Admin only | `backend/src/modules/uploads/uploads.controller.ts` | | `UploadsController` | `/api/v1/uploads` | Admin only | `backend/src/modules/uploads/uploads.controller.ts` |
+8 -5
View File
@@ -3,7 +3,7 @@ type: guide
title: First-Run Bootstrap title: First-Run Bootstrap
description: How a fresh HIPCTF instance is initialized by the very first administrator. description: How a fresh HIPCTF instance is initialized by the very first administrator.
tags: [guide, bootstrap, first-admin, onboarding, tester] tags: [guide, bootstrap, first-admin, onboarding, tester]
timestamp: 2026-07-21T16:48:20Z timestamp: 2026-07-21T17:18:08Z
--- ---
# When this flow runs # When this flow runs
@@ -15,12 +15,15 @@ to create the very first admin user. This is gated by the
| `initialized` | User experience | | `initialized` | User experience |
|---------------|---------------------------------------------------------------| |---------------|---------------------------------------------------------------|
| `false` | Every route is redirected to `/bootstrap` until an admin is created. | | `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. | | `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 The `authGuard` enforces the redirect on the client side; the backend
independently rejects `/api/v1/setup/create-admin` with `404 NOT_FOUND` independently rejects `/api/v1/setup/create-admin` with
once any admin row exists, so the flow is safe even if the SPA is `409 SYSTEM_INITIALIZED` once any admin row exists, so the flow is safe
bypassed. 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) # How to access (tester steps)
+1 -1
View File
@@ -11,7 +11,7 @@ scoreboard, an event window with a public countdown, theming, and admin
controls. controls.
The docs below are organized by purpose so agents can pull just the slice The docs below are organized by purpose so agents can pull just the slice
they need. Last regenerated 2026-07-21T16:48:20Z. they need. Last regenerated 2026-07-21T17:18:08Z.
# Architecture # Architecture