Feat/rbac page scoped permissions#61
Merged
ilramdhan merged 12 commits intoJul 1, 2026
Merged
Conversation
This reverts commit 2ecb911.
…d menu_id (Tasks 8, 11) Task 8: ListPermissionsParams gains menuId; BFF list route forwards menu_id. Task 11: description now required (zod min 1) with helper copy; new searchable MenuCombobox (Popover+Command, no raw UUID, 'Global / none' option) sets menu_id on create + update. tsc clean; eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
groupPermissionsByMenu pure converter (5 vitest cases: grouping, global-last, first-seen order, whitespace-menuId, title fallback). PermissionPicker renders permissions grouped by page with per-page select-all (indeterminate), per-row name+action+description, and read-only 'from role' inherited rows for reuse in the direct user-permission dialog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (Task 10) Replaces the service->module->entity tree with the shared PermissionPicker fed by the full permission catalog (usePermissions pageSize 500). Permissions now group by owning page with per-page select-all and per-row descriptions; diff-based save (assign/remove) unchanged. Directly addresses the 'grouped by page' pain point. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds 'Manage Permissions' user action opening a page-grouped picker (reuses PermissionPicker). Direct grants are toggleable; role-inherited permissions show checked + read-only 'from role'. New BFF routes users/[id]/permissions(+/remove), useAssignUserPermissions/useRemoveUserPermissions hooks (backend already supported this). Resolves role-sprawl: grant one permission without a throwaway role. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New /administrator/permissions/catalog route: all permissions grouped by owning
page (via groupPermissionsByMenu) with name, code, action, description, and role
count — answers 'what permissions does this page have and what do they do' without
reading code. Searchable across page/name/code/description. Linked from the
permission management page header ('View catalog').
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
src/lib/rbac/permissions.ts: all 279 permission codes grouped by owning page (generated from the RBAC audit / spec Appendix A), so components import constants instead of scattered string literals. Refactors product-requests-page-client to use PERMISSIONS.ProductRequests.requestCreate as the reference pattern; remaining call sites can adopt incrementally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ListPermissions and ListMenus cap page_size at 100 (buf.validate lte:100), so the
pickers' pageSize:500 and MenuCombobox's 200 were rejected -> empty responses
('No permissions available', menu combobox only 'Global/none'). Add useAllPermissions()
backed by GetPermissionsByService (no pagination, already returns menu_id) and use it
in the role dialog, user-permission dialog, and catalog; cap MenuCombobox at 100.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…og grouping MenuCombobox passed sortBy 'sortOrder' but ListMenus only allows snake_case 'sort_order' (proto in-list) -> request rejected -> only 'Global/none' showed. Fixed to 'sort_order'. Also rebuilt MenuPermissionDialog (menu visibility gating) to use useAllPermissions + shared PermissionPicker: was capped at pageSize 100 (truncated to first 100 of 279 perms) and ungrouped; now full + grouped by page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
roles[].permissions from the backend is empty, so the previous readOnlyIds computation never marked anything. Now compute role-inherited = allPermissionCodes (effective) minus direct-grant codes, mapped to catalog IDs. Role-inherited permissions show checked + read-only 'from role', so an admin cannot redundantly grant a permission the user already has via a role; direct selection is the true direct grants only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds page-scoped RBAC permission metadata (menuId/menuTitle) and introduces shared UI tooling to browse and assign permissions by owning page, including new direct user-permission assignment flows and a permission catalog view.
Changes:
- Extend IAM permission types/requests to carry
menuIdandmenuTitle, and addmenuIdfiltering support. - Introduce shared permission grouping + selection components (grouping helper, picker UI, and menu combobox) and reuse them across role/menu/user assignment dialogs.
- Add direct user-permission assignment hooks + API routes, plus an admin “Permission Catalog” page and a centralized
PERMISSIONSconstants map.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/iam/user.ts | Re-export generated request/response types/parsers for direct user-permission assignment. |
| src/types/iam/role.ts | Add menuId to list params to support page-scoped permission filtering. |
| src/types/generated/iam/v1/role.ts | Generated updates adding menuId/menuTitle fields and request support. |
| src/lib/rbac/permissions.ts | New generated permission-code constant map grouped by page. |
| src/lib/rbac/group-permissions.ts | New utility to group permissions by owning menu/page (global bucket last). |
| src/hooks/iam/use-users.ts | Add mutations for assigning/removing direct permissions for users. |
| src/hooks/iam/use-permissions.ts | Add useAllPermissions() to flatten full permission catalog from by-service endpoint. |
| src/components/settings/users/user-table.tsx | Add optional “Manage Permissions” row action. |
| src/components/settings/users/user-permission-dialog.tsx | New dialog to manage direct permissions on a user, with role-inherited read-only selections. |
| src/components/settings/users/index.ts | Export UserPermissionDialog. |
| src/components/settings/roles/role-permissions-dialog.tsx | Refactor to use PermissionPicker + full permissions catalog. |
| src/components/settings/rbac/permission-picker.tsx | New shared grouped permission selector UI (collapsible groups + select-all). |
| src/components/settings/rbac/menu-combobox.tsx | New menu/page selector for scoping a permission to a menu. |
| src/components/settings/permissions/permission-form-dialog.tsx | Make description required and add page/menu association via MenuCombobox. |
| src/components/iam/menus/menu-permission-dialog.tsx | Refactor to use PermissionPicker and useAllPermissions(). |
| src/app/api/v1/iam/users/[userId]/permissions/route.ts | New API route to assign direct permissions to a user. |
| src/app/api/v1/iam/users/[userId]/permissions/remove/route.ts | New API route to remove direct permissions from a user. |
| src/app/api/v1/iam/permissions/route.ts | Add query parsing for menuId filter. |
| src/app/(dashboard)/finance/product-requests/product-requests-page-client.tsx | Switch hardcoded permission string to PERMISSIONS constant. |
| src/app/(dashboard)/administrator/users/users-page-client.tsx | Wire up user permission dialog and handler. |
| src/app/(dashboard)/administrator/permissions/permissions-page-client.tsx | Add link to the new permissions catalog page. |
| src/app/(dashboard)/administrator/permissions/catalog/permission-catalog-client.tsx | New searchable catalog browsing permissions grouped by page. |
| src/app/(dashboard)/administrator/permissions/catalog/page.tsx | New page wrapper for the catalog. |
| src/app/(dashboard)/administrator/permissions/catalog/loading.tsx | New loading skeleton for the catalog route. |
| src/tests/rbac/group-permissions.test.ts | Add unit tests for permission grouping behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+112
to
+115
| <span className="flex items-center gap-2 text-xs text-muted-foreground"> | ||
| {selectedCount}/{toggleableIds.length} | ||
| {open ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />} | ||
| </span> |
Comment on lines
+25
to
+26
| // GLOBAL_OPTION represents a permission that is not scoped to any single page. | ||
| const GLOBAL_VALUE = "__global__" |
Comment on lines
+49
to
53
| if (open) { | ||
| // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: sync selection state when dialog opens | ||
| setSelected(new Set(currentPermIds)) | ||
| } | ||
| }, [open, JSON.stringify(currentPermIds)]) // eslint-disable-line react-hooks/exhaustive-deps |
Comment on lines
+36
to
+40
| Dashboard: { | ||
| dashboardView: "ci.module.dashboard.view", | ||
| }, | ||
| // Dashboard (EXSIM_DASHBOARD) | ||
| Dashboard1: { |
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.
Description
Type of Change
Module/Component Affected
Changes Made
Related Issues
Fixes #
Related to #
Screenshots
Before
After
Testing Performed
Manual Testing
Browser Testing
Build Verification
npm run lintpassesnpx tsc --noEmitpassesnpm run buildsucceedsAccessibility
Performance
Pre-merge Checklist
Reviewer Notes