Skip to content

feat: add permission sets - #1861

Merged
joshunrau merged 5 commits into
DouglasNeuroInformatics:mainfrom
joshunrau:feat/permission-sets
Nov 4, 2026
Merged

joshunrau merged 5 commits into
DouglasNeuroInformatics:mainfrom
joshunrau:feat/permission-sets

Conversation

@joshunrau

Copy link
Copy Markdown
Collaborator

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

  • API (apps/api/src/permission-sets/): CRUD under /v1/permission-sets, every route ADMIN_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 same GROUP_SCOPED_CONDITIONS as 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).
  • Mutually exclusive with custom grants. 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 (isPermissionSetGrantable), and an administrator never holds one.
  • Web: admin pages to list, create, edit and delete sets, with the holder count stated before a delete. The user page's permissions card switches between a set and custom permissions, warning when choosing a set discards saved custom grants. The create-user form can link a set to any user below administrator.
  • Docs: apps/api/AGENTS.md, packages/schemas/AGENTS.md, .agents/docs/architecture/auth-and-permissions.md and 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 lint and pnpm test:coverage are green, at 100% on every metric.
  • Unit tests: permission-sets.service.test.ts, permission-sets.controller.test.ts, ability.factory.test.ts and users.service.test.ts in apps/api; permission-set.test.ts and user.test.ts in packages/schemas; and the PermissionSetRulesEditor, PermissionSetForm, UserPermissionsEditor, hook and admin/permission-sets route tests in apps/web.
  • Integration: apps/api/test/suites/04-permission-sets.suite.ts.
  • End to end: testing/src/specs/permission-sets.spec.ts.

Not verified locally: pnpm test:e2e did 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

joshunrau and others added 5 commits November 4, 2026 11:29
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
@joshunrau
joshunrau merged commit 91dd2b9 into DouglasNeuroInformatics:main Nov 4, 2026
2 checks passed
@joshunrau
joshunrau deleted the feat/permission-sets branch November 4, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant