Access rules
Decide what the publishable key may read and write, per table and per signed-in user.
The secret key can do anything, so it stays on your server. The publishable key is safe in a browser because it can do nothing until a rule allows it. Rules sit on the table in the schema.
import { defineSchema, rule, t, table } from "@shipmantis/sdk";
export default defineSchema(
{
posts: table({
title: t.text(),
published: t.boolean().default(false),
authorId: t.text(),
}).rules({
read: rule.any(rule.where({ published: true }), rule.owner("authorId")),
insert: rule.owner("authorId"),
update: rule.owner("authorId"),
delete: rule.all(rule.owner("authorId"), rule.role("admin")),
}),
},
{
auth: {
jwks: "https://example.clerk.accounts.dev/.well-known/jwks.json",
issuer: "https://example.clerk.accounts.dev",
},
},
);Rules
| Rule | Allows |
|---|---|
rule.public() | Anyone with the publishable key. |
rule.signedIn() | Any signed-in user. |
rule.owner("column") | Rows whose column holds the signed-in user's id. The column is text or uuid. |
rule.role("admin") | Users with that role. |
rule.where({ column: value }) | Rows whose columns hold those values. |
rule.any(...), rule.all(...) | Any one of the rules, or all of them. |
- Deny by default. An operation with no rule is refused for the publishable key.
- Rows, not tables.
ownerandwherenarrow a read, an update or a delete to the rows they allow; the rest are left alone, as if they weren't there. - Owners fill themselves in. On an insert, an
ownercolumn takes the user's id from the token, whatever the browser sent. An update can't change a column the update rule depends on. - The secret key skips the rules. So does the agent, which holds it.
Signed-in users
Mantis doesn't sign users in. It checks the tokens your auth provider already gives them: Clerk, Auth0 or any provider that signs JWTs and publishes its keys as a JWKS.
auth field | What it holds |
|---|---|
jwks | The provider's JWKS URL, over https://. |
issuer | The iss the tokens must carry. |
audience | Optional. The aud the tokens must carry. |
roleClaim | The claim that holds the user's role, or a list of roles. Default: role. |
A token's sub is the user's id for owner. With a list of roles, rule.role matches any of
them. An expired token, or one from another issuer, is refused with 401.
Give the client a function that returns the current token. It is called before every request, so it can hand back a fresh one:
import { createClient } from "@shipmantis/sdk";
import schema from "../mantis/schema";
export const mantis = createClient({
url: process.env.NEXT_PUBLIC_MANTIS_URL!,
key: process.env.NEXT_PUBLIC_MANTIS_PUBLISHABLE_KEY!,
schema,
userToken: () => window.Clerk?.session?.getToken(),
});init writes MANTIS_URL and MANTIS_PUBLISHABLE_KEY to .env.local. A browser bundle only
sees what your framework exposes to it, so in Next.js copy them to NEXT_PUBLIC_ names. Never
expose the secret key that way.
The token goes in the mantis-user-token header. Without one the caller is signed out, so only
public and where rules can allow it.
Changing rules
A rule change is a step in the plan like any other. Making a rule stricter applies at once. A rule
that lets more people in is held on main until a person approves it, because it can expose rows
that were private. So is a change to auth once the project has tables.