AI Implementation feature(871): Admin Area System: Database Backup/Restore and Danger Zone (#61)

This commit was merged in pull request #61.
This commit is contained in:
2026-07-23 12:10:41 +00:00
parent 6bac67fad7
commit 470ddd30c3
42 changed files with 3996 additions and 55 deletions
+55
View File
@@ -0,0 +1,55 @@
---
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)