Files
HIPCTF2/docs/api/system.md
T
2026-07-22 13:41:05 +00:00

145 lines
5.8 KiB
Markdown

---
type: api
title: System Endpoints
description: Bootstrap payload, public event status, public/authenticated SSE streams, and public event-window timestamps.
tags: [api, system, bootstrap, event, sse, settings]
timestamp: 2026-07-22T13:40:00Z
---
# Endpoints
| Method | Path | Auth | Source |
|--------|-----------------------------------|--------|-----------------------------------------------------|
| `GET` | `/api/v1/bootstrap` | Public | `backend/src/modules/system/system.controller.ts` |
| `GET` | `/api/v1/event/status` | Public | Same. |
| `GET` | `/api/v1/event/stream` | Public (SSE) | Same. |
| `GET` | `/api/v1/settings/event` | Public | Same. |
| `GET` | `/api/v1/events/status` | Authenticated (SSE) | Same. |
| `GET` | `/api/v1/scoreboard/stream` | Public (SSE) | Same. |
> Path note: the **authenticated** status stream is mounted at the plural
> `/api/v1/events/status` so it can be distinguished from the legacy
> public `/api/v1/event/status` snapshot endpoint.
# `GET /api/v1/bootstrap`
Built by `SystemService.bootstrap()`. Public payload consumed by the
SPA's `BootstrapService`:
```json
{
"initialized": true,
"pageTitle": "HIPCTF",
"logo": "",
"welcomeMarkdown": "# Welcome\n\n...",
"theme": { "id": "classic", "tokens": { "...": "..." } },
"defaultChallengeIp": "127.0.0.1",
"registrationsEnabled": false,
"passwordPolicy": {
"minLength": 12,
"requireMixed": true,
"description": "At least 12 characters with upper, lower, digit and symbol"
}
}
```
* `initialized` is `true` iff at least one admin user exists.
* `theme` is the resolved `Theme` (one of the 10 canonical themes; see
[Theming](/guides/theming.md)).
* `registrationsEnabled` is read from the `setting` table
(`SETTINGS_KEYS.REGISTRATIONS_ENABLED`) and gates the new public
register endpoint (see [Auth Endpoints](/api/auth.md)).
* `passwordPolicy` is derived from `PASSWORD_MIN_LENGTH` /
`PASSWORD_REQUIRE_MIXED` env vars (see
[Change Password Guide](/guides/change-password.md)).
* The frontend `BootstrapService` caches the initial bootstrap request through
`load()`/`ready()`. Its `refresh()` method fetches the payload again, updates
signals, and reapplies the theme; `/admin/general` calls it after a successful
settings update.
# `GET /api/v1/event/status` (legacy public snapshot)
Returns the current event window computed by `EventStatusService.getStatus()`:
```json
{ "state": "Running", "countdownMs": 12345, "startUtc": "...", "endUtc": "...", "serverNowUtc": "..." }
```
`status` is `Stopped` while `now < eventStartUtc` (countdown), `Running`
between start and end, and `Stopped` once `now >= eventEndUtc`. This
endpoint is preserved for backward compatibility — new consumers should
use `getState()` (see below).
# `GET /api/v1/settings/event` (public timestamps)
Returns the raw UTC event-window timestamps without computing state.
Useful for the SPA when only the window endpoints are needed:
```json
{ "eventStartUtc": "2026-07-21T12:00:00.000Z", "eventEndUtc": "2026-07-28T12:00:00.000Z" }
```
A `null` value for either field means that the underlying `setting` row
is missing or empty; in that case `EventStatusService.getState()` returns
the `unconfigured` state.
# `GET /api/v1/events/status` (authenticated SSE)
Authenticated Server-Sent Events stream that pushes the full
`EventStatePayload` whenever it changes. Protected by the global
`JwtAuthGuard` — unauthenticated clients receive `401` before any frame
is sent.
Each frame:
```json
{
"state": "countdown" | "running" | "stopped" | "unconfigured",
"serverNowUtc": "2026-07-21T12:00:00.000Z",
"eventStartUtc": "2026-07-21T12:00:00.000Z" | null,
"eventEndUtc": "2026-07-28T12:00:00.000Z" | null,
"secondsToStart": 3600,
"secondsToEnd": 604800
}
```
* `state` transitions:
* `unconfigured``eventStartUtc`/`eventEndUtc` settings are missing or unparseable.
* `countdown``now < eventStartUtc`.
* `running``start <= now <= end`.
* `stopped``now > eventEndUtc`.
* The stream emits:
1. An **initial** frame on connect (`defer(getState())`).
2. A **60-second tick** frame so the SPA can update even if no admin
cron publishes through the hub.
3. Any frame pushed via `SseHubService.publish()`.
* Consecutive identical payloads are suppressed via `distinctUntilChanged(JSON.stringify)`.
See `backend/src/common/services/event-status.service.ts` for the
authoritative state machine; see
[Event Window Guide](/guides/event-window.md) for the full diagram and
the SPA wiring. The authenticated browser consumer uses the fetch-based
transport in `frontend/src/app/core/services/authenticated-event-source.service.ts`
so it can send the JWT `Authorization` header; native `EventSource` cannot set
that header.
# `GET /api/v1/event/stream` (public SSE, legacy)
Public, flattened event stream that emits the legacy payload shape
(`status`, `countdownMs`, `startUtc`, `endUtc`, `serverNowUtc`) on a
1-second tick and on hub pushes. Preserved for backward compatibility
with the original landing page status badge.
# `GET /api/v1/scoreboard/stream`
Public SSE stream that pushes a flattened solve payload whenever a new
solve is published (`backend/src/common/services/sse-hub.service.ts`).
# See also
- [Auth and Settings Tables](/database/auth-settings.md)
- [Theming](/guides/theming.md)
- [Event Window Guide](/guides/event-window.md)
- [Scoreboard Stream Guide](/guides/scoreboard-stream.md)
- [Authenticated Shell Guide](/guides/authenticated-shell.md)