RBAC Strategy
Merchant-side roles (merchant_users.role)
| Role | Can do |
|---|---|
OWNER | Everything below, plus transfer ownership, close the account, change billing plan |
ADMIN | Invite/remove users, create/rotate/revoke API keys, change merchant settings, view billing (not change plan) |
DEVELOPER | Create/rotate API keys (sandbox unrestricted; production keys require ADMIN+ approval), view API logs/usage, configure webhooks |
ACCOUNT_MANAGER | Manage 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)
| Role | Can do |
|---|---|
OWNER | Everything below, plus transfer ownership, delete the Business — always implicitly satisfied by Business.ownerUserId, even without a row in this table |
ADMIN | Invite/remove BusinessUsers, edit the business profile, manage SpaceApiKeys (including production) |
DEVELOPER | Create/rotate sandbox SpaceApiKeys (production requires ADMIN+), manage SpaceApiKeyConfig and Business webhook subscriptions |
ACCOUNT_MANAGER | Create/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)
| Role | Can do |
|---|---|
SUPER_ADMIN | Everything below — bypasses every @OpsRoles(...) check regardless of which roles a route lists |
MERCHANT_OPS | Merchant application review/approval (merchant-service) |
BUSINESS_OPS | Business verification (business-service), Space commission-rate overrides (space-service) |
MODERATION | Moderation queue review (moderation-service) |
FRAUD | Fraud signal review (fraud-service) |
SUPPORT | General 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.