Files
HIPCTF2/docs/api/admin.md
T
2026-07-22 12:31:57 +00:00

6.1 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
api Admin Endpoints Admin-only endpoints for user management, general settings, categories, and supporting uploads.
api
admin
users
general
categories
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 is trimmed server-side and must be 1120 chars after trimming (whitespace-only values are rejected).
  • 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.

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):

{
  "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.

Supporting uploads

Logo and category-icon uploads live on the uploads module and are documented in Uploads Endpoints:

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