TBBN.Merchant Platform docs
Securitydocs/security/rbac-strategy.md

RBAC Strategy

Merchant-side roles (merchant_users.role)

RoleCan do
OWNEREverything below, plus transfer ownership, close the account, change billing plan
ADMINInvite/remove users, create/rotate/revoke API keys, change merchant settings, view billing (not change plan)
DEVELOPERCreate/rotate API keys (sandbox unrestricted; production keys require ADMIN+ approval), view API logs/usage, configure webhooks
ACCOUNT_MANAGERManage seller relationships/support tooling in the dashboard, view analytics, no API key or billing access

Every merchant-facing dashboard route and every /v1/merchants/:id/... and /v1/api-keys/... API call is gated by this table — enforced centrally in auth-service's RBAC guard (currently implemented inside services/merchant-service, see docs/business/conflict-resolution-log.md item 13 for why it's not yet a standalone rbac-service), not re-implemented per endpoint.

Business-side roles (business_users.role, Phase 34)

RoleCan do
OWNEREverything below, plus transfer ownership, delete the Business — always implicitly satisfied by Business.ownerUserId, even without a row in this table
ADMINInvite/remove BusinessUsers, edit the business profile, manage SpaceApiKeys (including production)
DEVELOPERCreate/rotate sandbox SpaceApiKeys (production requires ADMIN+), manage SpaceApiKeyConfig and Business webhook subscriptions
ACCOUNT_MANAGERCreate/edit Branches and Spaces, manage pricing/bookings — no API-key or business-deletion access

Mirrors the Merchant table above exactly (see docs/business/conflict-resolution-log.md item 28) — "they or other designated roles in account.tbbnetwork.com will then proceed to space.tbbnetwork.com" (founder directive). Enforced via requireBusinessRole(), duplicated identically into business-service and space-service (same precedent as requireMarketplaceMemberUserId), not a shared package — see those files' own doc comments.

Internal TBBN Operations roles (StaffRole, Phase 39)

RoleCan do
SUPER_ADMINEverything below — bypasses every @OpsRoles(...) check regardless of which roles a route lists
MERCHANT_OPSMerchant application review/approval (merchant-service)
BUSINESS_OPSBusiness verification (business-service), Space commission-rate overrides (space-service)
MODERATIONModeration queue review (moderation-service)
FRAUDFraud signal review (fraud-service)
SUPPORTGeneral support/audit-log read access; also BUSINESS_OPS-equivalent for commission-rate overrides

TBBN_OPS is a User.authType value with a companion staffRole: StaffRole field on the same row (no separate StaffUser table — a single role is enough since staff aren't scoped to multiple orgs the way MerchantUser/BusinessUser are). Staff sign in at admin.tbbnetwork.com (apps/ops-console) via auth-service's requestStaffOtp/ verifyStaffOtp — the same OTP pattern as every other principal kind, issuing a TbbnOpsClaims (packages/core-auth). No self-registration: accounts are provisioned via tools/create-staff-user, never a public endpoint. Enforced by OpsRolesGuard/@OpsRoles(...) (packages/core-auth/src/ops-rbac.guard.ts) — a sibling to the Merchant-side RolesGuard/ @Roles, kept separate rather than generalized to avoid any regression risk to that already- working guard. Every route named above is gated; feature-service's narrow GET :key/check (a runtime lookup other services call to gate a single feature) deliberately stays open — it's not an admin action. See docs/business/conflict-resolution-log.md item 32.

Sellers

Sellers have no role hierarchy — a marketplace member's JWT grants access only to their own records (seller_id claim scoping) across every endpoint, enforced by row-level ownership checks, not a role table.

Principle

RBAC checks happen at the service layer (NestJS guards), never trusted from client-supplied data, and always re-derived from the validated principal (JWT claims or internal-principal header) — never from a request body field like role or merchantId.