Role model — what each scope covers
Every scoped route's decorator is @Roles(<granular scope>, admin, super_admin), and authorization passes if the user holds any of those. The granular scope below is the lowest-privilege role that unlocks the route; admin and super_admin also unlock it.
user_administration_read
- Everything visible on a User Detail page: profile, KYC status, bans/state, tags, notes, limits, referrals, bonuses, codes, tournaments, statistics, wallets, transactions, wagers, bets.
- Also: audit log, external users list, list of pending password reset requests, and the payouts and invoices read surfaces (lists, details, counts) — the former
payments_readscope has been folded in here.
user_administration_write
- Writes to a user's account that neither move money nor adjust balance: notes/tags CRUD, chat moderation, ban/unban/bulk-ban, user state changes, KYC decisions, RG limits, password reset, reset TOTP.
- Carve-outs: crediting or seizing balance now lives in
funds_write, changing a user's roles field (on the otherwiseuser_administration_writeroutePATCH /users/:id) requiressuper_admin, and level recalculation moved toplatform_setup_write. - Target-level guard: several of these routes additionally refuse to act on a target user who is a
super_adminunless the caller is also asuper_admin(see Target-level protection).
funds_write
- Dedicated scope for any change to a player's in-game balance beyond low-value AMOE grants: awarding bonuses (
POST /user-bonuses), granting cash and sweepstakes packages (POST /grants/grant-cash,POST /grants/grant-package), awarding SW packages (POST /user-sw-packages), crediting redemption codes (POST /user-codes/:id/credit), awarding tournament prizes (POST /user-tournament-prize-claims), and confiscating balances (POST /confiscations). - These affect internal balance only — real money leaves the platform through payouts, gated separately by
payments_write. Low-value AMOE grants are excluded (they don't require this scope), and bonus edits (PATCH /user-bonuses) are account maintenance rather than balance movements, so they remainuser_administration_write.
platform_setup_read
- Reads on catalog/content: games, providers, sections, promotions, discounts, popups, tournaments, avatars, tags, levels, SW packages, notifications, whitelabel content, bonus templates, global config.
- Also holds document reads (see
documents_writebelow).
platform_setup_write
- Writes on catalog/content: create/update/delete for everything above. Plus S3 presigned URL for asset uploads and user-level recalculation (
POST /user-levels/recalculate) — treated as a platform-config recompute across all players. Multiplayer-hash generation has moved tosuper_admin.
payments_write
- Only actions that actually move money in or out of the platform: approve/deny/complete/fail payouts (bulk or single) and invoice adjustments (complete-underpaid, dismiss-underpaid, recalculate-tax). The payout/invoice read surfaces are now under
user_administration_read. - Balance credits (bonuses, grants, code redemption, SW package awards, tournament prizes) are not here — those live in
funds_write, affect internal balances only, and must be confirmed through the payout flow to leave the platform.
documents_write
- Create/update/delete of legal & help documents. Read is granted via
platform_setup_readbecause doc content is not sensitive; only mutations are.
admin
- Legacy blanket role, still present on essentially every route's
@Roles(...)alongside the granular scope. Because the guard authorizes on any listed role, anadmincan call every scoped route regardless of which granular scopes they hold. - Exception:
admindoes not satisfy the twosuper_admin-only actions — generating multiplayer hashes (POST /multi-player-hashes/generate) and changing therolesfield onPATCH /users/:id. It is also subject to the super-admin target guard. - The
granular-admin-rolesmigration keeps this role and backfills every existingadminuser with all seven new granular scopes, so legacy admins keep full access.
super_admin
- Also a blanket role — listed on every scoped route, so it satisfies everything
admincan, plus the two highest-blast-radius actions no other role grants: changing a user's roles and generating multiplayer game hashes (POST /multi-player-hashes/generate, which affects game-outcome integrity). - It is the only role exempt from the super-admin target guard — a
super_admincan ban/restrict/reset anothersuper_admin. - Field-level exception: on
PATCH /users/:idthe role gate is enforced per field — the route itself requiresuser_administration_writeto edit status/notes, but a payload that touches therolesfield escalates the requirement tosuper_admin(a privilege-escalation vector). It appears under both scopes in the table above for this reason.
Target-level protection — acting on super_admin accounts
- Independent of the route scope, a caller who is not a
super_adminis refused when the target user is asuper_admin. This guards account-takeover / lockout vectors and applies to: ban & unban (POST /user-bans,PATCH /user-bans/unban, and each item ofPOST /user-bans/bulk-ban), user-state changes (PATCH /user-states/:id,PATCH /user-states/user/:userId, and each item ofPATCH /user-states/bulk-restrict), password reset (POST /password-reset-requests), and TOTP reset (DELETE /totp). - In bulk operations the check runs per target, so super_admin targets are skipped/failed individually rather than blocking the whole batch.
any
- Not a scope — routes tagged
anyhave no granular gate. Two flavours exist: - Fully public (no auth): app info and health checks (
GET /,/alive,/ready) and the login entry point (POST /auth/email/login). - Any admin role (session must hold at least one admin role, but no specific scope): current session profile (
GET /users/me), logout (DELETE /auth/logout), self-MFA setup (own TOTP activation and verification), and global-config read (GET /global-config), which the admin frontend needs to bootstrap.
Highest-blast-radius routes worth extra care
Gated by the roles above, but each deserves audit logging / secondary approval / rate limiting at the application layer:
PATCH /users/:id— base route isuser_administration_write, but a payload that changes therolesfield is gated bysuper_admin(privilege escalation vector). Enforcement is field-level, so validation must inspect the payload rather than gate the whole route.GET /global-config— readable by any admin session (needed for FE bootstrap), so its payload must be scrubbed of secrets server-side.POST /confiscations— irreversible balance seizure.POST /invoices/recalculate-tax— bulk across all invoices.POST /user-levels/recalculate— bulk recompute across all players; nowplatform_setup_write.POST /user-limits/:userId/reset— RG compliance implications.POST /multi-player-hashes/generate— affects game-outcome integrity; now gated bysuper_admin.DELETE /totp— resetting another user's MFA is an account-takeover vector; a super_admin's TOTP can only be reset by another super_admin.PATCH /user-states/bulk-restrict,POST /user-bans/bulk-ban,PATCH /user-states— bulk account restrictions.POST /grants/grant-cash,POST /user-bonuses,POST /user-sw-packages,POST /user-tournament-prize-claims— balance credits (funds_write), not payouts, but abusable at scale.
Judgment calls worth reviewing
- Blanket admin roles: every scoped route still accepts
adminandsuper_adminin addition to its granular scope, so the granular model is a minimum-privilege layer on top of a legacy hierarchy, not a replacement for it. If the intent is pure least-privilege, the blanketadminrole would need to be removed from route decorators (and from users) rather than merely supplemented. - Balance movements vs. payments: bonuses, grants, code credits, SW package awards, tournament prizes, and confiscations all sit under the dedicated
funds_writescope. They only change internal balances. Real money in/out is gated separately bypayments_write(payout + invoice mutations only). - Payout / Invoice reads → folded into
user_administration_readafter the standalonepayments_readscope was removed. If Finance needs read access without full user-admin visibility, reintroduce a dedicated read scope. - Wallet / Wallet Transaction / Bet Wallet Transaction / Wager / Bet reads →
user_administration_read(surfaced on User Detail). - User-level recalculation →
platform_setup_write, treated as a platform-config recompute rather than a per-user write. - Audit Log →
user_administration_read. Could arguably be its owncompliance_readscope. - Global Config read →
any(any admin role), since the admin frontend needs it to bootstrap. Because every admin session can read it, any real secrets must be filtered out server-side (or moved to a dedicated scoped endpoint). - S3 presigned URL →
platform_setup_write. If documents editors upload attachments via S3, they'll also need this scope — consider grantingdocuments_writeholders a scoped variant. - TOTP self-endpoints operate on the session user only. The
DELETE /totpreset targets another user and is gated byuser_administration_writeplus the super-admin target guard.