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,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React hook for managing Keycloak users within a realm.
|
|
3
|
+
* Provides comprehensive CRUD operations, password management, and bulk operations.
|
|
4
|
+
* @module admin/hooks/useKeycloakUsers
|
|
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} KeycloakUser
|
|
13
|
+
* @property {string} id - Unique user identifier
|
|
14
|
+
* @property {string} username - Username
|
|
15
|
+
* @property {string} [email] - Email address
|
|
16
|
+
* @property {string} [firstName] - First name
|
|
17
|
+
* @property {string} [lastName] - Last name
|
|
18
|
+
* @property {boolean} enabled - Account enabled status
|
|
19
|
+
* @property {boolean} emailVerified - Email verification status
|
|
20
|
+
* @property {number} createdTimestamp - Account creation timestamp
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} Pagination
|
|
25
|
+
* @property {number} page - Current page number (1-indexed)
|
|
26
|
+
* @property {number} pageSize - Items per page
|
|
27
|
+
* @property {number} total - Total items
|
|
28
|
+
* @property {number} pageCount - Total pages
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} FetchUsersParams
|
|
33
|
+
* @property {string} [search=''] - Search query (matches username, email, name)
|
|
34
|
+
* @property {number} [page=1] - Page number
|
|
35
|
+
* @property {number} [pageSize=25] - Items per page
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} BulkImportResult
|
|
40
|
+
* @property {Object[]} success - Successfully created users
|
|
41
|
+
* @property {Object[]} failed - Failed creations with error messages
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} UseKeycloakUsersReturn
|
|
46
|
+
* @property {KeycloakUser[]} users - Array of users
|
|
47
|
+
* @property {boolean} isLoading - Loading state
|
|
48
|
+
* @property {Pagination} pagination - Pagination metadata
|
|
49
|
+
* @property {Function} fetchUsers - Fetch users with search/pagination
|
|
50
|
+
* @property {Function} getUser - Get single user by ID
|
|
51
|
+
* @property {Function} createUser - Create new user
|
|
52
|
+
* @property {Function} updateUser - Update user attributes
|
|
53
|
+
* @property {Function} deleteUser - Permanently delete user
|
|
54
|
+
* @property {Function} resetPassword - Set user password
|
|
55
|
+
* @property {Function} enableUser - Enable user account
|
|
56
|
+
* @property {Function} disableUser - Disable user account
|
|
57
|
+
* @property {Function} sendVerificationEmail - Send email verification link
|
|
58
|
+
* @property {Function} sendResetPasswordEmail - Send password reset email
|
|
59
|
+
* @property {Function} bulkImport - Import multiple users
|
|
60
|
+
* @property {Function} exportUsers - Export all users
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hook for managing Keycloak users within a specific realm.
|
|
65
|
+
* Provides comprehensive user management including CRUD operations,
|
|
66
|
+
* password management, account status control, and bulk operations.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} realmId - Realm document ID
|
|
69
|
+
* @returns {UseKeycloakUsersReturn} User management functions and state
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* function UsersPage({ realmId }) {
|
|
73
|
+
* const {
|
|
74
|
+
* users,
|
|
75
|
+
* isLoading,
|
|
76
|
+
* pagination,
|
|
77
|
+
* fetchUsers,
|
|
78
|
+
* createUser,
|
|
79
|
+
* deleteUser,
|
|
80
|
+
* } = useKeycloakUsers(realmId);
|
|
81
|
+
*
|
|
82
|
+
* return (
|
|
83
|
+
* <UserTable
|
|
84
|
+
* users={users}
|
|
85
|
+
* loading={isLoading}
|
|
86
|
+
* pagination={pagination}
|
|
87
|
+
* onSearch={(search) => fetchUsers({ search })}
|
|
88
|
+
* onPageChange={(page) => fetchUsers({ page })}
|
|
89
|
+
* onDelete={(user) => deleteUser(user.id)}
|
|
90
|
+
* />
|
|
91
|
+
* );
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
export const useKeycloakUsers = (realmId) => {
|
|
95
|
+
const client = useFetchClient();
|
|
96
|
+
const { toggleNotification } = useNotification();
|
|
97
|
+
|
|
98
|
+
const [users, setUsers] = useState([]);
|
|
99
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
100
|
+
const [pagination, setPagination] = useState({
|
|
101
|
+
page: 1,
|
|
102
|
+
pageSize: 25,
|
|
103
|
+
total: 0,
|
|
104
|
+
pageCount: 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fetches users with optional search and pagination.
|
|
109
|
+
*
|
|
110
|
+
* @param {FetchUsersParams} [params={}] - Query parameters
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
113
|
+
const fetchUsers = useCallback(
|
|
114
|
+
async ({ search = '', page = 1, pageSize = 25 } = {}) => {
|
|
115
|
+
if (!realmId) return;
|
|
116
|
+
|
|
117
|
+
setIsLoading(true);
|
|
118
|
+
try {
|
|
119
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/users`, {
|
|
120
|
+
params: { search, page, pageSize },
|
|
121
|
+
});
|
|
122
|
+
setUsers(data.data?.users || []);
|
|
123
|
+
setPagination(data.data?.pagination || { page: 1, pageSize: 25, total: 0, pageCount: 0 });
|
|
124
|
+
} catch (err) {
|
|
125
|
+
toggleNotification({
|
|
126
|
+
type: 'danger',
|
|
127
|
+
message: err.response?.data?.error?.message || 'Failed to fetch users',
|
|
128
|
+
});
|
|
129
|
+
} finally {
|
|
130
|
+
setIsLoading(false);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
[realmId, client, toggleNotification]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Fetches a single user by their Keycloak ID.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} userId - Keycloak user ID
|
|
140
|
+
* @returns {Promise<KeycloakUser>} User details
|
|
141
|
+
* @throws {Error} If fetch fails
|
|
142
|
+
*/
|
|
143
|
+
const getUser = useCallback(
|
|
144
|
+
async (userId) => {
|
|
145
|
+
try {
|
|
146
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/users/${userId}`);
|
|
147
|
+
return data.data;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
toggleNotification({
|
|
150
|
+
type: 'danger',
|
|
151
|
+
message: err.response?.data?.error?.message || 'Failed to fetch user',
|
|
152
|
+
});
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[realmId, client, toggleNotification]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Creates a new user in Keycloak.
|
|
161
|
+
* Automatically refreshes the user list on success.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} userData - User data
|
|
164
|
+
* @param {string} userData.username - Required username
|
|
165
|
+
* @param {string} [userData.email] - Email address
|
|
166
|
+
* @param {string} [userData.firstName] - First name
|
|
167
|
+
* @param {string} [userData.lastName] - Last name
|
|
168
|
+
* @param {boolean} [userData.enabled=true] - Account enabled status
|
|
169
|
+
* @returns {Promise<KeycloakUser>} Created user
|
|
170
|
+
* @throws {Error} If creation fails
|
|
171
|
+
*/
|
|
172
|
+
const createUser = useCallback(
|
|
173
|
+
async (userData) => {
|
|
174
|
+
try {
|
|
175
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms/${realmId}/users`, {
|
|
176
|
+
data: userData,
|
|
177
|
+
});
|
|
178
|
+
toggleNotification({
|
|
179
|
+
type: 'success',
|
|
180
|
+
message: 'User created successfully',
|
|
181
|
+
});
|
|
182
|
+
await fetchUsers();
|
|
183
|
+
return data.data;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
toggleNotification({
|
|
186
|
+
type: 'danger',
|
|
187
|
+
message: err.response?.data?.error?.message || 'Failed to create user',
|
|
188
|
+
});
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Updates an existing user's attributes.
|
|
197
|
+
* Automatically refreshes the user list on success.
|
|
198
|
+
*
|
|
199
|
+
* @param {string} userId - Keycloak user ID
|
|
200
|
+
* @param {Object} userData - Fields to update
|
|
201
|
+
* @returns {Promise<KeycloakUser>} Updated user
|
|
202
|
+
* @throws {Error} If update fails
|
|
203
|
+
*/
|
|
204
|
+
const updateUser = useCallback(
|
|
205
|
+
async (userId, userData) => {
|
|
206
|
+
try {
|
|
207
|
+
const { data } = await client.put(`${API_BASE_PATH}/realms/${realmId}/users/${userId}`, {
|
|
208
|
+
data: userData,
|
|
209
|
+
});
|
|
210
|
+
toggleNotification({
|
|
211
|
+
type: 'success',
|
|
212
|
+
message: 'User updated successfully',
|
|
213
|
+
});
|
|
214
|
+
await fetchUsers();
|
|
215
|
+
return data.data;
|
|
216
|
+
} catch (err) {
|
|
217
|
+
toggleNotification({
|
|
218
|
+
type: 'danger',
|
|
219
|
+
message: err.response?.data?.error?.message || 'Failed to update user',
|
|
220
|
+
});
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Permanently deletes a user from Keycloak.
|
|
229
|
+
* Automatically refreshes the user list on success.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} userId - Keycloak user ID
|
|
232
|
+
* @returns {Promise<void>}
|
|
233
|
+
* @throws {Error} If deletion fails
|
|
234
|
+
*/
|
|
235
|
+
const deleteUser = useCallback(
|
|
236
|
+
async (userId) => {
|
|
237
|
+
try {
|
|
238
|
+
await client.del(`${API_BASE_PATH}/realms/${realmId}/users/${userId}`);
|
|
239
|
+
toggleNotification({
|
|
240
|
+
type: 'success',
|
|
241
|
+
message: 'User deleted successfully',
|
|
242
|
+
});
|
|
243
|
+
await fetchUsers();
|
|
244
|
+
} catch (err) {
|
|
245
|
+
toggleNotification({
|
|
246
|
+
type: 'danger',
|
|
247
|
+
message: err.response?.data?.error?.message || 'Failed to delete user',
|
|
248
|
+
});
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Sets a new password for a user.
|
|
257
|
+
*
|
|
258
|
+
* @param {string} userId - Keycloak user ID
|
|
259
|
+
* @param {string} password - New password
|
|
260
|
+
* @param {boolean} [temporary=true] - If true, user must change password on next login
|
|
261
|
+
* @returns {Promise<void>}
|
|
262
|
+
* @throws {Error} If password reset fails
|
|
263
|
+
*/
|
|
264
|
+
const resetPassword = useCallback(
|
|
265
|
+
async (userId, password, temporary = true) => {
|
|
266
|
+
try {
|
|
267
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/reset-password`, {
|
|
268
|
+
data: { password, temporary },
|
|
269
|
+
});
|
|
270
|
+
toggleNotification({
|
|
271
|
+
type: 'success',
|
|
272
|
+
message: 'Password reset successfully',
|
|
273
|
+
});
|
|
274
|
+
} catch (err) {
|
|
275
|
+
toggleNotification({
|
|
276
|
+
type: 'danger',
|
|
277
|
+
message: err.response?.data?.error?.message || 'Failed to reset password',
|
|
278
|
+
});
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
[realmId, client, toggleNotification]
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Enables a user account.
|
|
287
|
+
* Automatically refreshes the user list on success.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} userId - Keycloak user ID
|
|
290
|
+
* @returns {Promise<void>}
|
|
291
|
+
* @throws {Error} If enable fails
|
|
292
|
+
*/
|
|
293
|
+
const enableUser = useCallback(
|
|
294
|
+
async (userId) => {
|
|
295
|
+
try {
|
|
296
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/enable`);
|
|
297
|
+
toggleNotification({
|
|
298
|
+
type: 'success',
|
|
299
|
+
message: 'User enabled successfully',
|
|
300
|
+
});
|
|
301
|
+
await fetchUsers();
|
|
302
|
+
} catch (err) {
|
|
303
|
+
toggleNotification({
|
|
304
|
+
type: 'danger',
|
|
305
|
+
message: err.response?.data?.error?.message || 'Failed to enable user',
|
|
306
|
+
});
|
|
307
|
+
throw err;
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Disables a user account.
|
|
315
|
+
* Automatically refreshes the user list on success.
|
|
316
|
+
*
|
|
317
|
+
* @param {string} userId - Keycloak user ID
|
|
318
|
+
* @returns {Promise<void>}
|
|
319
|
+
* @throws {Error} If disable fails
|
|
320
|
+
*/
|
|
321
|
+
const disableUser = useCallback(
|
|
322
|
+
async (userId) => {
|
|
323
|
+
try {
|
|
324
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/disable`);
|
|
325
|
+
toggleNotification({
|
|
326
|
+
type: 'success',
|
|
327
|
+
message: 'User disabled successfully',
|
|
328
|
+
});
|
|
329
|
+
await fetchUsers();
|
|
330
|
+
} catch (err) {
|
|
331
|
+
toggleNotification({
|
|
332
|
+
type: 'danger',
|
|
333
|
+
message: err.response?.data?.error?.message || 'Failed to disable user',
|
|
334
|
+
});
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Sends an email verification link to the user.
|
|
343
|
+
*
|
|
344
|
+
* @param {string} userId - Keycloak user ID
|
|
345
|
+
* @returns {Promise<void>}
|
|
346
|
+
* @throws {Error} If email send fails
|
|
347
|
+
*/
|
|
348
|
+
const sendVerificationEmail = useCallback(
|
|
349
|
+
async (userId) => {
|
|
350
|
+
try {
|
|
351
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/send-verify-email`);
|
|
352
|
+
toggleNotification({
|
|
353
|
+
type: 'success',
|
|
354
|
+
message: 'Verification email sent',
|
|
355
|
+
});
|
|
356
|
+
} catch (err) {
|
|
357
|
+
toggleNotification({
|
|
358
|
+
type: 'danger',
|
|
359
|
+
message: err.response?.data?.error?.message || 'Failed to send verification email',
|
|
360
|
+
});
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[realmId, client, toggleNotification]
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Sends a password reset email to the user.
|
|
369
|
+
*
|
|
370
|
+
* @param {string} userId - Keycloak user ID
|
|
371
|
+
* @returns {Promise<void>}
|
|
372
|
+
* @throws {Error} If email send fails
|
|
373
|
+
*/
|
|
374
|
+
const sendResetPasswordEmail = useCallback(
|
|
375
|
+
async (userId) => {
|
|
376
|
+
try {
|
|
377
|
+
await client.post(`${API_BASE_PATH}/realms/${realmId}/users/${userId}/send-reset-password-email`);
|
|
378
|
+
toggleNotification({
|
|
379
|
+
type: 'success',
|
|
380
|
+
message: 'Password reset email sent',
|
|
381
|
+
});
|
|
382
|
+
} catch (err) {
|
|
383
|
+
toggleNotification({
|
|
384
|
+
type: 'danger',
|
|
385
|
+
message: err.response?.data?.error?.message || 'Failed to send reset email',
|
|
386
|
+
});
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
[realmId, client, toggleNotification]
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Bulk imports multiple users.
|
|
395
|
+
* Automatically refreshes the user list on completion.
|
|
396
|
+
*
|
|
397
|
+
* @param {Object[]} usersData - Array of user data objects
|
|
398
|
+
* @returns {Promise<BulkImportResult>} Import results with success/failed arrays
|
|
399
|
+
* @throws {Error} If import fails entirely
|
|
400
|
+
*/
|
|
401
|
+
const bulkImport = useCallback(
|
|
402
|
+
async (usersData) => {
|
|
403
|
+
try {
|
|
404
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms/${realmId}/users/import`, {
|
|
405
|
+
data: { users: usersData },
|
|
406
|
+
});
|
|
407
|
+
const result = data.data;
|
|
408
|
+
toggleNotification({
|
|
409
|
+
type: result.failed.length > 0 ? 'warning' : 'success',
|
|
410
|
+
message: `Imported ${result.success.length} users. ${result.failed.length} failed.`,
|
|
411
|
+
});
|
|
412
|
+
await fetchUsers();
|
|
413
|
+
return result;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
toggleNotification({
|
|
416
|
+
type: 'danger',
|
|
417
|
+
message: err.response?.data?.error?.message || 'Failed to import users',
|
|
418
|
+
});
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
[realmId, client, toggleNotification, fetchUsers]
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Exports all users from the realm.
|
|
427
|
+
*
|
|
428
|
+
* @param {string} [format='json'] - Export format ('json' or 'csv')
|
|
429
|
+
* @returns {Promise<KeycloakUser[]|string>} Users array or CSV string
|
|
430
|
+
* @throws {Error} If export fails
|
|
431
|
+
*/
|
|
432
|
+
const exportUsers = useCallback(
|
|
433
|
+
async (format = 'json') => {
|
|
434
|
+
try {
|
|
435
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/users/export`, {
|
|
436
|
+
params: { format },
|
|
437
|
+
});
|
|
438
|
+
return format === 'csv' ? data : data.data;
|
|
439
|
+
} catch (err) {
|
|
440
|
+
toggleNotification({
|
|
441
|
+
type: 'danger',
|
|
442
|
+
message: err.response?.data?.error?.message || 'Failed to export users',
|
|
443
|
+
});
|
|
444
|
+
throw err;
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
[realmId, client, toggleNotification]
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
// Fetch users when realmId changes
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (realmId) {
|
|
453
|
+
fetchUsers();
|
|
454
|
+
}
|
|
455
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
456
|
+
}, [realmId]);
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
users,
|
|
460
|
+
isLoading,
|
|
461
|
+
pagination,
|
|
462
|
+
fetchUsers,
|
|
463
|
+
getUser,
|
|
464
|
+
createUser,
|
|
465
|
+
updateUser,
|
|
466
|
+
deleteUser,
|
|
467
|
+
resetPassword,
|
|
468
|
+
enableUser,
|
|
469
|
+
disableUser,
|
|
470
|
+
sendVerificationEmail,
|
|
471
|
+
sendResetPasswordEmail,
|
|
472
|
+
bulkImport,
|
|
473
|
+
exportUsers,
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
export default useKeycloakUsers;
|