Audit role permission changes (Task #100)
Record an audit trail every time a role's permissions change and surface recent history inside the role edit dialog. Schema (lib/db): - New `role_permission_audit` table: id, roleId (FK roles), actorUserId (FK users, nullable on delete), previousPermissionIds int[], newPermissionIds int[], createdAt. Created via raw SQL because `drizzle push` currently fails on a pre-existing app_permissions duplicate (followup #148 tracks fixing this). API (artifacts/api-server): - PUT /api/roles/:id/permissions now wraps the permission delete/insert, the legacy audit_logs row, and the new role_permission_audit row in a *single* transaction so the permission set and its audit trail always commit (or roll back) together. Previous IDs are also read inside the transaction to avoid races. - A role_permission_audit row is written on EVERY PUT call (per task spec), including no-op saves; the GET handler computes addedPermissionIds/removedPermissionIds so the History UI can distinguish meaningful changes from no-op saves. - New GET /api/roles/:id/audit (admin-only, ?limit=1..50, default 10) returns recent entries newest-first with computed added/removedPermissionIds and joined actor info. - New tests in tests/role-permission-audit.test.mjs cover write, every-call write (incl. no-op), GET ordering+diff+actor, no-op diff reporting, 404, and limit. Drive-by fixes: - Fixed pre-existing syntax corruption in tests/apps-open.test.mjs (stray `ccaPassa,` line from a prior bad merge that prevented the whole api-server test workflow from running). - Made tests/roles-crud.test.mjs "rejects an invalid name" robust to parallel test execution by scoping the leak check to the bad name instead of relying on a global row count. API spec / codegen (lib/api-spec, lib/api-client-react): - Added `getRolePermissionAudit` operation and `RolePermissionAuditEntry` schema; ran orval codegen. Frontend (artifacts/tx-os): - New RolePermissionHistory component renders inside the role edit dialog (between permissions and Save/Cancel) using useGetRolePermissionAudit. Shows timestamp, actor, added/removed permission names (resolved via permissionsById), and total count. - Save invalidates the audit query so the new entry appears immediately on the next open. - Bilingual i18n strings (en + ar with full Arabic plural variants: zero/one/two/few/many/other) under admin.roles.history*. Verification: - tx-os typecheck passes. - All 5 new audit tests + 13 existing roles tests pass. - E2E (testing skill) verified the full flow: empty state on a fresh role, save adds a permission, reopened dialog shows the new entry with actor name in Arabic. Docs: - replit.md updated to list `role_permission_audit` in Database Tables. Follow-ups proposed: #146 paginate/filter history, #147 audit other admin permission changes, #148 fix drizzle push duplicate. Replit-Task-Id: 9b8886a2-6e76-4072-b345-a772421fa161
This commit is contained in:
@@ -1450,6 +1450,45 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/roles/{id}/audit:
|
||||
get:
|
||||
operationId: getRolePermissionAudit
|
||||
tags: [roles]
|
||||
summary: List recent permission-change audit entries for a role (admin)
|
||||
description: |
|
||||
Returns the most recent permission-change audit records for the given
|
||||
role, newest first. Each record captures the actor (if known), the
|
||||
previous permission IDs, the new permission IDs, and the timestamp.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: limit
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
default: 10
|
||||
responses:
|
||||
"200":
|
||||
description: Recent permission-change audit entries for the role
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/RolePermissionAuditEntry"
|
||||
"404":
|
||||
description: Role not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/roles/{id}/permissions/impact-preview:
|
||||
post:
|
||||
operationId: previewRolePermissionsImpact
|
||||
@@ -3579,6 +3618,46 @@ components:
|
||||
- id
|
||||
- username
|
||||
|
||||
RolePermissionAuditEntry:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
roleId:
|
||||
type: integer
|
||||
previousPermissionIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
newPermissionIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
addedPermissionIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
removedPermissionIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
actor:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/AuditLogActor"
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- roleId
|
||||
- previousPermissionIds
|
||||
- newPermissionIds
|
||||
- addedPermissionIds
|
||||
- removedPermissionIds
|
||||
- createdAt
|
||||
- actor
|
||||
|
||||
AuditLogEntry:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user