Files
HIPCTF2/docs/api/auth.md
T
2026-07-21 21:19:36 +00:00

104 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: Auth Endpoints
description: Login, refresh, logout, public self-registration, CSRF, first-admin registration, and registration throttling.
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` | The per-IP registration limiter has reached 10 attempts in the rolling 60-second window. |
## Wiring
* `AuthService.registerPlayer(dto, ip)` runs inside a single `DataSource.transaction`:
1. `RegistrationRateLimitService.tryConsume(ip)` permits at most 10 attempts per IP in a rolling 60-second window.
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. `mintSession(manager, user)` → access JWT + refresh row.
* `AuthService.registerFirstAdmin(username, password, ip)` uses the same limiter before checking whether an administrator already exists.
* The limiter is an in-memory Nest provider in `backend/src/common/services/registration-rate-limit.service.ts`; counters are process-local and reset when the API process restarts.
# Key Files
| File | Responsibility |
|------|----------------|
| `backend/src/modules/auth/auth.service.ts` | Validates registration policy, applies the limiter, creates users, and mints sessions. |
| `backend/src/common/services/registration-rate-limit.service.ts` | Tracks timestamps and enforces the 10-per-minute per-IP limit. |
| `backend/src/modules/auth/auth.controller.ts` | Exposes public registration and authentication routes. |
| `backend/src/modules/auth/dto/auth.dto.ts` | Validates registration request fields. |
# 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.
2. `POST` with `{ withCredentials: true, observe: 'response' }`.
3. Return success or the standard [error envelope](/api/rest-overview.md).
The `LandingComponent` consumes both methods and maps failures to user-facing
copy (see [Landing Page](/guides/landing-page.md)).
# Examples
## Tester flow: registration rate limit
1. Enable registrations in the admin settings.
2. Open the public registration form from the landing page.
3. Submit attempts from the same client IP.
4. The first 10 attempts are processed normally; the 11th within 60 seconds returns HTTP `429` with code `RATE_LIMITED`.
5. After the rolling window expires, a new attempt is accepted if other requirements pass.
# See also
- [REST API Overview](/api/rest-overview.md)
- [Setup Endpoint](/api/setup.md)
- [Landing Page Guide](/guides/landing-page.md)