Skip to content

Instantly share code, notes, and snippets.

@rami-ruhayel-vgw
Created March 30, 2026 08:28
Show Gist options
  • Select an option

  • Save rami-ruhayel-vgw/2be2a25c108cd62e0eba5102ef8195bc to your computer and use it in GitHub Desktop.

Select an option

Save rami-ruhayel-vgw/2be2a25c108cd62e0eba5102ef8195bc to your computer and use it in GitHub Desktop.
Permissions Service: Proposal and Enforcement Gap Analysis

Permissions Service: Proposal and Enforcement Gap Analysis

Context

An investigation of the game client's feature toggle and configuration system uncovered a broader issue: business rule authorization (market exits, self-exclusion, purchase limits) is enforced almost entirely client-side. Backend services accept requests from any authenticated user regardless of market status, self-exclusion state, or purchase limits.

This document summarizes the findings and proposes a centralized permission service that all backend services call before allowing restricted actions.

A full design document covering the complete toggle inventory, permission model, Kotlin implementation, test strategy, and phased rollout plan is in progress and will be submitted as a PR to gp-game-client (docs/features/feature-toggle-and-config-migration.md).


The Problem

The game client determines what a user can do based on their market, account status, and self-exclusion state. It hides UI elements accordingly. But the backend services behind those UI elements do not independently enforce these restrictions.

A user who calls backend APIs directly with a valid JWT can bypass all client-side restrictions.


Enforcement Gap Summary

Restricted behavior Client enforces? Server enforces? Risk
SC purchase in restricted market Yes (hides UI) No -- pok-store accepts claims without market check HIGH
SC gameplay in restricted market Yes (hides currency) No -- gp-poker, blackjack, slots do not check HIGH
Self-excluded user playing Yes (modal) No -- game servers do not check HIGH
SC redemption in restricted market Yes (hides UI) No -- pam-pay-hub-app does not check HIGH
Purchase limit exceeded Yes (client calculates) No -- pok-store does not call pok-player-account HIGH
SC vault claim in restricted market Yes (hides UI) Partial -- checks account status, not market MEDIUM
Self-excluded user in tournaments Partial (modal) Reactive only -- unregisters after exclusion, does not prevent new registration MEDIUM
Bank verification before redemption Yes (hides UI) No -- pam-pay-hub-app does not check MEDIUM

Per-service findings

pok-store: Auth (JWT/API key), package active/expired, claim limit. No market, region, phase, or purchase limit checks. A user in a blocked market can claim SC packages.

pok-player-account: Vault claims check user account status (BLOCKED, REMOVED, MUST_KYC_NOW, MUST_SOW_NOW) but not market exit phases or regional restrictions. Purchase limits are tracked but not enforced at purchase time (pok-store does not call pok-player-account).

gp-poker: playerJoined() adds the player to the table immediately. No market, phase, currency, or self-exclusion checks. Tournament registration only checks RUG conflicts, not self-exclusion.

pok-blackjack-server: Has MarketUtils and PlayerFeatureConfig with market/phase config, but only uses them for error message formatting after a wallet failure, not to prevent game initiation.

pok-slots-server: No market restriction or permission checks found.

pam-pay-hub-app: Redemption session creation checks player profile and token balance. No market restriction, bank verification, or regional checks.

pok-customer: Collects and stores residential address. Has a per-user account_restrictions table. No market-based enforcement on any endpoint.


Proposed Solution: Centralized Permission Service

A single service that answers "what can this user do?" All market data, phase logic, self-exclusion status, and purchase limit state are evaluated in one place. Backend services call it before allowing restricted actions.

Architecture

Game Client (browser)
    |
    | Auth0 JWT
    v
Express web tier --calls--> Permission Service --> { permissions, restriction }
    |                           ^      ^      ^
    | injects permissions       |      |      |
    v (for UI rendering)        |      |      |
Browser renders UI              |      |      |
    |                           |      |      |
    | Auth0 JWT                 |      |      |
    v                           |      |      |
pok-store ------calls-----------+      |      |
gp-poker -------calls------------------+      |
pam-pay-hub-app --calls-----------------------+
(etc.)

The Auth0 JWT provides identity (who is this user). The permission service provides authorization (what can this user do). Two questions, two systems.

Permission vocabulary

16 action-oriented permissions. Each maps to a concrete backend operation.

