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 schema | Where the name comes from |
|---|---|
User, Post | Models we define |
author | A relationship we define on Post |
editor, viewer | Permission rules we define |
read, create, update, delete, manage_grants | Operations recognized by Rent |
or, and, but not, when | RSL 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: trueThese 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
- Create and share a post through the generated client.
- Show Edit buttons beside rows using permission flags.
- Reuse a permission profile across resource types.
- Give different people access to different fields.
- Model an organization with private workspaces.
- Studio viewer mode lets you browse as a chosen person.
- Explain inherited access when a relationship supplies access.
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.
