strapi-plugin-keycloak-realm-users 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +485 -0
- package/__tests__/constants.test.mjs +207 -0
- package/__tests__/mocks/strapi.mjs +182 -0
- package/__tests__/services/audit-log.test.mjs +283 -0
- package/__tests__/services/keycloak-client.test.mjs +651 -0
- package/__tests__/services/permission.test.mjs +374 -0
- package/__tests__/services/realm.test.mjs +415 -0
- package/__tests__/services/user.test.mjs +487 -0
- package/__tests__/utils/errors.test.mjs +109 -0
- package/admin/src/components/Initializer.jsx +14 -0
- package/admin/src/components/RealmBadge.jsx +17 -0
- package/admin/src/constants.js +14 -0
- package/admin/src/hooks/useAuditLogs.js +142 -0
- package/admin/src/hooks/useKeycloakRoles.js +182 -0
- package/admin/src/hooks/useKeycloakUsers.js +477 -0
- package/admin/src/hooks/useRealmAdmins.js +249 -0
- package/admin/src/hooks/useRealms.js +269 -0
- package/admin/src/index.js +46 -0
- package/admin/src/pages/App.jsx +21 -0
- package/admin/src/pages/AuditPage/index.jsx +213 -0
- package/admin/src/pages/RealmsPage/RealmEditPage.jsx +791 -0
- package/admin/src/pages/RealmsPage/RealmListPage.jsx +231 -0
- package/admin/src/pages/RealmsPage/index.jsx +7 -0
- package/admin/src/pages/UsersPage/UserEditPage.jsx +313 -0
- package/admin/src/pages/UsersPage/UserListPage.jsx +437 -0
- package/admin/src/pages/UsersPage/index.jsx +7 -0
- package/admin/src/pluginId.js +2 -0
- package/admin/src/translations/en.json +77 -0
- package/admin/src/translations/fr.json +77 -0
- package/babel.config.cjs +17 -0
- package/coverage/clover.xml +422 -0
- package/coverage/coverage-final.json +8 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +146 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/bootstrap.js.html +346 -0
- package/coverage/lcov-report/src/config/index.html +116 -0
- package/coverage/lcov-report/src/config/index.js.html +106 -0
- package/coverage/lcov-report/src/constants.js.html +850 -0
- package/coverage/lcov-report/src/content-types/audit-log/index.html +116 -0
- package/coverage/lcov-report/src/content-types/audit-log/index.js.html +94 -0
- package/coverage/lcov-report/src/content-types/index.html +116 -0
- package/coverage/lcov-report/src/content-types/index.js.html +112 -0
- package/coverage/lcov-report/src/content-types/realm-admin/index.html +116 -0
- package/coverage/lcov-report/src/content-types/realm-admin/index.js.html +94 -0
- package/coverage/lcov-report/src/content-types/realm-config/index.html +116 -0
- package/coverage/lcov-report/src/content-types/realm-config/index.js.html +94 -0
- package/coverage/lcov-report/src/controllers/audit.js.html +517 -0
- package/coverage/lcov-report/src/controllers/index.html +161 -0
- package/coverage/lcov-report/src/controllers/index.js.html +112 -0
- package/coverage/lcov-report/src/controllers/realm.js.html +1057 -0
- package/coverage/lcov-report/src/controllers/user.js.html +1324 -0
- package/coverage/lcov-report/src/destroy.js.html +100 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/policies/can-access-realm.js.html +163 -0
- package/coverage/lcov-report/src/policies/index.html +146 -0
- package/coverage/lcov-report/src/policies/index.js.html +106 -0
- package/coverage/lcov-report/src/policies/is-authenticated.js.html +100 -0
- package/coverage/lcov-report/src/register.js.html +106 -0
- package/coverage/lcov-report/src/routes/admin.js.html +844 -0
- package/coverage/lcov-report/src/routes/index.html +131 -0
- package/coverage/lcov-report/src/routes/index.js.html +109 -0
- package/coverage/lcov-report/src/services/audit-log.js.html +673 -0
- package/coverage/lcov-report/src/services/index.html +176 -0
- package/coverage/lcov-report/src/services/index.js.html +124 -0
- package/coverage/lcov-report/src/services/keycloak-client.js.html +2359 -0
- package/coverage/lcov-report/src/services/permission.js.html +955 -0
- package/coverage/lcov-report/src/services/realm.js.html +1207 -0
- package/coverage/lcov-report/src/services/user.js.html +1924 -0
- package/coverage/lcov-report/src/utils/errors.js.html +274 -0
- package/coverage/lcov-report/src/utils/index.html +116 -0
- package/coverage/lcov-report/src/utils/index.js.html +103 -0
- package/coverage/lcov.info +804 -0
- package/dist/_chunks/App-BaKrvCeS.mjs +1975 -0
- package/dist/_chunks/App-DO6syS77.js +1975 -0
- package/dist/_chunks/en-Li-XBDe9.mjs +72 -0
- package/dist/_chunks/en-aCyfgNfr.js +72 -0
- package/dist/_chunks/fr-Cj33Q8jI.js +72 -0
- package/dist/_chunks/fr-vLrXph-Z.mjs +72 -0
- package/dist/_chunks/index-DwDO4-0C.js +69 -0
- package/dist/_chunks/index-jTVd7LdQ.mjs +70 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/server/index.js +3003 -0
- package/dist/server/index.mjs +3004 -0
- package/jest.config.cjs +50 -0
- package/package.json +55 -0
- package/server/src/bootstrap.js +87 -0
- package/server/src/config/index.js +7 -0
- package/server/src/constants.js +255 -0
- package/server/src/content-types/audit-log/index.js +3 -0
- package/server/src/content-types/audit-log/schema.json +61 -0
- package/server/src/content-types/index.js +9 -0
- package/server/src/content-types/realm-admin/index.js +3 -0
- package/server/src/content-types/realm-admin/schema.json +45 -0
- package/server/src/content-types/realm-config/index.js +3 -0
- package/server/src/content-types/realm-config/schema.json +56 -0
- package/server/src/controllers/audit.js +144 -0
- package/server/src/controllers/index.js +9 -0
- package/server/src/controllers/realm.js +324 -0
- package/server/src/controllers/user.js +413 -0
- package/server/src/destroy.js +5 -0
- package/server/src/index.js +21 -0
- package/server/src/policies/can-access-realm.js +26 -0
- package/server/src/policies/index.js +7 -0
- package/server/src/policies/is-authenticated.js +5 -0
- package/server/src/register.js +7 -0
- package/server/src/routes/admin.js +253 -0
- package/server/src/routes/index.js +8 -0
- package/server/src/services/audit-log.js +196 -0
- package/server/src/services/index.js +13 -0
- package/server/src/services/keycloak-client.js +758 -0
- package/server/src/services/permission.js +290 -0
- package/server/src/services/realm.js +374 -0
- package/server/src/services/user.js +613 -0
- package/server/src/utils/errors.js +63 -0
- package/server/src/utils/index.js +6 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React hook for fetching and managing audit logs.
|
|
3
|
+
* Provides filtering by realm, user, and action type.
|
|
4
|
+
* @module admin/hooks/useAuditLogs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
8
|
+
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
9
|
+
import { API_BASE_PATH } from '../constants';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} AuditLogEntry
|
|
13
|
+
* @property {string} documentId - Log entry document ID
|
|
14
|
+
* @property {string} realmName - Realm slug identifier
|
|
15
|
+
* @property {string} realmDisplayName - Realm display name
|
|
16
|
+
* @property {string} action - Action type (e.g., 'CREATE_USER', 'RESET_PASSWORD')
|
|
17
|
+
* @property {string|null} keycloakUserId - Target Keycloak user ID
|
|
18
|
+
* @property {string|null} keycloakUsername - Target Keycloak username
|
|
19
|
+
* @property {Object|null} details - Action-specific metadata
|
|
20
|
+
* @property {number|null} performedById - Strapi admin user ID
|
|
21
|
+
* @property {string} performedByEmail - Strapi admin email
|
|
22
|
+
* @property {string} createdAt - ISO timestamp
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} UseAuditLogsOptions
|
|
27
|
+
* @property {string} [realmId] - Filter by realm document ID
|
|
28
|
+
* @property {string} [keycloakUserId] - Filter by Keycloak user ID
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} FetchLogsParams
|
|
33
|
+
* @property {number} [limit=50] - Maximum entries to return
|
|
34
|
+
* @property {number} [offset=0] - Starting offset for pagination
|
|
35
|
+
* @property {string} [action] - Filter by action type
|
|
36
|
+
* @property {string} [realmName] - Filter by realm name
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} UseAuditLogsReturn
|
|
41
|
+
* @property {AuditLogEntry[]} logs - Array of audit log entries
|
|
42
|
+
* @property {boolean} isLoading - Loading state
|
|
43
|
+
* @property {number} total - Total matching entries
|
|
44
|
+
* @property {Function} fetchLogs - Fetch logs with optional filters
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Hook for fetching audit logs with optional filtering.
|
|
49
|
+
* Supports filtering by realm, user, and action type.
|
|
50
|
+
*
|
|
51
|
+
* @param {UseAuditLogsOptions} [options={}] - Filter options
|
|
52
|
+
* @returns {UseAuditLogsReturn} Audit log data and fetch function
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Fetch all logs
|
|
56
|
+
* function AuditLogPage() {
|
|
57
|
+
* const { logs, isLoading, total, fetchLogs } = useAuditLogs();
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <LogTable
|
|
61
|
+
* entries={logs}
|
|
62
|
+
* total={total}
|
|
63
|
+
* onPageChange={(offset) => fetchLogs({ offset })}
|
|
64
|
+
* />
|
|
65
|
+
* );
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Fetch logs for a specific realm
|
|
70
|
+
* function RealmAuditLogs({ realmId }) {
|
|
71
|
+
* const { logs } = useAuditLogs({ realmId });
|
|
72
|
+
* // logs are automatically filtered by realm
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Fetch logs for a specific user
|
|
77
|
+
* function UserActivityLog({ keycloakUserId }) {
|
|
78
|
+
* const { logs } = useAuditLogs({ keycloakUserId });
|
|
79
|
+
* // Shows all actions performed on this user
|
|
80
|
+
* }
|
|
81
|
+
*/
|
|
82
|
+
export const useAuditLogs = (options = {}) => {
|
|
83
|
+
const { realmId, keycloakUserId } = options;
|
|
84
|
+
const client = useFetchClient();
|
|
85
|
+
const { toggleNotification } = useNotification();
|
|
86
|
+
|
|
87
|
+
const [logs, setLogs] = useState([]);
|
|
88
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
89
|
+
const [total, setTotal] = useState(0);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fetches audit logs with optional pagination and filters.
|
|
93
|
+
* URL is determined by the hook options (realmId or keycloakUserId).
|
|
94
|
+
*
|
|
95
|
+
* @param {FetchLogsParams} [params={}] - Query parameters
|
|
96
|
+
* @returns {Promise<void>}
|
|
97
|
+
*/
|
|
98
|
+
const fetchLogs = useCallback(
|
|
99
|
+
async ({ limit = 50, offset = 0, action, realmName } = {}) => {
|
|
100
|
+
setIsLoading(true);
|
|
101
|
+
try {
|
|
102
|
+
let url = `${API_BASE_PATH}/audit-logs`;
|
|
103
|
+
const params = { limit, offset };
|
|
104
|
+
|
|
105
|
+
if (realmId) {
|
|
106
|
+
url = `${API_BASE_PATH}/audit-logs/realm/${realmId}`;
|
|
107
|
+
} else if (keycloakUserId) {
|
|
108
|
+
url = `${API_BASE_PATH}/audit-logs/user/${keycloakUserId}`;
|
|
109
|
+
} else {
|
|
110
|
+
if (action) params.action = action;
|
|
111
|
+
if (realmName) params.realmName = realmName;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { data } = await client.get(url, { params });
|
|
115
|
+
setLogs(data.data || []);
|
|
116
|
+
setTotal(data.meta?.total || data.data?.length || 0);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
toggleNotification({
|
|
119
|
+
type: 'danger',
|
|
120
|
+
message: err.response?.data?.error?.message || 'Failed to fetch audit logs',
|
|
121
|
+
});
|
|
122
|
+
} finally {
|
|
123
|
+
setIsLoading(false);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[client, toggleNotification, realmId, keycloakUserId]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Fetch logs on mount and when options change
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
fetchLogs();
|
|
132
|
+
}, [fetchLogs]);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
logs,
|
|
136
|
+
isLoading,
|
|
137
|
+
total,
|
|
138
|
+
fetchLogs,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default useAuditLogs;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React hook for managing Keycloak realm roles.
|
|
3
|
+
* Provides role fetching and user role assignment functionality.
|
|
4
|
+
* @module admin/hooks/useKeycloakRoles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
8
|
+
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
9
|
+
import { API_BASE_PATH } from '../constants';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} KeycloakRole
|
|
13
|
+
* @property {string} id - Role unique identifier
|
|
14
|
+
* @property {string} name - Role name
|
|
15
|
+
* @property {string} [description] - Role description
|
|
16
|
+
* @property {boolean} [composite] - Whether role contains other roles
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} UseKeycloakRolesReturn
|
|
21
|
+
* @property {KeycloakRole[]} roles - Array of available realm roles
|
|
22
|
+
* @property {boolean} isLoading - Loading state
|
|
23
|
+
* @property {Function} fetchRoles - Refetch all roles
|
|
24
|
+
* @property {Function} getUserRoles - Get roles assigned to a user
|
|
25
|
+
* @property {Function} assignRoles - Assign roles to a user
|
|
26
|
+
* @property {Function} removeRoles - Remove roles from a user
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook for managing Keycloak realm roles for a specific realm.
|
|
31
|
+
* Provides functionality to fetch roles and manage user role assignments.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} realmId - Realm document ID
|
|
34
|
+
* @returns {UseKeycloakRolesReturn} Role management functions and state
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* function UserRolesEditor({ realmId, userId }) {
|
|
38
|
+
* const { roles, getUserRoles, assignRoles, removeRoles } = useKeycloakRoles(realmId);
|
|
39
|
+
* const [userRoles, setUserRoles] = useState([]);
|
|
40
|
+
*
|
|
41
|
+
* useEffect(() => {
|
|
42
|
+
* getUserRoles(userId).then(setUserRoles);
|
|
43
|
+
* }, [userId]);
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <RoleSelector
|
|
47
|
+
* availableRoles={roles}
|
|
48
|
+
* assignedRoles={userRoles}
|
|
49
|
+
* onAssign={(role) => assignRoles(userId, [role])}
|
|
50
|
+
* onRemove={(role) => removeRoles(userId, [role])}
|
|
51
|
+
* />
|
|
52
|
+
* );
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
55
|
+
export const useKeycloakRoles = (realmId) => {
|
|
56
|
+
const client = useFetchClient();
|
|
57
|
+
const { toggleNotification } = useNotification();
|
|
58
|
+
|
|
59
|
+
const [roles, setRoles] = useState([]);
|
|
60
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fetches all available realm roles from Keycloak.
|
|
64
|
+
*
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
const fetchRoles = useCallback(async () => {
|
|
68
|
+
if (!realmId) return;
|
|
69
|
+
|
|
70
|
+
setIsLoading(true);
|
|
71
|
+
try {
|
|
72
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/roles`);
|
|
73
|
+
setRoles(data.data || []);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
toggleNotification({
|
|
76
|
+
type: 'danger',
|
|
77
|
+
message: err.response?.data?.error?.message || 'Failed to fetch roles',
|
|
78
|
+
});
|
|
79
|
+
} finally {
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
}
|
|
82
|
+
}, [realmId, client, toggleNotification]);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fetches roles currently assigned to a specific user.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} userId - Keycloak user ID
|
|
88
|
+
* @returns {Promise<KeycloakRole[]>} Array of assigned roles
|
|
89
|
+
* @throws {Error} If fetch fails
|
|
90
|
+
*/
|
|
91
|
+
const getUserRoles = useCallback(
|
|
92
|
+
async (userId) => {
|
|
93
|
+
try {
|
|
94
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`);
|
|
95
|
+
return data.data || [];
|
|
96
|
+
} catch (err) {
|
|
97
|
+
toggleNotification({
|
|
98
|
+
type: 'danger',
|
|
99
|
+
message: err.response?.data?.error?.message || 'Failed to fetch user roles',
|
|
100
|
+
});
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[realmId, client, toggleNotification]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Assigns roles to a user.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} userId - Keycloak user ID
|
|
111
|
+
* @param {KeycloakRole[]} rolesToAssign - Roles to assign (must include id and name)
|
|
112
|
+
* @returns {Promise<void>}
|
|
113
|
+
* @throws {Error} If assignment fails
|
|
114
|
+
*/
|
|
115
|
+
const assignRoles = useCallback(
|
|
116
|
+
async (userId, rolesToAssign) => {
|
|
117
|
+
try {
|
|
118
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`, {
|
|
119
|
+
data: { roles: rolesToAssign },
|
|
120
|
+
});
|
|
121
|
+
toggleNotification({
|
|
122
|
+
type: 'success',
|
|
123
|
+
message: 'Roles assigned successfully',
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
toggleNotification({
|
|
127
|
+
type: 'danger',
|
|
128
|
+
message: err.response?.data?.error?.message || 'Failed to assign roles',
|
|
129
|
+
});
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
[realmId, client, toggleNotification]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Removes roles from a user.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} userId - Keycloak user ID
|
|
140
|
+
* @param {KeycloakRole[]} rolesToRemove - Roles to remove (must include id and name)
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
* @throws {Error} If removal fails
|
|
143
|
+
*/
|
|
144
|
+
const removeRoles = useCallback(
|
|
145
|
+
async (userId, rolesToRemove) => {
|
|
146
|
+
try {
|
|
147
|
+
await client.del(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`, {
|
|
148
|
+
data: { roles: rolesToRemove },
|
|
149
|
+
});
|
|
150
|
+
toggleNotification({
|
|
151
|
+
type: 'success',
|
|
152
|
+
message: 'Roles removed successfully',
|
|
153
|
+
});
|
|
154
|
+
} catch (err) {
|
|
155
|
+
toggleNotification({
|
|
156
|
+
type: 'danger',
|
|
157
|
+
message: err.response?.data?.error?.message || 'Failed to remove roles',
|
|
158
|
+
});
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[realmId, client, toggleNotification]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Fetch roles when realmId changes
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (realmId) {
|
|
168
|
+
fetchRoles();
|
|
169
|
+
}
|
|
170
|
+
}, [realmId, fetchRoles]);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
roles,
|
|
174
|
+
isLoading,
|
|
175
|
+
fetchRoles,
|
|
176
|
+
getUserRoles,
|
|
177
|
+
assignRoles,
|
|
178
|
+
removeRoles,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export default useKeycloakRoles;
|