6.1 KiB
6.1 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| api | Auth Endpoints | Login, refresh, logout, public self-registration, CSRF, first-admin registration, and registration throttling. |
|
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 | 3–32 chars, regex ^[a-zA-Z0-9_.-]+$ |
password |
string | 1–256 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)
{
"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 singleDataSource.transaction:RegistrationRateLimitService.tryConsume(ip)permits at most 10 attempts per IP in a rolling 60-second window.SettingsService.get(SETTINGS_KEYS.REGISTRATIONS_ENABLED, 'false')check.- Username uniqueness lookup →
USERNAME_TAKENon collision. - Argon2id hash + insert
UserEntity({ role: 'player', status: 'enabled' }). 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:
- Call
ensureCsrf()→GET /api/v1/auth/csrfto materialise thecsrfcookie if it isn't set yet. POSTwith{ withCredentials: true, observe: 'response' }.- Return success or the standard error envelope.
The LandingComponent consumes both methods and maps failures to user-facing
copy (see Landing Page).
Examples
Tester flow: registration rate limit
- Enable registrations in the admin settings.
- Open the public registration form from the landing page.
- Submit attempts from the same client IP.
- The first 10 attempts are processed normally; the 11th within 60 seconds returns HTTP
429with codeRATE_LIMITED. - After the rolling window expires, a new attempt is accepted if other requirements pass.