Wiki
Permissions

Overview

The two-layer authorization model, and where each decision is made.

Authorization in Wiki has two independent layers. Understanding which layer governs a given action is the key to the whole model.

The split, precisely

ActionGoverned by
Create a space, manage members, manage rolesOrganization RBAC
Read / write pages, comments, attachments, tagsSpace & page access

Content actions (page, comment, attachment) are not gated by org RBAC page:* / comment:* statements — those are governed by the space and page layer. Org RBAC governs organization management.

Always enforced server-side

Both layers are enforced on the server. The frontend only mirrors them to gate UI — never rely on the client for enforcement, because the server always re-checks.

Everything is org-scoped

A permission check always resolves against a specific organization's member row. With no active organization, checks fail — there is nothing to resolve against. Switch the active org with authClient.organization.setActive(...).

Where it lives in the code

FileContents
packages/auth/src/permissions.tsThe statement, the ac engine, and static roles. Single source of truth, server-free.
packages/api/src/index.tsBackend guards: requireOrgPermission, assertOrgPermission, hasOrgPermission.
packages/api/src/lib/access.tsPure space/page role resolvers (unit-tested, auth-free).
packages/api/src/lib/authz.tsRouter-facing space/page gates that fold in the org-manager override.
apps/web/src/lib/use-permissions.tsFrontend usePermission hook and static checks.

Keep permissions.ts server-free

permissions.ts is imported by both the server and the browser bundle. Never import the database, env, or server code into it, or the database gets pulled into the client build.

On this page