0022. Authorisation and roles¶
| Status | Accepted |
| Date | 2026-10-10 |
| Deciders | Stuart Meeks |
Context¶
Access comes from memberships of accounts, and every request acts as one membership (ADR 0010). One central policy decides which records and which fields an actor sees, for responses, RQL queries and audit snapshots alike (ADR 0010, ADR 0016, ADR 0019). Operations users have no access to a business's data unless they add themselves to its account (ADR 0010). API clients belong to one account and hold scoped tokens (ADR 0021).
This record decides how permissions and roles work within that frame. In a small shop, people wear several hats: the owner sells, designs and quotes. Some data is sensitive within a business, not just between businesses: production staff may not need to see prices, and customers must never see costs or margins.
Decision¶
Permissions¶
- A permission is a named right to do one thing, by module and action, such as
sales.quotes.read,sales.quotes.send,production.jobs.moveorbilling.invoices.create. Modules match the API's modules (ADR 0020). - Permissions are defined in code, because each one needs code behind it to enforce it. The full list is kept in the authorisation design.
- Deny by default. Every endpoint and action declares the permission it needs. An automated test fails if any endpoint declares none.
- Permissions are worked out on the server for every request, from the membership's roles. They are never stored in the session cookie or in a token, so a role change takes effect on the next request.
Field groups¶
- Every field belongs to one field group, such as
pricing.cost(cost prices, margins, supplier prices),pricing.sell(sell prices and totals),customer.contactorinternal.notes. Fields with no particular sensitivity belong to the object's general group. - A permission such as
fields.pricing.cost.readgrants visibility of a group. A field the actor cannot see is omitted from responses, cannot be filtered, sorted or selected, and is stripped from audit snapshots. - Permission to change a field requires permission to see it.
Roles¶
- A role is a named set of permissions and field groups.
- Built-in roles only, for now. Signboard ships the roles below, and businesses cannot yet create their own. Custom roles are a later decision.
- A membership can hold several roles. Its permissions are the combination of all of them.
Built-in roles by account kind:
| Account kind | Role | In short |
|---|---|---|
| Business | Administrator | Everything, including settings, members, roles and API clients |
| Business | Sales | Customers, quotes and sales orders in full, including costs and margins; reads jobs and invoices |
| Business | Designer | Reads quotes and orders; works on design and proofs; no cost or margin |
| Business | Production | Reads and moves jobs through work centres; no prices |
| Business | Installer | Reads jobs and their install details, records install progress; no prices |
| Business | Accounts | Invoices, payments and accounting sync in full; reads customers, quotes and orders, including costs |
| Business | Signboard support | Given to an operations user who adds themselves to the account: reads everything, changes nothing |
| Customer | Customer administrator | Everything the customer account can see and do, including managing its users |
| Customer | Approver | Views, and approves or declines, quotes and proofs |
| Customer | Viewer | Views only |
| Operations | Platform administrator | Runs the platform, including operations memberships |
| Operations | Support | Supports businesses, including adding themselves to a business's account |
| Operations | Read-only | Views platform data only |
The exact permissions in each role are set out in the authorisation design. The table above is their intent.
Record scope¶
- Business staff see all of their business's records, limited by their permissions and field groups.
- Customer users see only their own customer account's records.
- Narrower scopes (for example, an installer seeing only jobs assigned to them) are a later decision.
Managing memberships¶
- Business administrators invite users to the business by email and assign their roles.
- Customer users are managed by both the business (staff with the right permission) and the customer's own administrators. The business can always override.
- An operations user who adds themselves to a business's account gets the Signboard support role. If they need to change something, either the business grants more, or the operations user changes their own role. A role change made by an operations user is notified to the business's administrators and audited, exactly like adding themselves (ADR 0010).
Guard rails¶
- A business always has at least one Administrator. Removing or demoting the last one is refused.
- Nobody can grant a permission or role they do not hold themselves.
- An API token's permissions cannot exceed those of the person who created it.
- Every change to memberships, roles and API tokens is audited (ADR 0019).
Deferred¶
- Custom roles defined by a business.
- Narrower record scopes.
- Limits and approvals, such as maximum discounts or approval thresholds. These are conditions on actions, to be decided with business rules.
Options considered¶
- Code-defined permissions, built-in roles, several roles per membership, field groups: simple to understand and test; covers a small shop with no configuration; leaves room for custom roles. Chosen.
- Custom roles from the start: maximum flexibility; needs a role editor and careful testing of arbitrary combinations before anyone needs them.
- One role per membership: simplest model; forces a custom role for anyone who does more than one job, which is most people in a small shop.
- Permissions per individual field: precise; hundreds of switches nobody can manage.
- Attribute-based policies (rules over arbitrary attributes): very expressive; hard to reason about, test and explain to a shop owner.
Consequences¶
- The authorisation design lists every permission and field group, and the exact contents of each built-in role. It must be written before the first endpoint.
- Every field in every object's design is assigned a field group.
- Every endpoint is tested for each built-in role: allowed, refused, and fields hidden (ADR 0012).
- Adding custom roles later needs only an editor and storage for role definitions, because roles are already sets of permissions.