Skip to content
Concepts

Permissions language

Imagine a writing app. Maya creates a post. It starts private, but she can invite Ken to read it or help edit it. Comments should follow the post's access, so Maya doesn't have to share each comment separately.

Rent lets you describe those rules in RSL, beside the data they belong to. Your Rust code keeps using the ordinary query and update builders. Rent applies the rules for the person making the request.

Permissions are optional. If your application doesn't need them, leave out access and permissions and use Rent as an ordinary ORM.

Start with a post and its author

A post has an author. That is an ordinary data relationship:

model User {
  id    BigInt @id
  name  String
  posts Post[]
}

model Post {
  id        BigInt @id
  title     String
  author_id BigInt
  author    User @relation(fields: [author_id], references: [id]) @rent.creator
}

author is the name we chose for the relationship to User. It isn't a built-in role. The @rent.creator annotation fills that relationship from the person creating the post and keeps authorship from being changed afterward.

Now opt into permissions and say what authors can do:

access {
  subjects = [User] // People using the app are represented by User rows.
}

permissions User {
  read = anyone
  create, update, delete = nobody // Account provisioning stays in trusted code.
}

permissions Post {
  read   = author // You can read a post you authored.
  create = anyone // Any signed-in subject can create a post.
  update = author
  delete = author
}

Maya can create, read, edit, and delete her posts. Ken cannot see them yet. Here, anyone means a viewer with an authenticated subject. Rent relies on your application to verify the login and tell it who that subject is.

Give a post editors and readers

Replace the Post rules with ones that allow sharing:

permissions Post {
  editor = author or [User] // The author, plus people assigned as editors.
  viewer = editor or [User] // Editors, plus people assigned as readers.

  read          = viewer
  create        = anyone
  update        = editor
  delete        = author
  manage_grants = author   // Only the author can change sharing.
}

Read it as a few sentences: an editor is the author or someone given an editor grant. A viewer is an editor or someone given a viewer grant. Viewers can read. Editors can also update. The author controls deletion and sharing.

[User] does not mean every user. It means this rule accepts assignments to individual users. Assign Ken as an editor on one post, and he can edit that post. He receives no access to Maya's other posts.

The names editor and viewer are ours. Rent recognizes the operation names such as read and update. We connect those operations to our own rules.

In this schemaWhere the name comes from
User, PostModels we define
authorA relationship we define on Post
editor, viewerPermission rules we define
read, create, update, delete, manage_grantsOperations recognized by Rent
or, and, but not, whenRSL language syntax

Use the generated permission API

After changing the rules, run rent generate to update the client. When adding stored grants or model fields, use the migration workflow to update the database too.

Your application binds the signed-in person to a client. For this single-key User model, the generated subject helper takes the user's ID:

use generated::{post::Post, user::User};
use rent::policy::viewer::Viewer;

let maya = root.clone().with_viewer(Viewer::of(User::subject(maya_id)));
let ken = root.clone().with_viewer(Viewer::of(User::subject(ken_id)));

Maya can then share an existing post with Ken:

maya.post_permissions(&post_id)
    .grant_editor(User::subject(ken_id))
    .save()
    .await?;

Sharing is a data change. It doesn't require a schema edit, regeneration, or a new JWT. To remove that assignment:

maya.post_permissions(&post_id)
    .revoke_editor(User::subject(ken_id))
    .await?;

Ken's ordinary queries and edits use the same generated API as before:

let posts = ken.post().order_by(Post::id).all().await?;

let post = ken.update_post()
    .id_eq(post_id)
    .title("A clearer title")
    .save_one()
    .await?;

The list contains only posts Ken can read. The edit succeeds only if he has update permission at the time of the operation. You don't have to repeat the sharing rules inside every handler. See root and scoped clients for where this request binding belongs in an application.

Let comments follow their post

Suppose Comment has the ordinary relationships post and author. Its permissions can use the Post rules you already wrote:

permissions Comment {
  read   = post.viewer
  create = post.viewer
  update = author and post.viewer
  delete = (author and post.viewer) or post.editor
}

post.viewer means “follow this comment's post, then use its viewer rule.” Someone who can read the post can read and add comments. A comment's author can edit it while they still have access, and a post editor can moderate it.

Sharing the post therefore shares the discussion. Removing Ken's only route to the post also removes his access to its comments. For the complete models, see tenant-safe sharing.

Build on the same idea

Relationships let you grow beyond direct sharing without changing the mental model:

// Grant access to a team's members, as well as individual users.
editor = author or [User, Team#member]

// A child can inherit its parent's readers.
viewer = editor or parent.viewer

// Published posts can be read without signing in.
read = viewer or public when published: true

These are separate extensions to the example: Team, its member rule, the parent relationship, and the published field are definitions you add to your schema. Team#member lets you share with a team once. Its membership determines who benefits. parent.viewer lets you share a collection rather than every item inside it.

Use or for either route to access, and when both must hold, and but not to exclude people matched by another rule. A when condition limits a rule using the resource's fields. The language reference covers the full vocabulary.

Keep tenants separate

Tenancy answers “which organization's data is this?” Permissions answer “what can this person do with it?” You can enable either feature independently or combine them.

With both enabled, a permission never crosses the tenant boundary. Even a public post stays inside the selected tenant. The multi-tenant tutorial shows the complete tenant-aware schema and viewer identities.

Being in the same tenant does not automatically grant access to a post. The tenant boundary and the post's permission rules must both allow the request. See tenancy and permissions for how these boundaries work together and where custom Rust policies fit.

Choose your next step

For syntax, conditions, transaction requirements, and detailed API behavior, continue to the permissions reference. You can add permission caching later if repeated explicit checks need it.