4.3 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||
|---|---|---|---|---|---|---|---|---|
| api | REST API Overview | Base URL, versioning, auth, CSRF, and the standard error envelope. |
|
2026-07-23T10:12:24Z |
Base URL & versioning
All endpoints live under /api/v1. The Angular SPA uses the same
prefix; the SPA fallback only kicks in for non-API routes.
| Path prefix | Purpose |
|---|---|
/api/v1/* |
Versioned JSON REST endpoints. |
/api/docs |
Swagger UI (OpenAPI 3.1). |
/api/docs-json |
Raw OpenAPI 3.1 document. |
/uploads/* |
Static uploads directory (UPLOAD_DIR). |
/* |
SPA fallback (FRONTEND_DIST/index.html). |
Authentication
JwtAuthGuardis registered asAPP_GUARDand runs on every request unless the handler is marked with@Public()frombackend/src/common/decorators/public.decorator.ts.- Successful auth produces an access JWT in the response body and an
HttpOnlyrefresh cookie namedrt(seebackend/src/modules/auth/cookie.ts). Authorization: Bearer <accessToken>is attached by theauthInterceptoron the frontend.
CSRF
CsrfMiddleware(registered globally) mints acsrfcookie on every response and, for unsafe methods (POST,PUT,PATCH,DELETE), compares the cookie against thex-csrf-tokenheader usingcrypto.timingSafeEqual.- The frontend's
csrfInterceptorruns beforeauthInterceptorso the header is attached on every unsafe call. - Skip list (
SKIP_PATH_PREFIXESinbackend/src/common/middleware/csrf.middleware.ts):/api/v1/auth/register-first-admin/api/v1/setup/create-admin/api/v1/auth/loginis no longer skipped — the middleware still mints the cookie on the first response, and the SPA'sAuthService.login/AuthService.registerfirst callGET /api/v1/auth/csrfviaensureCsrf()to materialise the cookie before submitting.
Error envelope
Every error flows through GlobalExceptionFilter and is shaped as:
{
"code": "USERNAME_TAKEN",
"message": "Username already exists",
"details": null,
"path": "/api/v1/setup/create-admin",
"timestamp": "2026-07-21T14:43:00.000Z"
}
| Field | Type | Notes |
|---|---|---|
code |
string | One of ERROR_CODES (backend/src/common/errors/error-codes.ts). |
message |
string | Human-readable summary. |
details |
unknown | null | Optional structured details (e.g. zod field errors). |
path |
string | Original request path. |
timestamp |
ISO 8601 string | When the filter produced the response. |
Canonical error codes
USERNAME_TAKEN is the most recently added code (used by the setup
endpoint). Full list lives in backend/src/common/errors/error-codes.ts:
VALIDATION_FAILED, UNAUTHORIZED, FORBIDDEN, NOT_FOUND,
CONFLICT, LAST_ADMIN, SYSTEM_INITIALIZED, RATE_LIMITED,
CSRF_INVALID, WEAK_PASSWORD, USERNAME_TAKEN,
INVALID_CREDENTIALS, TOKEN_REVOKED, THEME_INVALID,
REGISTRATIONS_DISABLED, INVALID_OLD_PASSWORD, PASSWORD_POLICY,
PASSWORDS_DO_NOT_MATCH, CATEGORY_HAS_CHALLENGES, SYSTEM_PROTECTED,
CHALLENGE_NAME_TAKEN, CHALLENGE_INVALID_CATEGORY,
CHALLENGE_INVALID_POINTS, CHALLENGE_INVALID_PORT,
CHALLENGE_INVALID_IP, CHALLENGE_FILE_LIMIT,
IMPORT_VALIDATION_FAILED, IMPORT_PARTIAL_FAILURE,
EVENT_NOT_RUNNING, FLAG_INCORRECT, CHALLENGE_DISABLED, INTERNAL.
The player-facing codes (EVENT_NOT_RUNNING, FLAG_INCORRECT) are
emitted by the challenges submit endpoint; see
Challenges Endpoints for the full set of HTTP
statuses and the canonical messages produced by
messageForSolveError.