Repository navigation
feat: add permission sets - #1861
Merged
joshunrau merged 5 commits intoNov 4, 2026
Merged
Conversation
A permission set is a named list of rules an administrator defines once and links users to,
instead of rebuilding the same additional permissions for each user. Each rule applies to every
group or to the holder's groups, resolved at sign-in like a group manager's own rules. A set is
linked, not copied: editing it changes every holder from their next sign-in, and deleting it
removes it from its holders.
A set and custom additional permissions are mutually exclusive. PUT /v1/users/:id/permissions
now takes { kind: 'permissionSet', permissionSetId } or { kind: 'custom', permissions }, and each
write empties the other. A set may not grant manage all, an administrator never holds one, and
every /v1/permission-sets route is admin-only, since editing a set is a grant to each holder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the admin pages to list, create, edit and delete permission sets, with the holder count stated before a delete. The user page's permissions card switches between a permission set and custom permissions, warning when choosing a set discards saved custom grants, and the create-user form can link a set to any user below administrator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `$CreatePermissionSetData` derives its name from `$PermissionSet` rather than repeating it, as `packages/schemas/AGENTS.md` asks of every variant. - `AbilityFactory` grants a permission set rule and a per-user permission through one `grantWithin`, so the branch that confines a rule to groups exists once. The error for a stored rule no group can own now lists the groups it was confined to. - The permission sets list clips a long name to one line with the full name on hover, and no longer passes a `data-testid` that libui's `DataTable` drops. - The two amber warnings in the user permissions card share one `WarningCallout`. - `useUpdateUserMutation` takes `permissionsUpdate`, ending `permissions.permissions` at its call site, and `withoutPermission` is `withoutIndex`, which both editors now also use to drop a draft. - The e2e helper reading a holder's access reads ids alone, without a bare `T` generic. - The auth architecture doc says a user with no base level gets their permission set's rules too, `packages/schemas/AGENTS.md` lists `isPermissionSetGrantable` among its security-relevant exports, and `apps/web/AGENTS.md` no longer claims every table is a `DataTable`. Co-Authored-By: Claude Opus 5.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A permission set is a named list of rules an administrator defines once and links users to, instead of rebuilding the same additional permissions for each user.
What changed
apps/api/src/permission-sets/): CRUD under/v1/permission-sets, every routeADMIN_ONLY, since editing a set is a grant to each holder. A rule applies to every group (ALL_GROUPS) or to the holder's groups (HOLDER_GROUPS), resolved at sign-in through the sameGROUP_SCOPED_CONDITIONSas a per-user grant. A set is linked, not copied: an edit reaches every holder at their next sign-in, and deleting a set clears it from its holders (onDelete: SetNull).PUT /v1/users/:id/permissionsnow takes{ kind: 'permissionSet', permissionSetId }or{ kind: 'custom', permissions }, and each write empties the other. A set may not grantmanage all(isPermissionSetGrantable), and an administrator never holds one.apps/api/AGENTS.md,packages/schemas/AGENTS.md,.agents/docs/architecture/auth-and-permissions.mdand the security reference describe sets.The last commit addresses a standards review of the first four: a derived create schema, one shared grant helper in
AbilityFactory, a clipped name column in the list, a shared warning callout, and clearer names.Verification
pnpm lintandpnpm test:coverageare green, at 100% on every metric.permission-sets.service.test.ts,permission-sets.controller.test.ts,ability.factory.test.tsandusers.service.test.tsinapps/api;permission-set.test.tsanduser.test.tsinpackages/schemas; and thePermissionSetRulesEditor,PermissionSetForm,UserPermissionsEditor, hook andadmin/permission-setsroute tests inapps/web.apps/api/test/suites/04-permission-sets.suite.ts.testing/src/specs/permission-sets.spec.ts.Not verified locally:
pnpm test:e2edid not run after the review commit, because a local dev server held port 5500 and Playwright refused to start beside it. CI runs the suite on this PR.Co-Authored-By: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com