Permissions & RBAC
Nebutra's role-based access control model — 4 roles, 17 typed scopes, and how to enforce them.
Overview
Nebutra ships a production-ready RBAC (Role-Based Access Control) system out of the box. It includes:
- 4 built-in roles:
OWNER,ADMIN,MEMBER,VIEWER - 17 typed permission scopes across resources
- In-process enforcement via CASL (sub-millisecond)
- Zanzibar-scale enforcement via OpenFGA (optional, for complex policy graphs)
- React UI gates via the
<Can>component
Roles
| Role | Description |
|---|---|
OWNER | Full control — billing, deletion, all scopes |
ADMIN | Manage team, settings, API keys — all scopes except org deletion |
MEMBER | Core product access — create, read, update |
VIEWER | Read-only access |
Each tenant member has exactly one role. Roles cannot be combined.
Scope reference
| Scope | Description | OWNER | ADMIN | MEMBER | VIEWER |
|---|---|---|---|---|---|
org:read | Read org details | ✓ | ✓ | ✓ | ✓ |
org:update | Update org settings | ✓ | ✓ | — | — |
org:delete | Delete the organization | ✓ | — | — | — |
member:read | List org members | ✓ | ✓ | ✓ | ✓ |
member:invite | Invite new members | ✓ | ✓ | — | — |
member:remove | Remove a member | ✓ | ✓ | — | — |
member:update_role | Change a member's role | ✓ | ✓ | — | — |
api_key:create | Create API keys | ✓ | ✓ | — | — |
api_key:read | List API keys (masked) | ✓ | ✓ | ✓ | — |
api_key:delete | Revoke API keys | ✓ | ✓ | — | — |
project:create | Create new projects | ✓ | ✓ | ✓ | — |
project:read | Read project data | ✓ | ✓ | ✓ | ✓ |
project:update | Update project settings | ✓ | ✓ | ✓ | — |
project:delete | Delete a project | ✓ | ✓ | — | — |
billing:read | View billing info | ✓ | ✓ | — | — |
billing:update | Change plan / payment | ✓ | — | — | — |
audit_log:read | View audit log | ✓ | ✓ | — | — |
Enforcing permissions
API Gateway (Hono middleware)
import { requirePermission } from "@nebutra/permissions";
// Protects the route — returns 403 if the scope is missing
app.delete("/api/projects/:id", requirePermission("project:delete"), handler);Server Actions (Next.js)
"use server";
import { requirePermission } from "@nebutra/permissions";
export async function deleteProject(projectId: string) {
await requirePermission("project:delete"); // throws if unauthorized
// ...
}React UI gates
import { Can } from "@nebutra/permissions/react";
// Only renders children if the user has the scope
<Can action="delete" resource="Project">
<DeleteButton />
</Can>
// Inverse — show a fallback for users without the scope
<Can action="billing:update" fallback={<UpgradePrompt />}>
<BillingSettings />
</Can>Manual check
import { can } from "@nebutra/permissions";
const allowed = await can(userId, "project:create", { orgId });
if (!allowed) {
return new Response("Forbidden", { status: 403 });
}Custom scopes
You can extend the scope list for your own resources:
// packages/iam/permissions/src/scopes.ts
export const SCOPES = [
// built-in scopes...
"report:generate", // custom scope
"widget:embed", // custom scope
] as const;Then add them to the role matrix in the same file.
OpenFGA (optional)
For complex authorization scenarios — resource sharing, cross-tenant policies, or graph-based access — Nebutra supports OpenFGA as a drop-in replacement:
PERMISSIONS_PROVIDER=openfga
OPENFGA_API_URL=https://api.us1.fga.dev
OPENFGA_STORE_ID=your_store_id
OPENFGA_API_KEY=your_api_keyThe application code doesn't change — requirePermission() and <Can> work identically with both backends.
Related
How is this guide?
Edit on GitHub
Last updated on