From 236002137611bbc11f348fab7011f10abbc332f3 Mon Sep 17 00:00:00 2001 From: Hyko Date: Sat, 25 Apr 2026 09:47:34 -0400 Subject: [PATCH] refactor(users)!: merge users.edit and users.delete into users.manage permission BREAKING CHANGE: permissions `users.edit` and `users.delete` have been replaced by a single `users.manage` permission; any role or code referencing the old keys must be updated - remove `USERS_EDIT` and `USERS_DELETE` from `PERMISSIONS` and `PERMISSION_DEFINITIONS` - add `USERS_MANAGE` permission covering create, edit and delete actions - update `db.js` to use `users.manage` in permission checks - update `auth/api.js` to reference the new permission key - update `UsersPage.client.js` to check `users.manage` instead of old keys - update `api/define.js` and all README examples to reflect the new key --- src/core/api/README.md | 2 +- src/core/api/define.js | 2 +- src/core/users/README.md | 4 ++-- src/core/users/constants.js | 14 ++++++-------- src/core/users/db.js | 16 ++++++++++++++++ src/features/admin/pages/UsersPage.client.js | 2 +- src/features/auth/api.js | 14 +++++++------- 7 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/core/api/README.md b/src/core/api/README.md index 2fb4e62..f33e17c 100644 --- a/src/core/api/README.md +++ b/src/core/api/README.md @@ -181,7 +181,7 @@ Champs optionnels : | Champ | Type | Description | |-------|------|-------------| | `skipRateLimit` | `boolean` | Exempte la route du rate limiting par IP (ex. health checks) | -| `permission` | `string` | Sur une route `auth: 'admin'`, exige en plus cette clé de permission granulaire (ex. `'users.edit'`). Retourne 403 si l'utilisateur ne la possède pas. Voir `PERMISSIONS` dans `src/core/users/constants.js` | +| `permission` | `string` | Sur une route `auth: 'admin'`, exige en plus cette clé de permission granulaire (ex. `'users.manage'`). Retourne 403 si l'utilisateur ne la possède pas. Voir `PERMISSIONS` dans `src/core/users/constants.js` | --- diff --git a/src/core/api/define.js b/src/core/api/define.js index 6aeba46..35451de 100644 --- a/src/core/api/define.js +++ b/src/core/api/define.js @@ -25,7 +25,7 @@ * (e.g. health checks from monitoring systems). * permission {string} When set on an 'admin' route, the router additionally * verifies that the authenticated user holds this granular - * permission key (e.g. 'users.edit'). If the user lacks + * permission key (e.g. 'users.manage'). If the user lacks * the permission, the request is rejected with 403 Forbidden. * * Auth levels: diff --git a/src/core/users/README.md b/src/core/users/README.md index 43cbf18..edce495 100644 --- a/src/core/users/README.md +++ b/src/core/users/README.md @@ -204,7 +204,7 @@ const role = await createRole({ name: 'Modérateur', description: 'Peut gérer l await updateRole(roleId, { name: 'Modérateur', - permissionKeys: [PERMISSIONS.USERS_VIEW, PERMISSIONS.USERS_EDIT], + permissionKeys: [PERMISSIONS.USERS_VIEW, PERMISSIONS.USERS_MANAGE], }); await deleteRole(roleId); // impossible sur les rôles système @@ -230,7 +230,7 @@ const keys = await getUserPermissions(userId); | Groupe | Clés | |--------|------| | Administration | `admin.access` | -| Utilisateurs | `users.view`, `users.edit`, `users.delete` | +| Utilisateurs | `users.view`, `users.manage` | | Rôles | `roles.view`, `roles.manage` | --- diff --git a/src/core/users/constants.js b/src/core/users/constants.js index 01c874a..94d0b85 100644 --- a/src/core/users/constants.js +++ b/src/core/users/constants.js @@ -6,19 +6,17 @@ export const PERMISSIONS = { ADMIN_ACCESS: 'admin.access', USERS_VIEW: 'users.view', - USERS_EDIT: 'users.edit', - USERS_DELETE: 'users.delete', + USERS_MANAGE: 'users.manage', ROLES_VIEW: 'roles.view', ROLES_MANAGE: 'roles.manage', }; export const PERMISSION_DEFINITIONS = [ - { key: 'admin.access', name: 'Accès au panneau admin', description: "Permet d'accéder à l'interface d'administration.", group_name: 'Administration' }, - { key: 'users.view', name: 'Voir les utilisateurs', description: 'Permet de consulter la liste des membres et leurs profils.', group_name: 'Utilisateurs' }, - { key: 'users.edit', name: 'Modifier les utilisateurs', description: 'Permet de changer les informations et les rôles des membres.', group_name: 'Utilisateurs' }, - { key: 'users.delete', name: 'Supprimer des utilisateurs', description: 'Permet de supprimer des comptes membres.', group_name: 'Utilisateurs' }, - { key: 'roles.view', name: 'Voir les rôles', description: 'Permet de consulter la liste des rôles et leurs permissions.', group_name: 'Rôles' }, - { key: 'roles.manage', name: 'Gérer les rôles', description: 'Permet de créer, modifier et supprimer des rôles.', group_name: 'Rôles' }, + { key: 'admin.access', name: 'Accès au panneau admin', description: "Permet d'accéder à l'interface d'administration.", group_name: 'Administration' }, + { key: 'users.view', name: 'Voir les utilisateurs', description: 'Permet de consulter la liste des membres et leurs profils.', group_name: 'Utilisateurs' }, + { key: 'users.manage', name: 'Gérer les utilisateurs', description: 'Permet de créer, modifier et supprimer des comptes membres.', group_name: 'Utilisateurs' }, + { key: 'roles.view', name: 'Voir les rôles', description: 'Permet de consulter la liste des rôles et leurs permissions.', group_name: 'Rôles' }, + { key: 'roles.manage', name: 'Gérer les rôles', description: 'Permet de créer, modifier et supprimer des rôles.', group_name: 'Rôles' }, ]; /** diff --git a/src/core/users/db.js b/src/core/users/db.js index 9ba8f7b..4b05f33 100644 --- a/src/core/users/db.js +++ b/src/core/users/db.js @@ -66,6 +66,20 @@ async function dropRoleCheckConstraint() { `); } +async function migratePermissions() { + // Migrate users.edit / users.delete → users.manage + await query(` + INSERT INTO zen_auth_role_permissions (role_id, permission_key) + SELECT DISTINCT role_id, 'users.manage' + FROM zen_auth_role_permissions + WHERE permission_key IN ('users.edit', 'users.delete') + AND EXISTS (SELECT 1 FROM zen_auth_permissions WHERE key = 'users.manage') + ON CONFLICT DO NOTHING + `); + await query(`DELETE FROM zen_auth_role_permissions WHERE permission_key IN ('users.edit', 'users.delete')`); + await query(`DELETE FROM zen_auth_permissions WHERE key IN ('users.edit', 'users.delete')`); +} + async function seedDefaultRolesAndPermissions() { // Permissions for (const perm of PERMISSION_DEFINITIONS) { @@ -75,6 +89,8 @@ async function seedDefaultRolesAndPermissions() { ); } + await migratePermissions(); + // Admin role const adminRoleId = generateId(); await query( diff --git a/src/features/admin/pages/UsersPage.client.js b/src/features/admin/pages/UsersPage.client.js index a583095..7a10777 100644 --- a/src/features/admin/pages/UsersPage.client.js +++ b/src/features/admin/pages/UsersPage.client.js @@ -174,7 +174,7 @@ const UsersPageClient = ({ currentUserId, refreshKey, canEdit }) => { const UsersPage = ({ user }) => { const [createModalOpen, setCreateModalOpen] = useState(false); const [refreshKey, setRefreshKey] = useState(0); - const canEdit = user?.permissions?.includes('users.edit'); + const canEdit = user?.permissions?.includes('users.manage'); return (
diff --git a/src/features/auth/api.js b/src/features/auth/api.js index 243a753..43f934d 100644 --- a/src/features/auth/api.js +++ b/src/features/auth/api.js @@ -897,7 +897,7 @@ async function handleAdminCreateUser(request) { export const routes = defineApiRoutes([ { path: '/users', method: 'GET', handler: handleListUsers, auth: 'admin', permission: PERMISSIONS.USERS_VIEW }, - { path: '/users', method: 'POST', handler: handleAdminCreateUser, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, + { path: '/users', method: 'POST', handler: handleAdminCreateUser, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, { path: '/users/profile', method: 'PUT', handler: handleUpdateProfile, auth: 'user' }, { path: '/users/profile/email', method: 'POST', handler: handleInitiateEmailChange, auth: 'user' }, { path: '/users/profile/password', method: 'POST', handler: handleChangeOwnPassword, auth: 'user' }, @@ -908,13 +908,13 @@ export const routes = defineApiRoutes([ { path: '/users/profile/sessions/:sessionId', method: 'DELETE', handler: handleDeleteSession, auth: 'user' }, { path: '/users/email/confirm', method: 'GET', handler: handleConfirmEmailChange, auth: 'user' }, { path: '/users/:id/roles', method: 'GET', handler: handleGetUserRoles, auth: 'admin', permission: PERMISSIONS.USERS_VIEW }, - { path: '/users/:id/roles', method: 'POST', handler: handleAssignUserRole, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, - { path: '/users/:id/roles/:roleId', method: 'DELETE', handler: handleRevokeUserRole, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, + { path: '/users/:id/roles', method: 'POST', handler: handleAssignUserRole, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, + { path: '/users/:id/roles/:roleId', method: 'DELETE', handler: handleRevokeUserRole, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, { path: '/users/:id', method: 'GET', handler: handleGetUserById, auth: 'admin', permission: PERMISSIONS.USERS_VIEW }, - { path: '/users/:id', method: 'PUT', handler: handleUpdateUserById, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, - { path: '/users/:id/email', method: 'PUT', handler: handleAdminUpdateUserEmail, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, - { path: '/users/:id/password', method: 'PUT', handler: handleAdminSetUserPassword, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, - { path: '/users/:id/send-password-reset', method: 'POST', handler: handleAdminSendPasswordReset, auth: 'admin', permission: PERMISSIONS.USERS_EDIT }, + { path: '/users/:id', method: 'PUT', handler: handleUpdateUserById, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, + { path: '/users/:id/email', method: 'PUT', handler: handleAdminUpdateUserEmail, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, + { path: '/users/:id/password', method: 'PUT', handler: handleAdminSetUserPassword, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, + { path: '/users/:id/send-password-reset', method: 'POST', handler: handleAdminSendPasswordReset, auth: 'admin', permission: PERMISSIONS.USERS_MANAGE }, { path: '/roles', method: 'GET', handler: handleListRoles, auth: 'admin', permission: PERMISSIONS.ROLES_VIEW }, { path: '/roles', method: 'POST', handler: handleCreateRole, auth: 'admin', permission: PERMISSIONS.ROLES_MANAGE }, { path: '/roles/:id', method: 'GET', handler: handleGetRole, auth: 'admin', permission: PERMISSIONS.ROLES_VIEW },