Files
HIPCTF2/docs/api/admin.md
T

135 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: api
title: Admin Endpoints
description: Admin-only endpoints for user management, general settings, categories, and supporting uploads.
tags: [api, admin, users, general, categories]
timestamp: 2026-07-22T12:00:00Z
---
# Endpoints
All handlers below are mounted behind `JwtAuthGuard` (global) +
`AdminGuard` + `@Roles('admin')`, so they require a valid admin JWT.
CSRF is enforced on every mutating call (standard cookie + header
pattern; nothing is added to the skip list).
## User management — `/api/v1/admin`
| Method | Path | Source |
|----------|-------------------------------|---------------------------------------------------|
| `GET` | `/api/v1/admin/users` | `backend/src/modules/admin/admin.controller.ts` |
| `POST` | `/api/v1/admin/users` | Same. |
| `PATCH` | `/api/v1/admin/users/:id` | Same. |
| `DELETE` | `/api/v1/admin/users/:id` | Same. |
`AdminService` enforces the **last-admin invariant** (`LAST_ADMIN` /
`409`) — the final admin row cannot be demoted or deleted. `POST`
creates a new user; `PATCH .../:id` changes role / status; `DELETE .../:id`
removes the row.
## General settings — `/api/v1/admin/general`
| Method | Path | Source |
|--------|---------------------------------------|-------------------------------------------------------------|
| `GET` | `/api/v1/admin/general/settings` | `backend/src/modules/admin/admin-general.controller.ts` |
| `PUT` | `/api/v1/admin/general/settings` | Same. |
| `GET` | `/api/v1/admin/general/themes` | Same. |
`GET /settings` returns the full `GeneralSettingsView`
(`pageTitle`, `logo`, `welcomeMarkdown`, `themeKey`, `eventStartUtc`,
`eventEndUtc`, `defaultChallengeIp`, `registrationsEnabled`).
`PUT /settings` is validated by `GeneralSettingsSchema` (zod):
* `pageTitle` 1120 chars
* `logo` up to 2048 chars (a public URL — uploaded separately)
* `welcomeMarkdown` up to 64 000 chars
* `themeKey` is one of `THEME_IDS`
* `eventStartUtc` / `eventEndUtc` parseable dates; `superRefine`
enforces `eventEndUtc > eventStartUtc` and reports the issue on
`eventEndUtc`
* `defaultChallengeIp` 1255 chars
* `registrationsEnabled` boolean
On success the handler persists every field via `SettingsService`,
emits an SSE `general` event (`{ topic: 'general', themeKey }`) via
`SseHubService`, and returns the updated view.
`GET /themes` returns the `ThemeView[]` for which a corresponding
JSON file exists under `THEMES_DIR`. Each item is `{ id, key, name }`.
See [Admin — General Settings](/guides/admin-general-settings.md).
## Categories — `/api/v1/admin/categories`
| Method | Path | Source |
|----------|-------------------------------------|-----------------------------------------------------------------|
| `GET` | `/api/v1/admin/categories` | `backend/src/modules/admin/admin-categories.controller.ts` |
| `POST` | `/api/v1/admin/categories` | Same. |
| `PUT` | `/api/v1/admin/categories/:id` | Same. |
| `DELETE` | `/api/v1/admin/categories/:id` | Same. |
Behavior:
* `GET` returns all categories sorted by `LOWER(abbreviation)` ascending.
* `POST` uppercases the abbreviation, rejects duplicates with `409 CONFLICT`,
and persists the row. Body validated by `CreateCategorySchema`
(`name` 1120 chars; `abbreviation` 26 chars; `description` up to
2000 chars; `iconPath` optional, up to 2048 chars).
* `PUT` validates the body with `UpdateCategorySchema` (every field
optional). System rows (`system_key IS NOT NULL`) cannot change their
abbreviation — server returns `409 SYSTEM_PROTECTED`. Duplicate
abbreviations on user rows return `409 CONFLICT`. Updates the
`updated_at` timestamp on success.
* `DELETE` is blocked for system rows (`403 SYSTEM_PROTECTED`) and for
rows with at least one attached challenge (`409 CATEGORY_HAS_CHALLENGES`
with `{ count }` in `details`). Returns `404 NOT_FOUND` for unknown ids.
Response shape (`CategoryView`):
```json
{
"id": "uuid",
"name": "Cryptography",
"abbreviation": "CRY",
"description": "Cryptographic challenges",
"iconPath": "/uploads/icons/CRY.png",
"isSystem": true,
"systemKey": "CRY",
"createdAt": "2026-07-21T18:00:00.000Z",
"updatedAt": "2026-07-21T18:00:00.000Z"
}
```
See [Admin — Categories](/guides/admin-categories.md).
## Supporting uploads
Logo and category-icon uploads live on the uploads module and are
documented in [Uploads Endpoints](/api/uploads.md):
| Method | Path | Purpose |
|--------|-----------------------------------|----------------------------------|
| `POST` | `/api/v1/uploads/logo` | Logo used by General settings. |
| `POST` | `/api/v1/uploads/category-icon` | Icon used by Categories. |
Both endpoints are admin-only and use `FileInterceptor('file')` for a
single multipart part named `file`.
# Guard chain
1. `JwtAuthGuard` (global) validates the access token unless the handler
is `@Public()`. Admin handlers are not.
2. `AdminGuard` (`backend/src/common/guards/admin.guard.ts`) requires
`req.user.role === 'admin'`.
3. `RolesGuard` enforces `@Roles('admin')` metadata on the controller.
# See also
- [Backend Module Map](/architecture/backend-modules.md)
- [Admin Shell](/guides/admin-shell.md)
- [Admin — General Settings](/guides/admin-general-settings.md)
- [Admin — Categories](/guides/admin-categories.md)
- [Uploads Endpoints](/api/uploads.md)
- [REST API Overview](/api/rest-overview.md)