Skip to content
LIVOI

RBAC technical deep dive

LIVOI combines role-based permissions, tenant context, resource-specific access filters, and PostgreSQL row-level security. Each request should only perform actions and see data allowed within its current tenant context.

A request passes through authentication, tenant resolution, membership checks, permission resolution, and finally the route handler. Early exits return controlled error codes before business processing begins.

RBAC · Access control
100%

The diagram shows the standard path for tenant-bound resources. The handler starts after the route check; access filters and RLS then constrain data access. Superusers can bypass membership, permission, and RLS checks. Privileged admin sessions form a separate trust boundary.

  • Users receive roles directly or indirectly through groups.
  • Roles contain specific permission strings such as chats.read, files.update.own, or agents.delete.any.
  • Effective access is calculated per tenant.
  • API endpoints declare their required permissions.
  • Service operations check existing resources using central access filters.
  • The database receives authentication context as session variables and additionally enforces RLS at the SQL level.
  • API keys bound to both a user and a tenant intersect their permissions with the owner’s effective rights.
  • API keys without a user association derive permissions exclusively from the key itself.
  • Resource grants selectively share individual resources with users, groups, roles, or API keys.
  • Resource grants are checked when accessing a specific resource, rather than included in the cached security context.

The RBAC model has five central layers. Together, they define the organizational security boundary, effective roles, and targeted access to individual resources.

LayerPurpose
TenantThe organizational security boundary. Roles, groups, memberships, and most business resources are tenant-bound.
User and membershipA user may belong to one or more tenants. Only active memberships count toward the effective security context.
GroupsGroups collect users within a tenant. Roles can be assigned directly to users or through groups.
Roles and permissionsRoles contain specific permission strings. Default roles are initialized per tenant.
Resource grantsShare individual resources without expanding a global role.
RolePurpose
ownerFull access to tenant resources, configuration, and access control.
adminAdministrative access, excluding tenant deletion.
memberStandard access to shared tenant structures and the user’s own resources under the principle of least privilege.

Permissions combine a resource type, an action, and an optional scope.

<resource>.<action>
<resource>.<action>.any
<resource>.<action>.own
<resource>.<action>.granted

Examples: chats.read, chats.read.any, files.update.own, and roles.delete.any.

Actions: create, read, update, delete, and access_manage.

ScopeMeaning
No suffixGeneric permission for the action on the resource.
.anyAccess to all resources of this type within the effective tenant context.
.ownAccess only to resources owned by the effective user.
.grantedAccess only to resources with a valid resource grant.

LIVOI builds an AuthContext for each authenticated request. It combines authentication, actor information, tenant selection, roles, groups, and effective permissions.

  • Actor and identity: authentication method, effective user ID, effective tenant ID, and request ID.
  • Roles and groups: direct user roles, group memberships, and group roles are combined per tenant.
  • Permissions: effective permissions are collected from the combined roles and cached briefly.
  • Resource grants: grant records are excluded from the cached context. A matching role or key permission with .granted must be present in the context; the specific grant is checked separately at access time.

The request determines the tenant context, for example through X-Tenant-ID. Tenant-bound endpoints require an effective tenant. Invalid or unauthorized tenant selections are rejected in a controlled manner.

  • JWT requests: if X-Tenant-ID is set, a regular user must be an active member of that tenant. Without the header, the effective tenant remains empty; no default tenant is selected automatically.
  • Multiple tenants: tenant-bound JWT requests require explicit tenant selection, even with only one active membership.
  • API keys: tenant-bound API keys can only be used in their bound tenant. A conflicting tenant header is rejected.

Each protected route defines the authentication context and resource-action combination it requires. This initial check determines whether the request may proceed to business processing.

  • Context requirement: a route specifies whether it needs a user context, a tenant context, or any authenticated identity.
  • Permission requirement: each business action checks for a matching permission in the effective security context.
  • Request context: created from the AuthContext before the route check and passed to the route after successful checks. Opening an app database session applies the authentication context to the transaction.
  1. Missing authentication causes rejection as unauthenticated.
  2. Without the required tenant context, no business handler runs.
  3. A missing required permission ends the request with a permission error.
  4. Only then may the route read or write data or perform a service operation.

