[codex] Add app-based permission management - #378
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive "Permissions by App" feature, allowing administrators to manage permissions grouped by application. It includes a local audit script to scan for permission usages, new reusable UI components (cards and modals), a dedicated AppPermissions.vue view, and a new AppPermissionService for managing assignments. The feedback identifies several critical issues and improvements: a bug where .length is checked on an object dictionary (allPermissions), potential TypeError risks due to missing optional chaining or guard checks on currentGroup, a potential race condition in Vuex state updates, and a Windows compatibility issue in the audit script's path resolution.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if(this.securityGroupUsers[this.currentGroup.groupId]) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
| async loadGroupPermissions() { | ||
| await this.store.dispatch('util/getSecurityGroups'); | ||
| await this.store.dispatch('util/getClassificationSecurityGroups'); | ||
| if(!this.allPermissions.length) await this.store.dispatch('permission/getAllPermissions'); |
There was a problem hiding this comment.
Since allPermissions is a dictionary/object (as evidenced by this.allPermissions?.[permissionId] and Object.values(this.allPermissions) in PermissionItems.vue), checking !this.allPermissions.length will always evaluate to true because objects do not have a length property. This results in redundant API calls to fetch all permissions every time loadGroupPermissions is executed. Use Object.keys(this.allPermissions || {}).length instead.
if(!Object.keys(this.allPermissions || {}).length) await this.store.dispatch('permission/getAllPermissions');
| import { promises as fs } from "node:fs"; | ||
| import path from "node:path"; | ||
| import process from "node:process"; | ||
|
|
||
| // Local one-time audit tool only. Runtime code must use committed catalog modules, not repo scans. | ||
| const repoRoot = path.resolve(new URL("..", import.meta.url).pathname); |
There was a problem hiding this comment.
Using new URL(..., import.meta.url).pathname to resolve file paths can fail on Windows platforms because it retains URL-encoded characters and can prepend an extra leading slash (e.g., /C:/...). Use fileURLToPath from the node:url module to safely convert file URLs to native system paths.
| import { promises as fs } from "node:fs"; | |
| import path from "node:path"; | |
| import process from "node:process"; | |
| // Local one-time audit tool only. Runtime code must use committed catalog modules, not repo scans. | |
| const repoRoot = path.resolve(new URL("..", import.meta.url).pathname); | |
| import { promises as fs } from "node:fs"; | |
| import path from "node:path"; | |
| import process from "node:process"; | |
| import { fileURLToPath } from "node:url"; | |
| // Local one-time audit tool only. Runtime code must use committed catalog modules, not repo scans. | |
| const repoRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url))); |
| }, | ||
| methods: { | ||
| createGroup() { | ||
| this.$router.push({ path: `/create-security-group/` }); |
| await this.store.dispatch('util/getClassificationSecurityGroups'); | ||
| if(!this.allPermissions.length) await this.store.dispatch('permission/getAllPermissions'); | ||
| if(!Object.keys(this.permissionsByClassificationGroups).length) await this.store.dispatch('permission/getPermissionsByClassificationGroups'); | ||
| if(this.currentGroup.groupId) { |
| emitter.emit('presentLoader'); | ||
| await this.store.dispatch('permission/updateCurrentGroup', group); |
There was a problem hiding this comment.
Instead of relying on the Vuex store to synchronously update this.currentGroup before calling getPermissionsByGroup, use group.groupId directly from the function parameter. This avoids potential race conditions or stale state issues.
await this.store.dispatch('permission/updateCurrentGroup', group);
await this.store.dispatch('permission/getPermissionsByGroup', group.groupId);
Business summary
This PR gives Users admins an app-first way to understand and manage permissions. Instead of asking users to interpret abstract permission categories, the experience shows each app, the actions available in that app, and which security groups have those permissions.
Closes #377
What changed
By appandBy groupsegments./tabs/permissionsroute to the group segment.Other permissions.Validation
npm run buildNotes
The temporary local Moqui login commit was intentionally left out of this clean PR branch.