The role hierarchy: owner > admin > user
Every account has one coarse role:
- user — a normal account; access is governed entirely by topic ACLs (its own grants, group grants and the Everyone grant).
- admin — bypasses every access-control check and can open the Site Administration area.
- owner — the single highest account. An owner is a superset of admin (also bypasses every check) and additionally holds the owner-only powers, chiefly role management.
Both admin and owner bypass capability checks, so custom roles (below) only ever gate delegated, non-admin access.
How the owner role is minted
Owner is deliberately hard to obtain — it cannot be set like a normal role. The ordinary role-setting paths (creating a user, changing a role in the admin UI or via ntfy user change-role) accept only user or admin; owner is rejected there.
- Bootstrap (one time). On a server with no owner yet, an existing admin is promoted with the CLI-only
ntfy user set-owner USERNAME. This succeeds only while no owner exists and only if the target is already an admin. - Transfer (after that). Once an owner exists, ownership moves only through the ownership-transfer flow (
POST /v1/account/owner/transfer), which hands the owner role to another admin. Trying to bootstrap again is rejected because an owner already exists.
Last-owner protection
There is always exactly one owner. The last owner cannot be demoted and cannot be deleted — both are blocked at the server. To step down, an owner transfers ownership to another admin first; the transfer itself performs the swap.
Custom roles (RBAC)
Beside the coarse role, an owner can define custom roles — named bundles of capabilities — and assign them to principals to delegate a slice of admin power without granting full admin.
- Capabilities are either system-wide (empty scope) or topic-scoped (a topic name or
*pattern, matched with the same wildcard rule as topic ACLs). System capabilities includeusers.manage,acl.manage,groups.manage,branding.manage,tours.manage,approvals.manage,server.view,topics.acl.viewandtopics.protect.manage. Topic-scoped ones aretopic.acl.manage,topic.protect.manageandtopic.acl.view— e.g. authority overteam-*only. - Assignment targets a user or a whole group (in the
@g:<group>form), so a role can be handed to every member of a group at once. - Role management is owner-only. Role CRUD and assignment are the anti-self-escalation guard, so only the owner sees the Roles card and can use it (or the
ntfy roleCLI). There is no delegable "manage roles" capability — it stays with the owner.
See MokoNtfy CLI — groups, roles & protected topics for the exact ntfy role syntax.
How it composes with groups and protected topics
For a user (admin/owner bypass all of this), a request on a topic is resolved by the real authorization engine in this shape:
- Groups are additive. A group grant applies to every member; a user's effective topic permission is the union of their groups' grants. Group grants are read/write/read-write only — a group deny is not allowed.
- Per-user grants win. A user's own explicit grant (including a per-user
deny) always takes precedence over any group grant — that's how you subtract access that a group would otherwise give. - Protected topics require an explicit grant. A topic marked protected is no longer reachable through the fall-through Everyone grant or the server's default access; a matching user or group grant is required. Owners, admins and the topic's reservation owner still retain access.
- Custom roles don't grant topic publish/subscribe access; they grant management capabilities (e.g. the ability to manage a topic's ACL or protect it). Access to send/receive on a topic still comes from the ACL grants above.
The admin Access preview resolves all of this for you: pick a user (or an anonymous visitor) and a topic and it reports read/write allowed or denied using the same engine.
