Files

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.
api
rest
overview
csrf
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

  • JwtAuthGuard is registered as APP_GUARD and runs on every request unless the handler is marked with @Public() from backend/src/common/decorators/public.decorator.ts.
  • Successful auth produces an access JWT in the response body and an HttpOnly refresh cookie named rt (see backend/src/modules/auth/cookie.ts).
  • Authorization: Bearer <accessToken> is attached by the authInterceptor on the frontend.

CSRF

  • CsrfMiddleware (registered globally) mints a csrf cookie on every response and, for unsafe methods (POST, PUT, PATCH, DELETE), compares the cookie against the x-csrf-token header using crypto.timingSafeEqual.
  • The frontend's csrfInterceptor runs before authInterceptor so the header is attached on every unsafe call.
  • Skip list (SKIP_PATH_PREFIXES in backend/src/common/middleware/csrf.middleware.ts):
    • /api/v1/auth/register-first-admin
    • /api/v1/setup/create-admin
    • /api/v1/auth/login is no longer skipped — the middleware still mints the cookie on the first response, and the SPA's AuthService.login/AuthService.register first call GET /api/v1/auth/csrf via ensureCsrf() 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.

See also