Skip to content
Concepts

Tenancy and permissions

Maya and Ken work at Acme. Another company, Northwind, uses the same application and database. Maya writes a private HR document. Ken works in Finance.

Two boundaries matter here. Northwind must never see Acme's document. And being an Acme employee should not, by itself, give Ken access to HR's work.

Tenancy separates customers. Permissions control access to resources inside that boundary. You can use either feature independently, or combine them.

Acme and Northwind are separate tenants. Inside Acme, HR and Finance share resources independently.

Keep each customer's data separate

A tenant identifies the customer whose data a request is working with. Your application chooses that tenant after authenticating the request and checking that the person belongs to it.

Declare the boundary once in RSL:

tenancy {
  scope = "required"
  field = "tenant_id"
}

Bind the selected tenant to the request's client, and ordinary queries stay in that tenant. Creates receive its tenant ID. An explicitly conflicting ID is rejected. Reads, updates, deletes, and relationship queries keep the same boundary.

This is a required guard on tenant-scoped models, not a filter each handler has to remember. A request without a tenant is rejected rather than allowed to query every customer's rows. Adding a custom application policy cannot remove the guard.

Acme and Northwind can therefore share a database without sharing their data. But tenancy alone does not decide which Acme employees can read an Acme document. That is the next question.

Decide who can use a resource

Permissions describe relationships between people and resources: who owns a document, who it has been shared with, or which workspace supplies access.

Suppose Document.author is a relationship to the User who created it. The permission part of that schema can start with:

access {
  subjects = [User]
}

permissions Document {
  // Authors can read their work and share it with individual users.
  viewer = author or [User]

  read          = viewer
  create        = anyone
  update        = author
  delete        = author
  manage_grants = author
}

Here, [User] means that a document can have individual users assigned as viewers. It does not mean that every user can read it. The permissions guide builds the complete example.

Maya can read and edit her document. Ken cannot read it until Maya shares it with him. If access should follow a workspace instead, a rule can follow the document's workspace relationship and its membership rules.

With tenancy enabled, these permissions operate inside the selected tenant. Sharing a document does not turn into permission to cross a customer boundary. Even a public read rule remains inside that boundary for tenant-scoped requests.

RequestResult
Maya reads her document in AcmeAllowed by its author relationship.
Ken reads it in Acme before it is sharedDenied, even though he belongs to Acme.
Ken reads it in Acme after receiving a viewer grantAllowed to read. The grant does not let him edit.
A Northwind request tries to reach the Acme documentIt cannot reach the document through the tenant boundary.

Add an application-specific restriction when you need one

Some decisions depend on application behavior rather than resource relationships. For example, an account under administrative review might allow reading while temporarily rejecting writes.

A custom Rust application policy can add that restriction around generated operations. It can reject an operation or narrow the rows it may touch. It does not replace the tenant guard or grant access that the RSL permission rules deny. An author can have permission to edit while an account-wide rule temporarily prevents the edit.

These policies are a general extension point, so they can express some of the same conditions as permission rules. You usually do not need that duplication: keep customer isolation in tenancy, and ownership, sharing, and inherited access in permissions. Reach for a Rust policy when the application needs an additional rule. Hooks and interceptors provide other ways to customize behavior around reads and writes.

Choose only what your application needs

  • Neither feature: use Rent as an ordinary ORM.
  • Tenancy alone: separate customers without defining per-resource permissions.
  • Permissions alone: control resource access without a tenant boundary.
  • Both: first keep customers separate, then apply each resource's access rules.

With both enabled, a normal request must satisfy both boundaries, along with any additional application policy. There is no need to repeat those rules in each CRUD handler. Give each handler a client scoped to the request.

Keep trusted access deliberate

These protections apply through Rent's scoped client. A root client can create an explicit system() handle for trusted operations, such as maintenance across tenants. Keep that authority out of ordinary request handlers. Having an administrator viewer does not, by itself, remove the tenant boundary.

Direct database connections and SQL you author need their own access controls. PostgreSQL row-level security can add enforcement inside the database. Its SQL policies are separate from both Rent's Rust application policies and RSL relationship permissions. See database features for that additional layer.

Continue with multi-tenancy to bind a tenant, or permissions to model ownership and sharing.