Admin & Audit¶
Status: ✅ Shipped — Admin model is fully implemented: User.is_admin, AdminCredentials, AuditLog with all columns, and per-resource inline admin flags all match the spec exactly … (impl audit 2026-06-16)
Admin authentication is a side table on User (AdminCredentials). Authorization is a fine-grained scope model per ADR-0058 A-F2: each admin capability is one of 19 canonical scopes, granted per-admin via the AdminScopeGrant join table, and every admin action is recorded in AdminActionLog. The User.is_admin boolean is a derived view (is_admin = EXISTS (active scope grant)), not the authorization gate. Beyond scope-gated capabilities, granular moderation state 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
The is_admin: Boolean column on User is a derived convenience view — is_admin = EXISTS an active AdminScopeGrant for that user — so a plain is_admin check resolves to "holds any admin scope." It is not the authorization gate: capability checks read scopes (see AdminScopeGrant). See ./player.md for the full User schema. An admin user is one holding at least one active scope grant, with a populated AdminCredentials row for the admin login path.
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
Purpose: The 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 19-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).
AdminActionLog¶
Source: services/gameserver/src/models/admin_action_log.py
Purpose: Append-only record of every scope-authorized admin action, carrying the scope_used that authorized it. Drives the daily review queue that surfaces high-impact actions (admin.subscriptions.*, admin.webhooks.replay, admin.regions.terminate, admin.scopes.*) for retrospective acknowledgement by another admin holding admin.audit.view. Distinct from the catch-all HTTP AuditLog below: AuditLog is the request-trail emitted by middleware, while AdminActionLog is the 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¶
Authorization is fine-grained scopes, not roles. Middleware/routes gate each admin endpoint on a specific scope (e.g., admin.regions.terminate); a request lacking the scope returns 403 with the missing scope name in the body. Scope membership is read from active AdminScopeGrant rows. There is no roles enum and no role hierarchy — operators dial each admin's capabilities up or down one scope at a time. This is canonical per ADR-0058 A-F2, which supersedes ADR-0027 (whose flat-is_admin model scoped the pre-0058 admin surface). 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.