Files
HIPCTF2/docs/api/auth.md
T
2026-07-21 18:34:42 +00:00

91 lines
5.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: Auth Endpoints
description: Login, refresh, logout, public self-registration, CSRF, and first-admin registration.
tags: [api, auth, login, register, refresh, csrf]
timestamp: 2026-07-21T18:28:00Z
---
# Endpoints
| Method | Path | Auth | CSRF | Rate limited | Source |
|--------|-----------------------------------|--------|-----------------------------------------------|--------------|--------|
| `POST` | `/api/v1/auth/register` | `@Public()` | Enforced — caller must first call `GET /api/v1/auth/csrf` (the SPA's `AuthService.ensureCsrf()` does this automatically). | Yes — `RegistrationRateLimitService` per IP | `backend/src/modules/auth/auth.controller.ts` |
| `POST` | `/api/v1/auth/login` | `@Public()` | Enforced — same pattern as `register`. CSRF skip was removed in this change. | Yes — `LoginBackoffService` per IP + username | `backend/src/modules/auth/auth.controller.ts` |
| `POST` | `/api/v1/auth/refresh` | Public route, requires a valid refresh cookie. | Skipped (the request is a `POST` carrying an HttpOnly cookie, not a state-changing user action). | No | `backend/src/modules/auth/auth.controller.ts` |
| `POST` | `/api/v1/auth/logout` | Authenticated (JWT). | Enforced. | No | `backend/src/modules/auth/auth.controller.ts` |
| `GET` | `/api/v1/auth/csrf` | `@Public()` | n/a (`GET`). | No | `backend/src/modules/auth/auth.controller.ts` |
| `POST` | `/api/v1/auth/register-first-admin` | `@Public()` | Skipped (path is in `CsrfMiddleware.SKIP_PATH_PREFIXES`). | Yes | `backend/src/modules/users/users.controller.ts` |
# Public player self-registration — `POST /api/v1/auth/register`
New endpoint, gated by the `registrationsEnabled` setting
(`backend/src/config/env.schema.ts``SETTINGS_KEYS.REGISTRATIONS_ENABLED`,
default `"false"`).
| Field | Type | Constraints |
|-------------------|--------|------------------------------------------------------------------------------|
| `username` | string | 332 chars, regex `^[a-zA-Z0-9_.-]+$` |
| `password` | string | 1256 chars; also passes `validatePassword` (Argon2 policy). |
| `passwordConfirm` | string | Must equal `password` (zod refine → `VALIDATION_FAILED` on `passwordConfirm`). |
Validated by `backend/src/modules/auth/dto/auth.dto.ts``RegisterDtoSchema`.
## Successful response (201)
```json
{
"accessToken": "<jwt>",
"expiresIn": 900,
"user": { "id": "<uuid>", "username": "player1", "role": "player" }
}
```
The refresh token is set as an `HttpOnly` cookie via `setRefreshCookie(res, session.refreshToken, config)`. The new `user` row has `role='player'` and `status='enabled'`.
## Error envelope
| HTTP | `code` | When |
|------|-------------------------|---------------------------------------------------------------------------------|
| 400 | `VALIDATION_FAILED` | zod body validation failed. |
| 400 | `WEAK_PASSWORD` | Argon2 policy rejects the password. |
| 403 | `REGISTRATIONS_DISABLED`| `setting.registrationsEnabled !== 'true'`. |
| 409 | `USERNAME_TAKEN` | A user with that username already exists. |
| 429 | `RATE_LIMITED` | `RegistrationRateLimitService` blocked the IP. |
## Wiring
* `AuthService.registerPlayer(dto, ip)` runs inside a single
`DataSource.transaction`:
1. `RegistrationRateLimitService.isAllowed(ip)` check.
2. `SettingsService.get(SETTINGS_KEYS.REGISTRATIONS_ENABLED, 'false')` check.
3. Username uniqueness lookup → `USERNAME_TAKEN` on collision.
4. Argon2id hash + insert `UserEntity({ role: 'player', status: 'enabled' })`.
5. `RegistrationRateLimitService.record(ip)`.
6. `mintSession(manager, user)` → access JWT + refresh row.
# Frontend consumer
`frontend/src/app/core/services/auth.service.ts` exposes `login()` and
`register()`. Both:
1. Call `ensureCsrf()``GET /api/v1/auth/csrf` to materialise the `csrf`
cookie if it isn't set yet (failures are swallowed because the
`CsrfMiddleware` will mint the cookie on the next response anyway).
2. `POST` with `{ withCredentials: true, observe: 'response' }`.
3. Return a discriminated union:
* `{ ok: true, accessToken, expiresIn, user }` on success.
* `{ ok: false, status, code, message }` on failure, built by the
private `mapAuthError()` helper from the standard
[error envelope](/api/rest-overview.md).
The `LandingComponent` (`frontend/src/app/features/landing/landing.component.ts`)
consumes both methods and maps the `code` to user-facing copy via
`buildLoginFailureMessage` (see [Landing Page](/guides/landing-page.md)).
# See also
- [REST API Overview](/api/rest-overview.md)
- [Setup Endpoint](/api/setup.md)
- [Landing Page Guide](/guides/landing-page.md)