Admin & Audit¶
Admin authentication is a side table on User; permissions are gated by User.is_admin plus the presence of AdminCredentials. There is no separate "permissions" table — granular flags live 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 the master switch. See ./player.md for the full User schema. An admin user is identified by is_admin=True AND a populated AdminCredentials row.
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).
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.
Note on "admin permissions"¶
Permission gating is done in middleware/routes by checking User.is_admin and then per-route logic. There is no permissions table.