Skip to content

Admin & Audit

Status: ✅ Shipped — the full RBAC scope chain is live: AdminScopeGrant model + 4 migrations (e2a7f3c8b5d1 phase A1 tables, a7c4e91b2d08 phase E audit-review seed, 9c46d8ea0c11 system-health-view seed, f3a8c2d91e47 operational-scopes expansion), per-endpoint scope checks wired across 32 route files, and User.is_admin is now Phase-C3 grant-derived — a correlated EXISTS expression (user.py), not the plain settable column this page previously described. The bootstrap superadmin seed and the AdminActionLog audit trail are also live. (impl 2026-08-04, status-flip — this was the stalest claim found in a full-repo sweep tonight)

Admin authentication is a side table on User (AdminCredentials). Authorization is scope-based RBAC per ADR-0058 A-F2, greenlit as the RBAC foundation chain by ADR-0093 item 19: each admin capability is one of the canonical scopes, granted per-admin via the AdminScopeGrant join table, with every admin action recorded in AdminActionLog. User.is_admin is Phase-C3 grant-derived — a correlated EXISTS against active scope grants (user.py) — superseding the historical flat Boolean gate from ADR-0027. Admin routes check the required scope (403 names the missing scope); grant/revoke APIs and ScopesManager at /scopes are live (see ../OPERATIONS/admin-ui.md; the old PermissionsDashboard scaffolding was deleted). Beyond the scope gate, granular moderation state already lives inline on the resource being administered (e.g., MarketTransaction.flagged_suspicious / reviewed_by, CombatLog.admin_resolved / admin_reviewed, Message.flagged / moderated_by, PriceAlert.acknowledged_by).


User (admin flag)

Source: services/gameserver/src/models/user.py (is_admin hybrid property)

User.is_admin is grant-derived: Python and SQL reads use EXISTS (active AdminScopeGrant WHERE revoked_at IS NULL). The physical users.is_admin column remains as a denormalized cache that grant/revoke still sync — it is not the live authorization gate and was not dropped in Phase C3. See ./player.md for the full User schema. An admin user is one with at least one active scope grant (and typically a populated AdminCredentials row for the admin login path). Capability checks read scopes from AdminScopeGrant, not the flat cache column.


AdminCredentials

Source: services/gameserver/src/models/admin_credentials.py

Purpose: Separate password hash for the admin login path, isolated from the OAuth/MFA flow used by ordinary users.

Fields:

name type constraints notes
user_id UUID FK users.id PK, CASCADE 1:1 with User
password_hash String(255) not null bcrypt/argon2
last_password_change DateTime server default now for rotation policy

Relationships: user (1:1, back-pop User.admin_credentials).


AdminScopeGrant

Source: services/gameserver/src/models/admin_scope_grant.py — ✅ shipped, greenlit as ADR-0093 item 19 (2026-07-10).

Purpose: The live authorization gate. One row per (admin user, scope) grant. An admin's capabilities are exactly the set of scopes they currently hold (revoked_at IS NULL). Capabilities are drawn from the canonical scope set defined in ADR-0058 A-F2 — families admin.players.*, admin.subscriptions.*, admin.webhooks.*, admin.regions.*, admin.aria.audit, admin.multi_account.review, admin.bang.regenerate, admin.scopes.*, admin.audit.view.

Full field schema (id, user_id, scope, granted_by, granted_at, revoked_at, revoked_by) and indexes are in ./gameplay.md#adminscopegrant. The bootstrap superadmin is granted admin.scopes.grant + admin.scopes.revoke + admin.audit.view via a one-time migration.

User.is_admin is the derived view EXISTS (SELECT 1 FROM admin_scope_grants WHERE user_id = User.id AND revoked_at IS NULL) — see User (admin flag).


AdminActionLog

Source: services/gameserver/src/models/admin_action_log.py — ✅ shipped with the ADR-0093 item 19 RBAC foundation chain.

Purpose: Append-only record of every scope-authorized admin action, carrying the scope_used that authorized it. Drives the Admin UI /audit ledger and HIGH_IMPACT retrospective review queue (AdminActionLogPage) that surfaces high-impact actions (admin.subscriptions.*, admin.webhooks.replay, admin.regions.terminate, admin.scopes.*) for acknowledgement by another admin holding admin.audit.view / admin.audit.review. Distinct from the catch-all HTTP AuditLog below: AuditLog is the request-trail emitted by middleware; AdminActionLog is the live scope-keyed action record.

Full field schema (admin_user_id, scope_used, action, target_type, target_id, payload_snapshot, result, failure_reason, reviewed_by, reviewed_at, at) and indexes are in ./gameplay.md#adminactionlog.


AuditLog

Source: services/gameserver/src/models/audit_log.py

Purpose: Catch-all request/audit trail emitted by middleware. Used for security/compliance review and admin search.

Fields:

name type constraints notes
id UUID PK
timestamp DateTime default utcnow, indexed
method String(10) not null HTTP verb
path String(255) not null, indexed
status_code Integer nullable
duration_ms Integer nullable
user_id UUID nullable, indexed no FK constraint (logs survive user deletes)
user_type String(20) nullable admin/player/anonymous
client_ip String(45) not null IPv6-safe
user_agent Text nullable
action String(100) indexed login/logout/economy_intervention/…
resource_type String(50) nullable auth/ship/economy/message/…
resource_id UUID nullable affected resource id
query_params, request_body, security_flags JSON nullable request body should be sanitized of secrets
response_summary Text nullable
violation_detected String(100) nullable violation classifier

Relationships: none (deliberately decoupled — no FK).


Per-resource admin flags

Several entities carry inline admin / moderation columns rather than living in a centralized permissions table. These are the surfaces an admin UI hits:

Entity Admin columns Source
MarketTransaction (enhanced_market_transactions) admin_notes, flagged_suspicious, reviewed_by (FK users.id) market_transaction.py
CombatLog admin_notes, admin_resolved, admin_resolved_at, disputed, resolved, admin_reviewed combat_log.py
Message flagged, flagged_reason, moderated_at, moderated_by (FK users.id) message.py
PriceAlert acknowledged_by (FK users.id), acknowledged_at, resolved_at market_transaction.py
Reputation is_locked, lock_reason, lock_expires, special_status reputation.py

Indexes on audit_logs.timestamp, audit_logs.path, audit_logs.user_id, and audit_logs.action cover the common admin-search queries. Composite indexes for user-activity searches are defined inline.


Authorization surface

The live gate is fine-grained scopes, not roles, per ADR-0058 A-F2, greenlit by ADR-0093 item 19. Middleware/routes gate each admin endpoint on a specific scope (e.g., admin.regions.terminate), returning 403 with the missing scope name in the body when a request lacks it. Scope membership is read from active AdminScopeGrant rows — operators dial each admin's capabilities up or down one scope at a time, with no role hierarchy. User.is_admin is the grant-derived EXISTS view used where a Boolean admin check remains; it is not the historical flat column gate from ADR-0027 (kept as history only). Per-resource moderation flags (the inline columns above) sit alongside the scope set: scopes gate who may invoke a capability, while the inline flags carry per-record moderation state for the entity being administered.