$can and the policy
Who may read or change a row is decided by rules the app ships in a migration. The page never
checks an owner or a role itself: it reads, writes and asks
$can, and the rules answer. Three notes were seeded by the
platform: one published, one embargoed, one draft. Add your own and see which ones each rule
lets you see and change.
Signed out, you read the published note and nothing else, and every write is refused. Log in or create an account to own notes of your own.
-
Release notes (the platform seeded this, and published it)published
// migrations/050-g2-security-policy.json (excerpt)
{ "op": "setPolicy", "channel": "security-tour", "rules": [
"create(SecNote, User) :- has(User.email)",
"read(SecNote, u) :- _owner(SecNote, u) || eq(SecNote.state, \"published\")",
"deny_read(SecNote, u) :- eq(SecNote.state, \"embargoed\")",
"delete(SecNote, User) :- _owner(SecNote, User)",
"publish(SecNote, User) :- _owner(SecNote, User)"
] }
// A rule reads: verb(Target, Actor) :- condition
// Nothing is allowed until a rule allows it, and a deny_ rule always wins.
Buttons only for what you may do
The Publish and Delete buttons carry
x-can: where the answer is no, the button is not in the page
at all. $can(verb, target)
asks the same question from code.
<button x-can="publish" x-can-on="n.key" @click="$publish(n.key)">Publish</button>
<button x-can="delete" x-can-on="n.key" @click="$remove(n.key)">Delete</button>
// publish is a verb the app made up: the store never checks it, so the
// method asks before it acts.
async $publish(key) {
if (!(await this.$can('publish', key))) return
await this.$data.update(key, { state: 'published' })
}
A hidden button is not the protection. The seeded note has no Delete button for you; call the delete anyway and the store refuses it.