Operations on existing resources use central access filters. These combine the tenant boundary, required action, available permission, ownership condition, and resource grant condition.

  • Tenant filter: for tenant-bound operations, resources must belong to the effective tenant.
  • Owner filter: for .own, the resource’s owner column must reference the effective user.
  • Grant filter: for .granted, an active resource grant with the matching permission must exist.

For example, a user with files.update.own may only modify files whose owner column references that effective user.

The standard filter combines the tenant condition with the permitted alternatives: a permission without a suffix, .any, .own with matching ownership, or .granted with a matching grant. No suffix and .any require no additional ownership or grant condition. Available scopes and special rules depend on the resource model.

RBAC checks extend beyond the API layer. LIVOI passes the effective authentication context into the PostgreSQL session. RLS policies read this context and determine whether each affected row is visible or writable in the current request.

Session context: tenant ID, user ID, API key ID, request ID, and permissions as a JSON list, plus superuser status, real user and tenant IDs, and impersonation status. These values are local to the current transaction.

Database checks:

  • Does the row belong to the effective tenant?
  • Is the required permission present?
  • Does the owner match the effective user ID?
  • Does a valid resource grant exist?

API keys have their own permissions. When a key is bound to a user and tenant, its permissions are intersected with the owner’s effective permissions.

  • A regular API key bound to both a user and a tenant can only restrict its owner’s role permissions.
  • A user-bound key without tenant binding uses its own permissions without intersecting role permissions. An explicitly selected tenant is checked for membership for regular users.
  • A key without a user association is resolved through its tenant and uses only its own permissions.
  • Tenant-bound keys must belong to an active tenant.
  • Inactive, expired, or revoked keys are rejected.

For a superuser owner, a tenant-bound key is intersected with the full permission catalog. A key’s superuser bypass additionally requires its own superuser flag.

Resource grants provide finer sharing than roles. A grant is always tenant-bound and refers to a specific resource, a subject, and a permission with .granted.

FieldDescription
ResourceA resource type, such as files, plus a specific resource ID.
Subjectuser, group, role, or api_key, plus a subject ID.
PermissionAlways a matching permission with .granted.
ValidityOptional expiration time and optional grant creator.

A grant alone does not add permissions to the AuthContext: the matching .granted permission must already be present. Group subjects check active group memberships. Role subjects currently check direct user_roles assignments, excluding roles inherited through groups.

  1. The client sends a request with a bearer token or API key.
  2. Authentication validates the token or key.
  3. LIVOI determines the effective tenant and user.
  4. Roles, groups, and permissions are loaded and combined into the AuthContext.
  5. Endpoint permissions are checked.
  6. The database session receives the authentication context.
  7. Service operations build resource access filters.
  8. PostgreSQL RLS additionally checks the final queries.

RBAC determines which actions an actor may perform in principle. RLS additionally determines which specific rows that actor may see or change in the current context.

Access is decided across several layers. Each layer evaluates the same request and provides a more specific answer than the previous one.

LayerTechnical questionResult
AuthenticationIs the actor unambiguously and validly identified by a token or API key?Unauthenticated or expired credentials are rejected early.
Tenant contextWithin which organizational security boundary should the request run?The effective tenant limits roles, groups, grants, and rows.
Effective permissionsWhich actions follow from direct roles, group roles, and API key restrictions?A normalized permission set is created for the current request.
Resource relationshipIs the specific resource accessible to this actor through tenant, owner, or grant?Lists, reads, updates, and deletes receive additional access criteria.
Database boundaryMay the final SQL operation see or change the affected row?RLS enforces the same boundary regardless of the query path.

New resources should use the central access system rather than introduce their own authorization logic. They provide the same technical information:

  • Supported actions: the resource declares available actions such as create, read, update, delete, and access_manage.
  • Scopes: the resource declares whether ownership and resource grants are technically supported. Only then are .own or .granted valid scopes.
  • Database boundary: RLS requires a clear way to determine the resource’s tenant, owner, or parent tenant relationships.
  • Default roles: default roles can selectively include the new resource without requiring special endpoint rules.
  • Least privilege: default roles provide only the access needed.
  • Tenant isolation: the effective tenant is always part of access evaluation.
  • Defense in depth: API checks and PostgreSQL RLS complement each other.
  • Explicit permissions: rights are modeled as specific resource-action-scope combinations.
  • Delegated sharing: resource grants provide targeted exceptions without expanding roles unnecessarily.
  • API key limits: binding a regular key to both a user and a tenant limits it to the intersection with its owner’s role permissions.