Permissions, flags and failures Recommended
Let the server say what each user may do
Send the actions the user may take on each record with the record, and show controls from that list. Never rebuild the permission rules in the client.
How to do it
Add a field such as "allowedActions": ["edit", "send"] to each record, computed by the same code that enforces access. The screen shows a control only when its action is listed. Hide actions the user can never take. Show actions they could take after a change, such as a plan upgrade, with the reason.
Example
def invoice_json(invoice: Invoice, user: User) -> dict:
return {
"id": invoice.id,
"number": invoice.number,
"status": invoice.status,
"allowedActions": [action for action in ("edit", "send", "delete") if policy.allows(user, action, invoice)],
}
export function InvoiceActions({ invoice }: { invoice: Invoice }) {
const can = (action: InvoiceAction) => invoice.allowedActions.includes(action);
return (
<div className="invoice-actions">
{can('edit') && <LinkButton href={`/invoices/${invoice.id}/edit`}>Edit</LinkButton>}
{can('send') && <SendButton invoice={invoice} />}
{can('delete') && <DeleteButton invoice={invoice} />}
</div>
);
}
How to check
Search the client for role names, such as 'admin' or role ===. There are no matches outside the sign-in code.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/capabilities-from-server.