What you are not granted does not exist.
Most access control puts a check in front of each thing, and every check is a place to forget one. In Nisc a screen is a whole unit of data: what you see, what you can press, what it calls. So Charter decides which screens your app is built from. Anything the charter does not mention was never put in, and there is nothing left to guard.
pnpm add @niscorp/charter · 0.1.4 · Apache-2.0
One charter, and the app each person is given
The charter: every role there is
{
guest: {
actions: ['welcome', 'programme'],
data: ['shows.read'],
},
member: {
actions: ['programme', 'seats', 'me.*'],
data: [
'shows.read',
'bookings.*',
'passes.read',
'members.read',
],
scoping: 'personal',
},
usher: {
actions: ['programme', 'door.*'],
data: ['shows.read', 'bookings.read', 'members.read'],
},
manager: {
extends: ['usher'],
actions: { allow: ['*'], deny: ['welcome', 'me.*'] },
data: ['*.read'],
},
}
5 of the app’s screens are not in it
shows.read bookings.read bookings.write passes.read members.read
personal
A member. me.* is every screen whose id starts with me., and bookings.* is every grant on that table. scoping says how far her reads go: her own rows.
Asked to open takings, which they were not given, their app answers:
There is no check in front of that screen that could be skipped, because the screen is not there. This page builds each app the way Moss does on a server: a shell that holds the granted screens and nothing else. The sentence in orange is that shell’s own.
First steps →Decided once, where the app is put together.
When you sign in, your roles are resolved to a list of screens, and the server builds your app from that list. Nothing checks you again on the way to a screen, because a screen you were not given is not there to reach. The default is no: what no role mentions, nobody gets.
The same document decides what the database answers.
A role also lists what it may do with each table. Vex is handed that list as its policy for you, and a table your roles do not mention has no rule to be read by. Here one stored read, who is coming on Saturday, is asked as each of four people, against Postgres in this page.
Postgres is starting in this page and loading the theatre’s bookings. It is the real database, compiled to run in a browser, so the first time takes a few seconds. Nothing is sent to a server.
A wrong charter does not start.
Before the server starts, a verifier reads the charter against the screens and tables that really exist. A deny that matches nothing is an error. A typo there leaves something unprotected, and nothing else would ever say so. Moss refuses to start on any error.
The role that was changed
{
member: {
actions: {
allow: ['*'],
deny: ['welcome', 'door.*', 'members', 'takings', 'settngs'],
},
data: [
'shows.read',
'bookings.*',
'passes.read',
'members.read',
],
scoping: 'personal',
},
}
Somebody rewrote the member role as every screen minus the staff ones, and mistyped settings. Without the verifier every member would get the settings screen, and nothing would fail.
Charter decides. Moss and Vex enforce.
Charter resolves names and checks the document. It does not run while a request is served.
It only knows names.
Charter never learns what a screen or a table is. It is handed a list of names and a role, and answers with the names that role selects. That is why screens, table grants, layout variants and the functions a screen calls all go through one function. It is also how it grows: to govern something new, whatever enforces it lists its names and takes the answer back. The grammar of a role does not change. What can be governed this way is anything that is set up as data and treats a missing entry as no.
handed in welcome programme seats me.tickets me.pass door.list members takings settings the role member: ['programme', 'seats', 'me.*'] comes back programme seats me.tickets me.pass
handed in shows.read bookings.read bookings.write passes.read members.read the role manager: ['*.read'] comes back shows.read bookings.read passes.read members.read
handed in bookings.find bookings.cancel shows.find refunds.issue the role desk: ['bookings.*', 'shows.find'] comes back bookings.find bookings.cancel shows.find