Admin interface

This commit is contained in:
2026-10-22 11:26:42 -04:00
parent 2a2d976013
commit 3b81cae024
6 changed files with 583 additions and 9 deletions
+37 -1
View File
@@ -27,10 +27,46 @@ Every press records the zone, the points, and the exact remaining time in millis
Logged-in players can also join the mailing list.
The Chat section is a simple public chatroom. Logged-in players can post plain-text messages (up to 200 characters, one message every two seconds). The latest 100 messages are displayed oldest first and refresh every five seconds while Chat is open. Messages persist in SQLite across restarts, including after the game ends. There are no editing, deletion, or moderation controls yet.
The Chat section is a simple public chatroom. Logged-in players can post plain-text messages (up to 200 characters, one message every two seconds). The latest 100 messages are displayed oldest first and refresh every five seconds while Chat is open. Messages persist in SQLite across restarts, including after the game ends. Administrators can remove messages through the Admin dashboard; players cannot edit messages.
Every successful button press also posts an automatic chat announcement with the player's name, exact time remaining, and points earned. These announcements are saved with the press and do not count toward the player's chat cooldown.
## Administrator dashboard
Administrator controls are available in the **Admin** section after signing in with an admin account. The server checks administrator permission on every dashboard request and action; hiding the menu is not the authorization boundary. Public registration cannot grant admin access, and legacy JSON imports do not import privileged flags.
### Assigning the first administrator
1. Upgrade/start the server once so it adds the new database fields, then create a regular account if needed.
2. **Stop the server and back up the SQLite database.** The game uses an in-memory snapshot; editing flags while it runs is unsupported and can be overwritten.
3. Open the configured SQLite database with a trusted local SQLite editor. In Docker, the database lives in the persistent `/data` volume; stop the service before editing/copying it. Do not modify the legacy JSON backup.
4. Find the intended account's exact ID, then promote only that ID:
```sql
SELECT id, name, is_admin, is_banned, is_deleted FROM players;
UPDATE players SET is_admin = 1 WHERE id = 'REPLACE_WITH_EXACT_PLAYER_ID'
AND is_banned = 0 AND is_deleted = 0;
```
5. Verify exactly one intended row changed, save/close the database, and restart the server. Sign in and open **Admin**. No public bootstrap endpoint or default admin password exists.
The database fields `players.is_admin`, `players.is_banned`, and `players.is_deleted` are boolean integers constrained to 0/1 and default to 0. Existing SQLite accounts receive non-admin defaults; their flags persist across normal saves, restarts, and seasons. For emergency recovery use the same stopped-server procedure to restore access to a non-deleted account.
### Controls and safeguards
- **Overview:** current season/game status, player count, season presses, session counts, and stored chat-row count. Password hashes, session tokens, and mailing-list contact data are never returned by the dashboard.
- **Admin access:** grant/revoke admin status for another account. Any role change revokes that account's current sessions, requiring login again.
- **Ban/unban:** bans immediately revoke all sessions and presence and block login and authenticated actions. Existing scores and messages remain. Unbanning does not revive old sessions. Banned players cannot be promoted until unbanned. This is an account ban, not an IP ban; it does not prevent creating another account.
- **Delete account:** irreversible logical deletion. Removes the display name, password hash, sessions, subscription flag, and privileges; anonymizes authored chat and press announcements and matching archived display names. The account disappears from admin account lists and its original name can be registered again. An inert `Deleted player` tombstone retains the internal ID/scored presses so historical scores, archives, and foreign keys remain consistent. Old archived standings have no player IDs, so exact duplicate names are conservatively anonymized together. Anonymized scores may remain on leaderboards.
- **Remove chat:** removes a message from the visible feed and scrubs its text while retaining row identity/timestamps for cooldowns and stable ordering. Also supports automatic press/milestone messages. Suppression survives restarts and seasonal milestone archival; removing a milestone announcement does not undo the unlock. Only messages in the latest 100 visible feed are currently offered for moderation.
- **Safety:** confirmation prompts for each action; cannot remove your own access or the last active administrator. Admin actions and audit writes commit together; failed actions do not partially update game memory. The last 50 successful actions appear in the dashboard. The SQLite `admin_audit` table retains action, actor/target IDs, and timestamp, not message bodies, passwords, or tokens.
Account deletion is **not** a comprehensive personal-data erasure tool: the separate mailing-list CSV, backups, exported data, and third-party copies are not edited automatically. Handle those separately when necessary. Audit identifiers and anonymized game history are deliberately retained. Dashboard controls refresh manually to avoid moving focused actions unexpectedly; access is still rechecked by the server each time. No game reset/end controls are included.
Admin access uses the existing bearer-token authentication, with cross-site fetch rejection on admin mutations and no permissive CORS. Use HTTPS behind a reverse proxy before exposing the service; administrators should log out on shared devices. The existing login failure limits apply, but there is no separate MFA or password reconfirmation flow yet.
Admin API: authenticated admin-only `GET /api/admin`; `POST /api/admin/action` accepts `{action, target_id, value?}` for `set_admin`, `set_banned`, and `delete_player`, or `{action: "delete_message", target_id, at}`. Boolean flags must be actual JSON booleans; IDs/timestamps must match the selected record. Anonymous requests receive 401, non-admin accounts 403, invalid inputs 400, missing targets 404, and protected actions 409. All JSON responses use `Cache-Control: no-store`.
## Seasons and champions
Chat supports emoji autocomplete: type `:` followed by a name (for example `:sm`, `:heart`, or `:fire:`), then choose a suggestion with the arrow keys and Enter/Tab, or click/tap it. Escape dismisses suggestions. Selection inserts the actual Unicode emoji at the cursor without sending the message; press Enter again or Send to post. Unknown shortcodes remain unchanged. Suggestions are a built-in curated list, with no external service, and respect the message length limit.