--- type: database title: Admin Operation Tokens description: Single-use confirmation tokens backing the destructive admin System operations (restore-backup, reset-scores, wipe-challenges). resource: backend/src/database/entities/admin-operation-token.entity.ts tags: [database, admin, system, backup, restore, danger-zone, tokens] timestamp: 2026-07-23T12:04:50Z --- # Schema | Column | Type | Description | |---------------|-----------|----------------------------------------------------------------------------------------------------------| | `id` | TEXT PK | UUIDv4 generated client-side; the primary key. | | `user_id` | TEXT FK | `user.id` of the administrator who issued the token. FK `ON DELETE CASCADE` to `user`. | | `operation` | TEXT | One of `restore-backup`, `reset-scores`, `wipe-challenges` (mirrors `ADMIN_OPERATION_KINDS`). | | `token_hash` | TEXT | Hex-encoded SHA-256 of the raw token. The raw token is returned once and never logged or stored. Unique. | | `issued_at` | TEXT | ISO-8601 timestamp at issuance. | | `expires_at` | TEXT | ISO-8601 timestamp after which the token is invalid (default TTL: `SYSTEM_OP_CONFIRM_TOKEN_TTL_MS`, 5 min). | | `consumed_at` | TEXT null | ISO-8601 timestamp when the token was consumed; null while still valid. | # Indexes | Index | Purpose | |--------------------------------|--------------------------------------------------------| | `uq_admin_op_token_hash` | `UNIQUE` on `token_hash` — guarantees one-shot use. | | `idx_admin_op_token_user_op` | `(user_id, operation)` — fast per-admin/per-op lookup. | | `idx_admin_op_token_expiry` | `expires_at` — enables the periodic expiry sweep. | # Lifecycle 1. Admin re-authenticates with their current password via `POST /api/v1/admin/system/confirmations`. The backend verifies the password with Argon2id, then `ConfirmationTokenService.issue()` generates a 32-byte base64url token, persists only its SHA-256 hash, and returns the raw token plus its `expiresAt`. 2. The client passes the raw token (and the `stagingId` for restores) to the matching destructive endpoint (`/restore/commit`, `/scores/reset`, `/challenges/wipe`). 3. `ConfirmationTokenService.consume()` runs the consumption inside a transaction: it looks the token up by hash, validates `user_id` + `operation` (+ `stagingId` for restores), checks `expires_at > now`, and finally performs a conditional update `WHERE consumedAt IS NULL` so only the first concurrent caller succeeds. A second attempt returns `SYSTEM_TOKEN_REUSED`. 4. `purgeExpired()` deletes expired rows and consumed rows older than 24h. # Related * Migration: [Admin Operation Tokens Migration](/database/admin-operation-tokens-migration.md) * Service: `backend/src/modules/admin/system/confirmation-token.service.ts` * Error codes: `SYSTEM_TOKEN_INVALID`, `SYSTEM_TOKEN_EXPIRED`, `SYSTEM_TOKEN_REUSED`, `SYSTEM_TOKEN_MISMATCH` * [Admin System API](/api/admin-system.md)