Gamers Lab Docs

3.4 Tenant Bans

Section 3 — Player Management

3.4 Tenant Bans

Scope

Covers how tenant admins and platform admins view and manage players within a tenant. Includes player listing, ban management, login enforcement, and platform-level player administration (block, reactivate, soft delete).

Two Levels of Management

LevelWhoRoute prefixScope
Tenant adminSaaS user with admin or owner role on the tenant, or platform admin/api/bus_tenants/\{tenantId\}/... and /api/admin/bus_tenants/\{tenantId\}/...Players within one specific tenant
Platform adminGamersLabAdmin or GamersLabOwner platform role/api/admin/playersAll players across the entire platform

Tenant Admin — Player Listing

GET /api/admin/bus_tenants/{tenantId}/players
Authorization: Bearer <saas_user_jwt>

Returns all players who have ever logged into the tenant. Includes ban status inline. bannedAt, bannedUntil, and bannedReason are populated only when the ban is currently active. Results are paginated.

Query parameters:

ParameterTypeDefaultDescription
querystringOptional prefix search on display name or email, or exact player ID match
pageint11-based page number
pageSizeint50Page size, max 200
includeInactiveboolfalseInclude merged/inactive player profiles

Required role: admin or owner on the tenant. Platform admins bypass the role check.

Returns (PagedTenantPlayerAdminResponseDto):

page            int
pageSize        int
totalCount      int
items[]
  playerId        Guid
  displayName     string?
  email           string?
  isActive        bool
  firstSeenAt     DateTime
  lastSeenAt      DateTime
  loginCount      int
  isBanned        bool
  bannedAt        DateTime?
  bannedUntil     DateTime?      — null if permanent active ban
  bannedReason    string?

Tenant Admin — Ban Management

Bans are scoped to a single tenant. A player banned from one game is not affected in any other game.

Apply or Update a Ban

PUT /api/bus_tenants/{tenantId}/player-bans/{playerId}
Authorization: Bearer <saas_user_jwt>
Content-Type: application/json

Required role: admin or owner on the tenant. Platform admins bypass the role check.

Request body:

{
  "bannedUntil": "2026-06-01T00:00:00Z",
  "reason": "Cheating — third-party aimbots detected",
  "metadata": {
    "reportId": "RPT-12345",
    "severity": "high"
  }
}
FieldRequiredDescription
bannedUntilNoUTC expiry. Omit or set to null for a permanent ban. Must be in the future if provided, and must include Z or an explicit timezone offset.
reasonNoHuman-readable reason. Stored on the ban record; not exposed to the player.
metadataNoArbitrary key-value pairs for internal tracking.

This endpoint is an upsert — calling it on a player who is already banned updates the existing ban record. If the supplied playerId is an old merged profile ID, the ban is applied to the current active canonical profile. The player must already have tenant access for this tenant. Admins cannot create bans for players who have never logged into the game.

Returns (PlayerTenantBanResponse):

playerId        Guid
tenantId        Guid
isBanned        bool
bannedAt        DateTime?
bannedUntil     DateTime?
reason          string?
bannedByUserId  Guid?          — SaaS user ID who set the ban
metadata        object

Clear a Ban

DELETE /api/bus_tenants/{tenantId}/player-bans/{playerId}
Authorization: Bearer <saas_user_jwt>

Required role: admin or owner on the tenant. Platform admins bypass the role check.

Returns 404 if no ban record exists for the player in this tenant. Returns the final ban record with isBanned=false. Clearing a ban is non-destructive: the original bannedAt, bannedUntil, reason, bannedByUserId, and metadata remain on the ban record for audit history.

Ban Enforcement at Login

The ban check runs during every player login flow, regardless of provider. It is not possible to bypass enforcement by switching providers — the check is on the player profile ID, not the auth method.

Covered login endpoints:

  • POST /api/player-auth/login (Steam, Epic, Sequence, Mock)
  • POST /api/player-auth/otc/exchange (EmailOneTimeCode)
  • POST /api/player-auth/challenge/verify
  • POST /api/player-auth/wallet/verify (EVM wallet)
  • POST /api/player-auth/refresh

Active ban criteria: isBanned = true AND (bannedUntil is null OR bannedUntil > utcNow)

Expired bans are treated as inactive and do not block login. The ban record is not automatically deleted or updated when it expires — it remains with the past expiry date.

Response on blocked login: 403 Forbidden

{
  "title": "Player Banned",
  "detail": "Player is banned from this tenant until 2026-06-01T00:00:00Z. Reason: Cheating"
}

The error message includes the expiry time (if set) and reason (if set). No internal IDs or metadata are exposed to the player client. bannedByUserId is never exposed on player-facing ban errors.

Ban Data Model

Table: game_player_tenant_bans

