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
+180
View File
@@ -0,0 +1,180 @@
---
type: guide
title: Admin — System (Backup, Restore, Danger Zone)
description: How an admin navigates to /admin/system, creates a backup, stages and commits a restore, resets all scores, and wipes challenges — including the re-authentication confirmation, progress, and side effects.
tags: [guide, admin, system, backup, restore, danger-zone, confirmation]
timestamp: 2026-07-23T12:04:50Z
---
# Navigation
1. Sign in as an admin and open the admin side-nav (see the
[Admin Shell guide](/guides/admin-shell.md)).
2. Click **System** in the side-nav. The route resolves to
`/admin/system` and lazily loads the
`AdminSystemComponent` standalone component.
3. The page renders two panels side by side: **Database** (left) and
**Danger Zone** (right). On narrow screens they stack vertically.
# Database panel
## Create a backup
1. Click **Create backup** (data-testid `admin-system-create-backup`).
A spinner appears inside the button while the request is in flight.
2. The browser receives a JSON file via `Content-Disposition:
attachment; filename="hipctf-backup-YYYY-MM-DD.json"` and the
browser download UI saves it to disk.
3. On success the panel shows a green **Backup ready.** status line
(data-testid `admin-system-backup-success`).
4. On failure the panel shows a red error message under the buttons
(data-testid `admin-system-backup-error`). The friendly message
comes from `system.pure.ts` and resolves `SYSTEM_BACKUP_FAILED`,
`SYSTEM_FILE_READ_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, etc.
## Restore a backup
1. Click **Restore backup** (data-testid `admin-system-restore-backup`).
A hidden file picker opens restricted to `application/json`/`.json`.
2. Pick a backup JSON file. The progress line shows
**Reading selected backup…**, then **Validating backup
server-side…** (data-testid `admin-system-restore-progress`).
3. The server validates the JSON, decodes uploads, and verifies
sizes/checksums. On success a summary card appears
(data-testid `admin-system-restore-summary`) showing:
* Tables detected
* Uploaded files
* Total bytes
* Stage expiry (ISO timestamp)
4. From the summary card, the admin has two options:
* **Discard staged backup** (data-testid
`admin-system-discard-restore`) — drops the staged state and
resumes from a clean panel.
* **Continue to destructive restore…** (data-testid
`admin-system-confirm-restore`) — opens the confirmation modal.
### Confirmation modal (restore-backup)
* The modal title is **Restore database?**.
* The detail paragraph explains that the entire database and uploads
will be replaced (the modal's `preserved` line says **Nothing is
preserved**).
* The stage banner explicitly warns: *You will be logged out after
restore completes.*
* The admin must enter their current password and type
`RESTORE BACKUP` exactly into the confirmation phrase field.
* Click **Confirm**. The modal swaps the input fields for a
*Working…* spinner.
* The frontend calls `POST /confirmations` with `{ operation:
'restore-backup', password, stagingId }` to receive a single-use
token, then immediately calls `POST /restore/commit` with that
token and the staging id.
* On success the SPA calls `AuthService.forceServerInvalidation()`,
resets the `UserStore`, and navigates to `/login`. The
`ChallengesStore` and `ScoreboardStore` invalidate themselves via
the `SystemDataChangeService` `'restore-completed'` event.
* On failure the modal stays open and shows the resolved error
(e.g. `SYSTEM_TOKEN_MISMATCH`, `SYSTEM_RESTORE_ROLLED_BACK`). The
database panel also reflects the error inline.
# Danger Zone panel
The danger zone panel is outlined by a dashed red border and contains
two red buttons:
| Button | Operation |
|--------------------------------------------|---------------------|
| `admin-system-reset-scores` | `reset-scores` |
| `admin-system-wipe-challenges` | `wipe-challenges` |
Each button opens the same confirmation modal with operation-specific
copy:
* **Reset all scores?** — detail lists what is removed
(every solve and award), preserved (users, sessions, roles,
challenges, categories, etc.). Confirmation phrase: `RESET SCORES`.
* **Wipe all challenges?** — detail explains that every challenge, its
attached files, every solve, and every award are removed, and that
every uploaded challenge file on disk is deleted. Preserved:
users, sessions, roles, categories, settings, blog posts, and
root-level uploads (e.g. the site logo). Confirmation phrase:
`WIPE CHALLENGES`.
After the admin enters their password and types the confirmation
phrase, the SPA re-authenticates to issue a token and then commits the
operation. The panel shows a green summary
(`N solve record(s) removed.` or `N challenge(s), M file(s) wiped.`)
and a toast notification. The corresponding stores
(`ChallengesStore`, `ScoreboardStore`) automatically reset themselves
via the `SystemDataChangeService` `scores-reset` and `challenges-wiped`
events so every open tab sees fresh data.
# Cross-tab invalidation
After any of the destructive operations, the
`SystemDataChangeService` notifies all subscribers in the same tab via
an Angular signal. The injected effects in `ChallengesStore` and
`ScoreboardStore` reset their cached board/matrix/event-log/graph
data. For a restore, the SPA additionally forces a cross-tab
invalidation by calling `AuthService.forceServerInvalidation()`,
which clears the access token and notifies peer tabs via
`BroadcastChannel` + the `storage`-event fallback so they redirect to
`/login` as well.
# Error mapping
The pure helper `friendlySystemErrorMessage(op, err)` in
`system.pure.ts` converts the backend error codes into user-facing
messages. Notable mappings:
| Backend code | UI message |
|---------------------------------------|----------------------------------------------------------------------------|
| `SYSTEM_REAUTH_REQUIRED` | "Your session is no longer valid. Please log in again." |
| `SYSTEM_INVALID_CREDENTIALS` | "The current password is incorrect." |
| `SYSTEM_TOKEN_INVALID` / `_EXPIRED` | "Confirmation token is invalid / expired. Re-authenticate and try again." |
| `SYSTEM_TOKEN_REUSED` | "Confirmation token was already used. Re-authenticate to get a fresh token." |
| `SYSTEM_TOKEN_MISMATCH` | "Confirmation token does not match this operation." |
| `SYSTEM_RESTORE_VALIDATION_FAILED` | "Backup validation failed. No data was changed." |
| `SYSTEM_RESTORE_PAYLOAD_TOO_LARGE` | "The backup is larger than the configured upload limit." |
| `SYSTEM_RESTORE_STAGE_EXPIRED` | "The backup staging window expired. Re-upload and validate again." |
| `SYSTEM_RESTORE_ROLLED_BACK` | "Restore failed and was rolled back. Your data is unchanged." |
| `SYSTEM_DANGER_ROLLED_BACK` | "Operation failed and was rolled back. Nothing was changed." |
| `SYSTEM_BACKUP_FAILED` | "Backup could not be generated. No data was changed." |
| `SYSTEM_DANGER_FAILED` | "Operation could not complete. No data was changed." |
| `SYSTEM_OPERATION_IN_PROGRESS` | "Another destructive operation is currently running." |
# Examples
## Manual restore happy path
1. Download the backup from a known-good environment.
2. Sign in as the admin on the target environment.
3. Navigate to `/admin/system`, click **Restore backup**, select the
downloaded file.
4. Verify the summary matches the expected table counts and file
counts.
5. Click **Continue to destructive restore…**, enter the admin
password, type `RESTORE BACKUP`, click **Confirm**.
6. The modal closes, the page shows a brief *Restore complete.
Logging out…* toast, and the SPA navigates to `/login`.
7. Sign in again with the same admin credentials. The data on the
target environment now matches the source backup.
## Manual reset happy path
1. Navigate to `/admin/system`.
2. Click **Reset all scores**, enter the password, type
`RESET SCORES`, click **Confirm**.
3. The danger zone panel shows the number of solve records removed
and a toast notifies the operation completed. Refresh the
Scoreboard page — every player is back at zero.
## Manual wipe happy path
1. Navigate to `/admin/system`.
2. Click **Wipe challenges**, enter the password, type
`WIPE CHALLENGES`, click **Confirm**.
3. The danger zone panel shows counts of challenges, files, and
solves removed. The Challenges page becomes empty and the
`/admin/challenges` list is empty. The `challenges/` folder on
disk is also gone.