Files
HIPCTF2/docs/api/rest-overview.md
T

101 lines
4.3 KiB
Markdown

---
type: api
title: REST API Overview
description: Base URL, versioning, auth, CSRF, and the standard error envelope.
tags: [api, rest, overview, csrf]
timestamp: 2026-07-23T00:10:00Z
---
# 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:
```json
{
"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](/api/challenges.md) for the full set of HTTP
statuses and the canonical messages produced by
`messageForSolveError`.
# See also
- [Auth Endpoints](/api/auth.md)
- [Admin Endpoints](/api/admin.md)
- [Setup Endpoint](/api/setup.md)
- [System Endpoints](/api/system.md)
- [Uploads Endpoints](/api/uploads.md)
- [Challenges Endpoints](/api/challenges.md)