Members & permissions
How CMS access is authorized — the cms permission catalog, per-site roles, and scoped invites.
Norcube CMS doesn't run its own access-control system. Every CMS action is authorized by Norcube Auth's IAM, against a resource that is either your whole organization or one specific site. This page is the authoritative list of what each permission gates, the roles you can hand out, and how you invite people to a single site.
How CMS access works
Authorization is delegated entirely to IAM. The CMS API carries no role logic of its own — it only declares, per route, which action it requires and which resource that action applies to. Two resource shapes exist:
| Resource ARN | Scope |
|---|---|
cms:org:<orgID> | Org-level actions (list / create sites) |
cms:site:<siteID> | Everything under one site |
Every authenticated request carries an access-token JWT minted by
Norcube Auth. The CMS rejects any token whose audience isn't
cms-api, even if the signature is valid — so a token scoped to
another product (say, Backup) can't be replayed against the CMS. The
token also carries your user id and organization id, which scope the
query underneath the permission check.
On each /sites/:id/... request the CMS asks IAM a single question:
"is this user allowed <action> on cms:site:<id>?" A denial is a
403; a missing or invalid identity is a 401. The check is
unconditional in production. There is no way to reach a mutating
endpoint without holding the matching permission.
The permission catalog
These are the exact cms:* actions. The server gates one action per
route; the names here match the IAM action ids verbatim.
Sites
| Action | Resource | Gates |
|---|---|---|
cms:ListSites | org | List the sites in your organization |
cms:CreateSite | org | Create a new site |
cms:GetSite | site | Read one site (and warm its clone) |
cms:UpdateSite | site | Rename / re-slug, connect git, sync |
cms:DeleteSite | site | Delete a site |
cms:GetSite also backs read-side helpers — the dashboard summary,
full-text search, clone warm-up, and per-user editor preferences.
cms:UpdateSite covers connecting the git repo and the manual "Pull
from git" sync.
Content
| Action | Resource | Gates |
|---|---|---|
cms:ListContent | site | Browse the content tree |
cms:GetContent | site | Read one file, its page history, field |
| history, diffs and point-in-time snapshots | ||
cms:UpdateContent | site | Write a file (Publish), and provision / |
| sync the live preview | ||
cms:DeleteContent | site | Delete a content file |
Drafts
| Action | Resource | Gates |
|---|---|---|
cms:GetDraft | site | Read the staging draft + the drafts index |
cms:UpdateDraft | site | Save / clear an in-progress editor draft |
Saving in the editor is a draft write (cms:UpdateDraft); Publish is
a content write (cms:UpdateContent).
Config & schema
| Action | Resource | Gates |
|---|---|---|
cms:GetConfig | site | Read the merged schema config |
cms:UpdateConfig | site | Write the single-file config; AI config |
| generation | ||
cms:GetSchema | site | Read a per-file schema (config.yml, a |
| collection, or a block) | ||
cms:UpdateSchema | site | Init / write / delete a schema file, and |
| AI block generation |
Media
| Action | Resource | Gates |
|---|---|---|
cms:ListMedia | site | List / search media, read declared |
| storages, mint signed preview URLs | ||
cms:UploadMedia | site | Upload a file into a storage |
cms:ManageMedia | site | Delete, rename / move, create folders |
AI
| Action | Resource | Gates |
|---|---|---|
cms:UseAI | site | The AI Assistant chat, apply / reject / |
| undo proposals, and the AI-generated site | ||
| profile |
Discussion
| Action | Resource | Gates |
|---|---|---|
cms:ListDiscussion | site | Read a page thread + unread counts |
cms:PostDiscussion | site | Post a message; mark a thread read |
cms:ManageDiscussion | site | Edit / delete a message |
Editing or deleting also requires you to be the message's original
author — that's an additional server-side check on top of
cms:ManageDiscussion.
Activity & members
| Action | Resource | Gates |
|---|---|---|
cms:ListActivity | site | The activity feed + live event stream |
cms:InviteMember | site | Delegate CMS access to a site |
Roles
Three per-site roles ship as system policies. They define an action set only — the site they apply to is bound per grant, at invite time.
| Role | Action set |
|---|---|
viewer | cms:Get*, cms:List* — read-only |
editor | viewer plus content, drafts, config, media, discussion and |
AI writes, and cms:UpdateSite. No schema edits; no create | |
| or delete of sites. | |
admin | cms:* — every CMS action on the site |
The editor set is exactly: cms:Get*, cms:List*,
cms:UpdateContent, cms:DeleteContent, cms:UpdateDraft,
cms:UpdateConfig, cms:UploadMedia, cms:ManageMedia,
cms:PostDiscussion, cms:ManageDiscussion, cms:UpdateSite,
cms:UseAI. Note editors can edit and delete content and update site
settings, but cannot touch schema or delete the site.
Above these sits the organization Owner policy (org:owner),
which allows every action on every resource across all Norcube
products — so an owner always has full CMS access to every site.
Roles aren't a fixed menu. A site invite grants an inline IAM policy,
so anyone with the org-level auth:CreatePolicy right can build a
custom policy (any subset of cms:* actions) and hand it out from the
global IAM pages. The three named roles are the common presets, not
the only options.
Inviting members to a site
The per-site Members page (CMS → your site → Members) lists
everyone in the org who holds a CMS policy, plus pending invitations.
The Invite button is gated on cms:InviteMember for that site —
if you can't delegate access, you don't see it.
An invite is scoped to one site. You pick an email, a role (viewer / editor / admin), and an optional note that lands in the email. Under the hood the invitation grants two statements:
cms:ListSiteson*— so the invitee's dashboard can load the site switcher. The list is still filtered server-side, so they only ever see the one site.- the role's actions scoped to
cms:site:<id>— the real grant.
Invitations are email-bound. The invitee gets a one-time accept link;
on accept they join your organization with access to only that site
and nothing else. The invite also carries product context (cms plus
the site name) so the email and accept page name the site instead of
a generic "join this org."
You can never hand out access you don't hold yourself. IAM enforces anti-escalation: every action and resource in a grant must already be allowed to you, and you must hold the delegation right for each service the grant touches. A CMS-only collaborator can't mint a grant for any other product.
How the UI reflects permissions
The dashboard hides or disables controls you can't use. It reads your
capabilities from Norcube Auth's self-scoped
GET /iam/me/permissions?resource=cms:site:<id> endpoint and gates
each control on the matching action:
| Control | Required action |
|---|---|
| New / edit page, Publish | cms:UpdateContent |
| Delete page | cms:DeleteContent |
| Save draft | cms:UpdateDraft |
| Edit schema | cms:UpdateSchema |
| Upload media | cms:UploadMedia |
| Delete / rename media | cms:ManageMedia |
| Delete site (settings) | cms:DeleteSite |
| Edit site settings | cms:UpdateSite |
| Config generation / AI | cms:UpdateConfig / |
cms:UseAI | |
| Invite to site | cms:InviteMember |
This is defence-in-UX, not the gate. The capability read never widens
access — it only mirrors what the server would allow. The CMS API
authorizes every request independently, so a hidden button being
forced open still gets a 403. While capabilities are still loading
the UI is optimistic (controls show) to avoid flicker for owners and
admins; the server remains the real guard.
What's not there yet
- No role management surface on the member row. Changing someone's level means detaching the policy and re-inviting, or editing the IAM policy directly. There's no inline "promote to admin" control.
- No field- or collection-level permissions. The finest grain is one site. Within a site, access is all-or-nothing per action — you can't scope someone to a single collection or folder.
- No per-site audit log in the CMS itself. The Activity feed
(
cms:ListActivity) shows what changed, not an IAM access trail. IAM audit logs live in Norcube Auth.