Dashboard: RBAC and Audit Logging¶
The dashboard is the operations control plane. It must be safe by default.
Goals¶
- Only authorized operators can perform privileged actions
- Every privileged action is recorded in an immutable audit log
- High-risk actions (production changes) require elevated roles
Authentication¶
- Provider: GitHub OAuth via NextAuth.js v5
- Session: Database-backed sessions with secure cookies
- Identity: GitHub user ID mapped to internal User record
Authorization (RBAC)¶
Roles are additive; permissions are enforced server-side.
The source of truth for the permission matrix is the dashboard RBAC module: apps/dashboard/src/lib/rbac.ts.
Role: VIEWER (default)¶
- View dashboard
- View matches
- View players
- View feature flags (read-only)
Role: SUPPORT¶
All VIEWER permissions, plus:
- View audit logs
- View moderation pages (read-only)
Role: MODERATOR¶
All SUPPORT permissions, plus:
- Issue moderation actions (ban/mute)
- Review appeals
- Perform bulk moderation
Role: ENGINEER¶
All MODERATOR permissions, plus:
- Toggle feature flags in dev/stage
- Create feature flags
- View settings (read-only)
- View, create, and manage games (register/edit Roblox experiences)
Role: ADMIN¶
All ENGINEER permissions, plus:
- Toggle feature flags in production
- Delete/kill feature flags
- Manage user roles
- Edit settings
- Delete registered games
Bootstrapping an admin (local/dev)¶
New users default to VIEWER, which hides privileged navigation items and blocks privileged routes.
For local/dev bootstrapping, you can promote specific GitHub usernames to ADMIN on sign-in.
Set the environment variable ADMIN_GITHUB_USERS (comma-separated GitHub usernames) for the dashboard server:
ADMIN_GITHUB_USERS="your-github-handle"
Notes:
- This is checked during GitHub sign-in; you may need to sign out/in after setting it.
- Recommended usage: keep this enabled during solo development as a safety net, then remove or tighten it once roles are managed in the database.
Managing roles¶
Admins can manage roles from /users:
- Select a role from the dropdown on a user row
- Click Save to apply
- The app prevents changing your own role from the UI to avoid lockouts
- Role changes are written to the audit log
For safety, role changes require:
- A human-readable reason (min 5 characters)
- A typed confirmation phrase that must exactly match:
set role <userId> <role>
Permission Matrix¶
| Action | VIEWER | SUPPORT | MODERATOR | ENGINEER | ADMIN |
|---|---|---|---|---|---|
| View dashboard | ✅ | ✅ | ✅ | ✅ | ✅ |
| View matches | ✅ | ✅ | ✅ | ✅ | ✅ |
| View players | ✅ | ✅ | ✅ | ✅ | ✅ |
| View flags | ✅ | ✅ | ✅ | ✅ | ✅ |
| View audit logs | ❌ | ✅ | ✅ | ✅ | ✅ |
| View moderation | ❌ | ✅ | ✅ | ✅ | ✅ |
| Moderation actions | ❌ | ❌ | ✅ | ✅ | ✅ |
| Toggle DEV flags | ❌ | ❌ | ❌ | ✅ | ✅ |
| Toggle STAGE flags | ❌ | ❌ | ❌ | ✅ | ✅ |
| Toggle PROD flags | ❌ | ❌ | ❌ | ❌ | ✅ |
| Create flags | ❌ | ❌ | ❌ | ✅ | ✅ |
| Delete/kill flags | ❌ | ❌ | ❌ | ❌ | ✅ |
| View games | ❌ | ❌ | ❌ | ✅ | ✅ |
| Create games | ❌ | ❌ | ❌ | ✅ | ✅ |
| Manage games | ❌ | ❌ | ❌ | ✅ | ✅ |
| Delete games | ❌ | ❌ | ❌ | ❌ | ✅ |
| Manage roles | ❌ | ❌ | ❌ | ❌ | ✅ |
Implementation¶
Server-side enforcement¶
Use permission checks (not client-side role checks) in server components and server actions.
Examples:
- Server pages:
requirePermission("view:flags") - Server actions:
checkPermission("flags:create") - API routes:
requireApiPermission("view:matches")
Client-side UI¶
Frontend hides controls for unauthorized actions (defense in depth):
{
/* UI can hide controls, but server is the authority */
}
Audit Logging¶
Every privileged action is recorded with:
| Field | Description |
|---|---|
id |
Unique audit log ID |
userId |
User who performed the action |
gameId |
Game the action was scoped to (null for platform-level actions) |
action |
Action type (e.g., flag.toggle.prod, game.create) |
target |
Target resource (e.g., flag key, game slug) |
reason |
Human-readable reason/context (required for some high-risk actions) |
before |
Previous state (JSON) |
after |
New state (JSON) |
timestamp |
When the action occurred |
ipHash |
Hashed IP address (optional) |
userAgent |
Browser user agent (optional) |
Action Types¶
| Action | Description |
|---|---|
game.create |
New game registered |
game.update |
Game metadata updated |
game.delete |
Game deleted |
flag.create |
New feature flag created |
flag.update |
Flag metadata updated |
flag.delete |
Feature flag deleted |
flag.toggle.dev |
Dev environment toggled |
flag.toggle.stage |
Stage environment toggled |
flag.toggle.prod |
Production environment toggled |
flag.kill |
Kill switch activated |
flag.unkill |
Kill switch deactivated |
flag.rollout.update |
Flag rollout updated |
evidence.create |
Evidence attached to a ban |
mute.create |
New mute issued |
mute.revoke |
Mute revoked/deactivated |
ban.create |
New ban issued |
ban.revoke |
Ban revoked |
appeal.approved |
Appeal approved |
appeal.denied |
Appeal denied |
user.role.change |
User role changed |
auth.login |
User signed in |
High-risk confirmations¶
Some actions require both a reason (min 5 characters) and a typed confirmation phrase.
This is enforced server-side and the reason is stored in the audit log reason field.
- Toggle PROD flag:
toggle prod <flagKey> on/toggle prod <flagKey> off - Kill switch:
kill <flagKey> - Un-kill flag:
unkill <flagKey> - Change user role:
set role <userId> <role>
Viewing Audit Logs¶
Users with SUPPORT (or higher) can view audit logs at /audit:
- Chronological list of all actions
- User attribution with avatar
- Before/after state comparison
- Relative timestamps
Filters:
- Action: Filter by action category (e.g.,
flag.*,ban.*,mute.*,evidence.*,auth.*) - Target contains: Substring match over the
targetfield (useful for player IDs, flag names, or record IDs) - Reason contains: Substring match over
reason(useful for moderation reasons, revoke reasons, and appeal resolutions) - Details contains: Substring match over serialized
before/afterJSON (useful for finding specific changed fields) - User: Filter to actions performed by a specific operator
UX:
- Copy target: Quickly copy the
targetvalue to your clipboard from the results list.
Exports:
- Use Export CSV / Export JSON to download the currently filtered view.
Security Considerations¶
- Server-side only: All permission checks happen on the server
- Audit immutability: Application code never updates or deletes audit logs
- Session security: Database-backed sessions with secure, httpOnly cookies
- Input validation: All user input validated before processing
Future Enhancements¶
- [ ] Approval workflows for high-risk actions
- [ ] Anomaly detection alerts
- [ ] More granular access restrictions (e.g., per-route allowlists)
- [ ] Two-factor authentication