Permission Description
REGISTER Can register a new account
LOGIN Can log in
PURCHASE Can make any purchase (within limits)
PURCHASE_SC Can purchase Sweeps Coin packages
PLAY_POKER Can play any poker (GC or SC)
PLAY_SC_POKER Can join SC poker tables/tournaments
PLAY_CASINO Can play any casino game (GC or SC)
PLAY_SC_CASINO Can play SC casino/slots games
REDEEM_SC Can redeem SC for cash
CANCEL_REDEMPTION Can cancel a pending SC redemption
CLAIM_SC_VAULT Can claim SC vault
CLAIM_GC_VAULT Can claim GC vault
RECEIVE_SC_BONUS Can receive SC daily bonus, verification bonus, gift spins, prize draw
RECEIVE_GC_BONUS Can receive GC daily bonus
USE_BONUS_CODES Can redeem bonus codes
VIEW_SC_HISTORY Can view SC transaction and gameplay history

Three parent-child relationships provide hierarchy:

  • PLAY_POKER -> PLAY_SC_POKER (self-exclusion denies the parent, market restrictions deny the child)
  • PLAY_CASINO -> PLAY_SC_CASINO
  • PURCHASE -> PURCHASE_SC (purchase limits deny the parent, market restrictions deny the child)

API

GET /permissions/{userId}?geoLocationState=ny

Response:
{
    "permissions": {
        "PURCHASE_SC": false,
        "PLAY_SC_POKER": false,
        "REDEEM_SC": true,
        ...
    },
    "restrictions": [
        {
            "type": "promo-play",
            "phase": 2,
            "market": { "isoEntry": "NY", "regionName": "New York" },
            "denies": ["PURCHASE_SC", "PLAY_SC_POKER", "PLAY_SC_CASINO", ...]
        }
    ]
}

Single-action query for backend services:

GET /permissions/{userId}?action=PURCHASE_SC

Response:
{
    "allowed": false,
    "reason": "Promo play phase 2 - SC purchases blocked"
}

What each service needs to do

Each service adds a single call before allowing a restricted action: extract userId from JWT, call permission service, check the relevant permission, reject with 403 if denied.

Service Restricted actions Permission(s) to check
pok-store SC package purchase/claim PURCHASE_SC AND PURCHASE
pok-player-account SC vault claim CLAIM_SC_VAULT
pok-player-account GC vault claim CLAIM_GC_VAULT
pam-pay-hub-app Create redemption session REDEEM_SC
pam-pay-hub-app Cancel redemption CANCEL_REDEMPTION
gp-poker Join table (any currency) PLAY_POKER, and PLAY_SC_POKER if SC table
gp-poker Register for tournament PLAY_POKER, and PLAY_SC_POKER if SC tournament
pok-blackjack-server Start game PLAY_CASINO, and PLAY_SC_CASINO if SC game
pok-slots-server Start game PLAY_CASINO, and PLAY_SC_CASINO if SC game
pok-customer Registration REGISTER

Where the permission service lives

pok-customer is the proposed home. It already stores the user's residential address, has an account_restrictions table with per-user phase data, has auth infrastructure (JWT validation, API keys for service-to-service), and is already called by pok-player-account for user status checks.

Alternatively, a standalone service if pok-customer's responsibilities should not grow.


Phased Rollout

Backend enforcement is added wave by wave, prioritized by risk and regulatory exposure.

Wave 1 -- Self-exclusion (regulatory, zero server enforcement today):

  1. gp-poker: Check PLAY_POKER on table join and tournament registration
  2. pok-blackjack-server: Check PLAY_CASINO on game start
  3. pok-slots-server: Check PLAY_CASINO on game start

Wave 2 -- Market restrictions on financial actions: 4. pok-store: Check PURCHASE_SC and PURCHASE before SC package claims 5. pam-pay-hub-app: Check REDEEM_SC before redemption session creation 6. pok-player-account: Check CLAIM_SC_VAULT before SC vault claims

Wave 3 -- Market restrictions on gameplay: 7. gp-poker: Add PLAY_SC_POKER check (SC table/tournament distinction) 8. pok-blackjack-server: Add PLAY_SC_CASINO check 9. pok-slots-server: Add PLAY_SC_CASINO check

Wave 4 -- Registration and remaining gaps: 10. pok-customer: Check REGISTER for new users in blocked markets


What This Does Not Change

  • API access control (JWT validation, role-based checks like canClaimPackage) stays where it is. The permission service adds business rule authorization alongside existing access control, not instead of it.
  • Client-side UI rendering still happens. The client receives the permission response and uses it for UI visibility decisions. The difference is that the client no longer evaluates the rules -- it receives the result.
  • Auth0 JWT issuance is unchanged. The JWT provides identity. The permission service provides authorization.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment