Rules
A rule is a filter that names the caller. It is compiled into the SQL that reads or writes the rows, so a row somebody may not see never leaves SQLite.
Where rules go
Each collection has five, one per action. Leave one out and only superusers may do it; "" lets anybody.
"rules": {
"list": "@request.auth.id != null",
"view": "@request.auth.id != null",
"create": "author = @request.auth.id",
"update": "author = @request.auth.id",
"delete": "author = @request.auth.id || @request.auth.staff = true"
}A field can have two of its own: visible, who may read it, and writable, who may set it. Both default to whoever the collection already let in.
{ "name": "mark", "type": "number",
"visible": "@request.auth.id = pupil || @request.auth.staff = true",
"writable": "@request.auth.roles ~ \"examiner\"" }The language
The same one filters use:
| Compare | = != > >= < <= |
| Contains, or doesn’t | ~ !~ |
| Combine | && || and parentheses |
| Values | "text", numbers, true, false, null |
| The record | its field names: author, org, status |
| The caller | @request.auth.… |
| Moments | @now, @days_ago.30, @years_ago.13 |
What a rule can say about the caller
@request.auth.id,collection,emailandsuperusercome from the token and cost nothing.- Any other field of the caller’s own record works too:
@request.auth.staff,@request.auth.org. Sluurp reads that record only when a rule asks, by its id, in the same transaction. @request.auth.rolesis a set.@request.auth.roles ~ "admin"asks whether “admin” is one of them, so it never matches “superadmin”.- For somebody who isn’t signed in, every one of these is
null. A rule written for members is simply false for them, never an error.
@request.auth.staff = true && org = @request.auth.org && id != @request.auth.idStaff may delete people in their own organisation, but not themselves.
How they are enforced
- Lists filter. A caller gets the rows they may see, and the counts are right.
- View, update and delete answer 404 when the rule says no, so a rule can’t be used to find out that a row exists.
- Writes are checked twice. The row is checked before the change and again inside the transaction after it. A change that would put a row out of the writer’s own reach is rolled back.
- A rule about the caller alone is settled before the query.
@request.auth.roles ~ "admin"becomes true or false and the query never sees it. A rule about the record becomes part of the query’sWHERE. - Field rules too. A field that nobody in the caller’s position may see is not selected. One that depends on the row is blanked row by row, in SQL.
Asking before trying
GET /api/acl answers, for the signed-in caller, what each collection lets them do: allow, deny, or conditional (they may, for some rows, such as their own). A screen uses it to show only the buttons that can work. It is the same rules asked in advance, not a second set, so the two can’t disagree.