ColumnTypeNotes
iduuidRow identifier
player_iduuidFK → game_player_profiles.id
tenant_iduuidFK → bus_tenants.id
is_bannedbooltrue = banned, false = cleared
banned_attimestamptzWhen the ban was set
banned_untiltimestamptz?Null = permanent
banned_reasontext?Internal reason string
banned_by_user_iduuid?SaaS user who applied the ban
ban_metadatajsonbArbitrary metadata dict

One row per (player_id, tenant_id) pair — bans are upserted, not appended.

Audit Trail

Both ban and unban actions are written to the tenant-scoped audit log automatically.

ActionEvent type
Ban applied / updatedplayer.tenant_ban.applied
Ban clearedplayer.tenant_ban.cleared

Each audit record captures: actorUserId, targetTenantId, playerId, isBanned, bannedAt, bannedUntil, and reason.

Platform Admin — Global Player Management

Platform admins (GamersLabAdmin / GamersLabOwner) have a separate set of endpoints that operate across the entire platform, not scoped to a single tenant.

List All Players

GET /api/admin/players?query=&limit=50&offset=0&includeInactive=false
Authorization: Bearer <platform_admin_jwt>

Searches across all player profiles. query matches display name, email, or player ID.

Returns (PagedAdminPlayerResponseDto):

items[]             AdminPlayerProfileDto[]
limit               int
offset              int
totalCount          int

items[] shape:

id                  Guid
displayName         string?
email               string?
profileVisibility   string
isActive            bool
isEmailVerified     bool
createdAt           DateTime
updatedAt           DateTime
mergedIntoId        Guid?

Get Single Player

GET /api/admin/players/{playerId}
Authorization: Bearer <platform_admin_jwt>

Returns AdminPlayerDetailsDto, a platform-admin detail view. It extends AdminPlayerProfileDto with:

avatarUrl           string?
platformRole        string
authMethods[]       AuthMethodDTO[]
tenantAccess[]      AdminPlayerTenantAccessDto[]
mergedProfileIds    Guid[]

Tenant access rows include current tenant-ban state: isBanned, bannedAt, bannedUntil, and bannedReason.

Create Player (Admin-Provisioned)

POST /api/admin/players
Authorization: Bearer <platform_admin_jwt>
Content-Type: application/json

Creates a player profile with email/password credentials directly, bypassing the normal signup flow. Returns 409 if the email is already registered.

Update Player

PUT /api/admin/players/{playerId}
Authorization: Bearer <platform_admin_jwt>
Content-Type: application/json

Updates displayName, avatarUrl, email, or profileVisibility on behalf of any player.

Block Player (Platform-Wide)

POST /api/admin/players/{playerId}/block
Authorization: Bearer <platform_admin_jwt>

Sets isActive = false on the player profile. Blocked players cannot log in to any tenant. This is a platform-level action — distinct from a tenant ban, which is game-specific.

Reactivate Player

POST /api/admin/players/{playerId}/reactivate
Authorization: Bearer <platform_admin_jwt>

Sets isActive = true. Reverses a block. Does not affect any existing tenant bans.

Soft Delete (Redact) Player

DELETE /api/admin/players/{playerId}
Authorization: Bearer <platform_admin_jwt>

Permanently redacts the player profile. PII is anonymized in-place — the record is not removed from the database, preserving foreign key integrity and audit history.

Ban vs Block — Key Distinction

Tenant BanPlatform Block
ScopeOne specific game/tenantAll tenants, platform-wide
Who sets itTenant admin, owner, or platform adminPlatform admin only
Effect403 at login for that tenant onlyCannot log in anywhere
ReversibleYes — clear ban endpointYes — reactivate endpoint
ExpiryOptional (bannedUntil)No expiry; manual reactivation only
Audit loggedYesNo dedicated audit event (uses profile update)
Reason storedYesNo

Code References

  • Ban controller: GamersLabRestAPI/PlayerAuth/Controllers/PlayerTenantBansController.cs
  • Tenant player list controller: GamersLabRestAPI/PlayerAuth/Controllers/TenantPlayersAdminController.cs
  • Platform admin controller: GamersLabRestAPI/PlayerAuth/Controllers/PlayerAdminController.cs
  • Ban service: GamersLabRestAPI/PlayerAuth/Services/PlayerTenantBanService.cs
  • Ban model: GamersLabRestAPI/PlayerAuth/Models/PlayerTenantBan.cs
  • Ban DTOs: GamersLabRestAPI/PlayerAuth/DTOs/PlayerTenantBanDTOs.cs
  • Admin player DTOs: GamersLabRestAPI/PlayerAuth/DTOs/AdminPlayerDTOs.cs
  • Tenant player admin DTOs: GamersLabRestAPI/PlayerAuth/DTOs/TenantPlayerAdminDTOs.cs
  • Banned exception: GamersLabRestAPI/PlayerAuth/Exceptions/PlayerTenantBannedException.cs

On this page