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,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React hook for managing realm admin assignments.
|
|
3
|
+
* Provides functionality to assign Strapi users as realm administrators with granular permissions.
|
|
4
|
+
* @module admin/hooks/useRealmAdmins
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from 'react';
|
|
8
|
+
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
9
|
+
import { API_BASE_PATH } from '../constants';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} RealmPermissions
|
|
13
|
+
* @property {boolean} canRead - Can view users in the realm
|
|
14
|
+
* @property {boolean} canCreate - Can create new users
|
|
15
|
+
* @property {boolean} canUpdate - Can modify users (includes enable/disable)
|
|
16
|
+
* @property {boolean} canDelete - Can permanently delete users
|
|
17
|
+
* @property {boolean} canManageRoles - Can assign/remove realm roles
|
|
18
|
+
* @property {boolean} canResetPassword - Can reset passwords and send password reset emails
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} RealmAdminAssignment
|
|
23
|
+
* @property {string} documentId - Assignment document ID
|
|
24
|
+
* @property {number} strapiUserId - Strapi admin user ID
|
|
25
|
+
* @property {string} strapiUserEmail - Strapi admin email
|
|
26
|
+
* @property {RealmPermissions} permissions - Granted permissions
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} StrapiUser
|
|
31
|
+
* @property {number} id - User ID
|
|
32
|
+
* @property {string} email - User email
|
|
33
|
+
* @property {string} [firstname] - First name
|
|
34
|
+
* @property {string} [lastname] - Last name
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Object} UseRealmAdminsReturn
|
|
39
|
+
* @property {RealmAdminAssignment[]} admins - Array of admin assignments
|
|
40
|
+
* @property {StrapiUser[]} strapiUsers - Array of available Strapi users
|
|
41
|
+
* @property {boolean} isLoading - Loading state
|
|
42
|
+
* @property {Function} fetchAdmins - Refetch admin assignments
|
|
43
|
+
* @property {Function} fetchStrapiUsers - Fetch available Strapi users
|
|
44
|
+
* @property {Function} addAdmin - Add new admin assignment
|
|
45
|
+
* @property {Function} updateAdmin - Update admin permissions
|
|
46
|
+
* @property {Function} removeAdmin - Remove admin assignment
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook for managing realm admin assignments.
|
|
51
|
+
* Allows assigning Strapi users as administrators of a specific realm
|
|
52
|
+
* with granular permission control.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} realmId - Realm document ID
|
|
55
|
+
* @returns {UseRealmAdminsReturn} Admin management functions and state
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* function RealmAdminManager({ realmId }) {
|
|
59
|
+
* const {
|
|
60
|
+
* admins,
|
|
61
|
+
* strapiUsers,
|
|
62
|
+
* fetchStrapiUsers,
|
|
63
|
+
* addAdmin,
|
|
64
|
+
* updateAdmin,
|
|
65
|
+
* removeAdmin,
|
|
66
|
+
* } = useRealmAdmins(realmId);
|
|
67
|
+
*
|
|
68
|
+
* useEffect(() => {
|
|
69
|
+
* fetchStrapiUsers();
|
|
70
|
+
* }, []);
|
|
71
|
+
*
|
|
72
|
+
* const handleAddAdmin = async (userId, email) => {
|
|
73
|
+
* await addAdmin(userId, email, {
|
|
74
|
+
* canRead: true,
|
|
75
|
+
* canCreate: true,
|
|
76
|
+
* canUpdate: false,
|
|
77
|
+
* canDelete: false,
|
|
78
|
+
* canManageRoles: false,
|
|
79
|
+
* canResetPassword: false,
|
|
80
|
+
* });
|
|
81
|
+
* };
|
|
82
|
+
*
|
|
83
|
+
* return (
|
|
84
|
+
* <AdminList
|
|
85
|
+
* admins={admins}
|
|
86
|
+
* availableUsers={strapiUsers}
|
|
87
|
+
* onAdd={handleAddAdmin}
|
|
88
|
+
* onRemove={removeAdmin}
|
|
89
|
+
* />
|
|
90
|
+
* );
|
|
91
|
+
* }
|
|
92
|
+
*/
|
|
93
|
+
export const useRealmAdmins = (realmId) => {
|
|
94
|
+
const client = useFetchClient();
|
|
95
|
+
const { toggleNotification } = useNotification();
|
|
96
|
+
|
|
97
|
+
const [admins, setAdmins] = useState([]);
|
|
98
|
+
const [strapiUsers, setStrapiUsers] = useState([]);
|
|
99
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fetches all admin assignments for the realm.
|
|
103
|
+
*
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
const fetchAdmins = useCallback(async () => {
|
|
107
|
+
if (!realmId) return;
|
|
108
|
+
|
|
109
|
+
setIsLoading(true);
|
|
110
|
+
try {
|
|
111
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${realmId}/admins`);
|
|
112
|
+
setAdmins(data.data || []);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
toggleNotification({
|
|
115
|
+
type: 'danger',
|
|
116
|
+
message: err.response?.data?.error?.message || 'Failed to fetch realm admins',
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
setIsLoading(false);
|
|
120
|
+
}
|
|
121
|
+
}, [realmId, client, toggleNotification]);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Fetches all Strapi admin users available for assignment.
|
|
125
|
+
*
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
const fetchStrapiUsers = useCallback(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const { data } = await client.get('/admin/users');
|
|
131
|
+
setStrapiUsers(data.data?.results || data.data || []);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
toggleNotification({
|
|
134
|
+
type: 'danger',
|
|
135
|
+
message: 'Failed to fetch Strapi users',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}, [client, toggleNotification]);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Adds a Strapi user as a realm admin with specified permissions.
|
|
142
|
+
* Automatically refreshes the admin list on success.
|
|
143
|
+
*
|
|
144
|
+
* @param {number} strapiUserId - Strapi user ID
|
|
145
|
+
* @param {string} strapiUserEmail - Strapi user email
|
|
146
|
+
* @param {RealmPermissions} permissions - Permissions to grant
|
|
147
|
+
* @returns {Promise<RealmAdminAssignment>} Created assignment
|
|
148
|
+
* @throws {Error} If creation fails
|
|
149
|
+
*/
|
|
150
|
+
const addAdmin = useCallback(
|
|
151
|
+
async (strapiUserId, strapiUserEmail, permissions) => {
|
|
152
|
+
try {
|
|
153
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms/${realmId}/admins`, {
|
|
154
|
+
data: {
|
|
155
|
+
strapiUserId,
|
|
156
|
+
strapiUserEmail,
|
|
157
|
+
permissions,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
toggleNotification({
|
|
161
|
+
type: 'success',
|
|
162
|
+
message: 'Admin added successfully',
|
|
163
|
+
});
|
|
164
|
+
await fetchAdmins();
|
|
165
|
+
return data.data;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
toggleNotification({
|
|
168
|
+
type: 'danger',
|
|
169
|
+
message: err.response?.data?.error?.message || 'Failed to add admin',
|
|
170
|
+
});
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
[realmId, client, toggleNotification, fetchAdmins]
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Updates an existing admin assignment's permissions.
|
|
179
|
+
* Automatically refreshes the admin list on success.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} adminId - Admin assignment document ID
|
|
182
|
+
* @param {RealmPermissions} permissions - New permissions
|
|
183
|
+
* @returns {Promise<RealmAdminAssignment>} Updated assignment
|
|
184
|
+
* @throws {Error} If update fails
|
|
185
|
+
*/
|
|
186
|
+
const updateAdmin = useCallback(
|
|
187
|
+
async (adminId, permissions) => {
|
|
188
|
+
try {
|
|
189
|
+
const { data } = await client.put(`${API_BASE_PATH}/realms/${realmId}/admins/${adminId}`, {
|
|
190
|
+
data: { permissions },
|
|
191
|
+
});
|
|
192
|
+
toggleNotification({
|
|
193
|
+
type: 'success',
|
|
194
|
+
message: 'Admin permissions updated',
|
|
195
|
+
});
|
|
196
|
+
await fetchAdmins();
|
|
197
|
+
return data.data;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
toggleNotification({
|
|
200
|
+
type: 'danger',
|
|
201
|
+
message: err.response?.data?.error?.message || 'Failed to update admin',
|
|
202
|
+
});
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
[realmId, client, toggleNotification, fetchAdmins]
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Removes a realm admin assignment.
|
|
211
|
+
* Automatically refreshes the admin list on success.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} adminId - Admin assignment document ID
|
|
214
|
+
* @returns {Promise<void>}
|
|
215
|
+
* @throws {Error} If removal fails
|
|
216
|
+
*/
|
|
217
|
+
const removeAdmin = useCallback(
|
|
218
|
+
async (adminId) => {
|
|
219
|
+
try {
|
|
220
|
+
await client.del(`${API_BASE_PATH}/realms/${realmId}/admins/${adminId}`);
|
|
221
|
+
toggleNotification({
|
|
222
|
+
type: 'success',
|
|
223
|
+
message: 'Admin removed successfully',
|
|
224
|
+
});
|
|
225
|
+
await fetchAdmins();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
toggleNotification({
|
|
228
|
+
type: 'danger',
|
|
229
|
+
message: err.response?.data?.error?.message || 'Failed to remove admin',
|
|
230
|
+
});
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
[realmId, client, toggleNotification, fetchAdmins]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
admins,
|
|
239
|
+
strapiUsers,
|
|
240
|
+
isLoading,
|
|
241
|
+
fetchAdmins,
|
|
242
|
+
fetchStrapiUsers,
|
|
243
|
+
addAdmin,
|
|
244
|
+
updateAdmin,
|
|
245
|
+
removeAdmin,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export default useRealmAdmins;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React hook for managing Keycloak realm configurations.
|
|
3
|
+
* Provides CRUD operations, connection testing, and state management for realms.
|
|
4
|
+
* @module admin/hooks/useRealms
|
|
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} RealmConfig
|
|
13
|
+
* @property {string} documentId - Strapi document ID
|
|
14
|
+
* @property {string} name - Unique slug identifier
|
|
15
|
+
* @property {string} displayName - Human-readable name
|
|
16
|
+
* @property {string} serverUrl - Keycloak server URL
|
|
17
|
+
* @property {string} realmName - Keycloak realm name
|
|
18
|
+
* @property {string} clientId - OAuth client ID
|
|
19
|
+
* @property {boolean} enabled - Active status
|
|
20
|
+
* @property {string} color - UI accent color (hex)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ConnectionTestResult
|
|
25
|
+
* @property {boolean} success - Whether connection succeeded
|
|
26
|
+
* @property {string} [message] - Error message if failed
|
|
27
|
+
* @property {string} [realmDisplayName] - Realm name from Keycloak if successful
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} UseRealmsReturn
|
|
32
|
+
* @property {RealmConfig[]} realms - Array of realm configurations
|
|
33
|
+
* @property {boolean} isLoading - Loading state for initial fetch
|
|
34
|
+
* @property {Function} fetchAll - Refetch all realms
|
|
35
|
+
* @property {Function} fetchOne - Fetch single realm by ID
|
|
36
|
+
* @property {Function} create - Create new realm
|
|
37
|
+
* @property {Function} update - Update existing realm
|
|
38
|
+
* @property {Function} remove - Delete realm
|
|
39
|
+
* @property {Function} testConnection - Test saved realm connection
|
|
40
|
+
* @property {Function} testConnectionRaw - Test connection with raw config
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook for managing Keycloak realm configurations.
|
|
45
|
+
* Provides complete CRUD operations and connection testing functionality.
|
|
46
|
+
*
|
|
47
|
+
* @returns {UseRealmsReturn} Realm management functions and state
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* function RealmsPage() {
|
|
51
|
+
* const { realms, isLoading, create, update, remove, testConnection } = useRealms();
|
|
52
|
+
*
|
|
53
|
+
* if (isLoading) return <Loader />;
|
|
54
|
+
*
|
|
55
|
+
* return (
|
|
56
|
+
* <ul>
|
|
57
|
+
* {realms.map(realm => (
|
|
58
|
+
* <li key={realm.documentId}>{realm.displayName}</li>
|
|
59
|
+
* ))}
|
|
60
|
+
* </ul>
|
|
61
|
+
* );
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
export const useRealms = () => {
|
|
65
|
+
const client = useFetchClient();
|
|
66
|
+
const { toggleNotification } = useNotification();
|
|
67
|
+
|
|
68
|
+
const [realms, setRealms] = useState([]);
|
|
69
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetches all realm configurations from the API.
|
|
73
|
+
* Updates the realms state and handles errors with notifications.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
const fetchAll = useCallback(async () => {
|
|
78
|
+
setIsLoading(true);
|
|
79
|
+
try {
|
|
80
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms`);
|
|
81
|
+
setRealms(data.data || []);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
toggleNotification({
|
|
84
|
+
type: 'danger',
|
|
85
|
+
message: err.response?.data?.error?.message || 'Failed to fetch realms',
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}, [client, toggleNotification]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fetches a single realm by its document ID.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} id - Realm document ID
|
|
96
|
+
* @returns {Promise<RealmConfig>} The realm configuration
|
|
97
|
+
* @throws {Error} If fetch fails
|
|
98
|
+
*/
|
|
99
|
+
const fetchOne = useCallback(
|
|
100
|
+
async (id) => {
|
|
101
|
+
try {
|
|
102
|
+
const { data } = await client.get(`${API_BASE_PATH}/realms/${id}`);
|
|
103
|
+
return data.data;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
toggleNotification({
|
|
106
|
+
type: 'danger',
|
|
107
|
+
message: err.response?.data?.error?.message || 'Failed to fetch realm',
|
|
108
|
+
});
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[client, toggleNotification]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a new realm configuration.
|
|
117
|
+
* Automatically refreshes the realms list on success.
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} realmData - Realm configuration data
|
|
120
|
+
* @param {string} realmData.name - Unique slug identifier
|
|
121
|
+
* @param {string} realmData.displayName - Human-readable name
|
|
122
|
+
* @param {string} realmData.serverUrl - Keycloak server URL
|
|
123
|
+
* @param {string} realmData.realmName - Keycloak realm name
|
|
124
|
+
* @param {string} realmData.clientId - OAuth client ID
|
|
125
|
+
* @param {string} [realmData.clientSecret] - OAuth client secret
|
|
126
|
+
* @param {boolean} [realmData.enabled=true] - Active status
|
|
127
|
+
* @param {string} [realmData.color] - UI accent color
|
|
128
|
+
* @returns {Promise<RealmConfig>} The created realm
|
|
129
|
+
* @throws {Error} If creation fails
|
|
130
|
+
*/
|
|
131
|
+
const create = useCallback(
|
|
132
|
+
async (realmData) => {
|
|
133
|
+
try {
|
|
134
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms`, { data: realmData });
|
|
135
|
+
toggleNotification({
|
|
136
|
+
type: 'success',
|
|
137
|
+
message: 'Realm created successfully',
|
|
138
|
+
});
|
|
139
|
+
await fetchAll();
|
|
140
|
+
return data.data;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
toggleNotification({
|
|
143
|
+
type: 'danger',
|
|
144
|
+
message: err.response?.data?.error?.message || 'Failed to create realm',
|
|
145
|
+
});
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[client, toggleNotification, fetchAll]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Updates an existing realm configuration.
|
|
154
|
+
* Automatically refreshes the realms list on success.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} id - Realm document ID
|
|
157
|
+
* @param {Partial<RealmConfig>} realmData - Fields to update
|
|
158
|
+
* @returns {Promise<RealmConfig>} The updated realm
|
|
159
|
+
* @throws {Error} If update fails
|
|
160
|
+
*/
|
|
161
|
+
const update = useCallback(
|
|
162
|
+
async (id, realmData) => {
|
|
163
|
+
try {
|
|
164
|
+
const { data } = await client.put(`${API_BASE_PATH}/realms/${id}`, { data: realmData });
|
|
165
|
+
toggleNotification({
|
|
166
|
+
type: 'success',
|
|
167
|
+
message: 'Realm updated successfully',
|
|
168
|
+
});
|
|
169
|
+
await fetchAll();
|
|
170
|
+
return data.data;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
toggleNotification({
|
|
173
|
+
type: 'danger',
|
|
174
|
+
message: err.response?.data?.error?.message || 'Failed to update realm',
|
|
175
|
+
});
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
[client, toggleNotification, fetchAll]
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Deletes a realm configuration and all associated admin assignments.
|
|
184
|
+
* Automatically refreshes the realms list on success.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} id - Realm document ID
|
|
187
|
+
* @returns {Promise<void>}
|
|
188
|
+
* @throws {Error} If deletion fails
|
|
189
|
+
*/
|
|
190
|
+
const remove = useCallback(
|
|
191
|
+
async (id) => {
|
|
192
|
+
try {
|
|
193
|
+
await client.del(`${API_BASE_PATH}/realms/${id}`);
|
|
194
|
+
toggleNotification({
|
|
195
|
+
type: 'success',
|
|
196
|
+
message: 'Realm deleted successfully',
|
|
197
|
+
});
|
|
198
|
+
await fetchAll();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
toggleNotification({
|
|
201
|
+
type: 'danger',
|
|
202
|
+
message: err.response?.data?.error?.message || 'Failed to delete realm',
|
|
203
|
+
});
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[client, toggleNotification, fetchAll]
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Tests the Keycloak connection for a saved realm.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} id - Realm document ID
|
|
214
|
+
* @returns {Promise<ConnectionTestResult>} Test result with success status
|
|
215
|
+
*/
|
|
216
|
+
const testConnection = useCallback(
|
|
217
|
+
async (id) => {
|
|
218
|
+
try {
|
|
219
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms/${id}/test`);
|
|
220
|
+
return data.data;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
return { success: false, message: err.response?.data?.error?.message || 'Connection failed' };
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
[client]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tests a Keycloak connection with raw configuration data.
|
|
230
|
+
* Useful for validating credentials before saving a realm.
|
|
231
|
+
*
|
|
232
|
+
* @param {Object} config - Raw connection configuration
|
|
233
|
+
* @param {string} config.serverUrl - Keycloak server URL
|
|
234
|
+
* @param {string} config.realmName - Keycloak realm name
|
|
235
|
+
* @param {string} config.clientId - OAuth client ID
|
|
236
|
+
* @param {string} config.clientSecret - OAuth client secret
|
|
237
|
+
* @returns {Promise<ConnectionTestResult>} Test result with success status
|
|
238
|
+
*/
|
|
239
|
+
const testConnectionRaw = useCallback(
|
|
240
|
+
async (config) => {
|
|
241
|
+
try {
|
|
242
|
+
const { data } = await client.post(`${API_BASE_PATH}/realms/test-connection`, { data: config });
|
|
243
|
+
return data.data;
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return { success: false, message: err.response?.data?.error?.message || 'Connection failed' };
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
[client]
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Fetch realms on mount
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
fetchAll();
|
|
254
|
+
}, [fetchAll]);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
realms,
|
|
258
|
+
isLoading,
|
|
259
|
+
fetchAll,
|
|
260
|
+
fetchOne,
|
|
261
|
+
create,
|
|
262
|
+
update,
|
|
263
|
+
remove,
|
|
264
|
+
testConnection,
|
|
265
|
+
testConnectionRaw,
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export default useRealms;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PLUGIN_ID } from './pluginId';
|
|
2
|
+
import Initializer from './components/Initializer';
|
|
3
|
+
import { getTrad } from './constants';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
register(app) {
|
|
7
|
+
app.registerPlugin({
|
|
8
|
+
id: PLUGIN_ID,
|
|
9
|
+
initializer: Initializer,
|
|
10
|
+
isReady: false,
|
|
11
|
+
name: PLUGIN_ID,
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
bootstrap(app) {
|
|
16
|
+
// Add settings link
|
|
17
|
+
app.addSettingsLink('global', {
|
|
18
|
+
id: PLUGIN_ID,
|
|
19
|
+
to: PLUGIN_ID,
|
|
20
|
+
intlLabel: {
|
|
21
|
+
id: getTrad('settings.title'),
|
|
22
|
+
defaultMessage: 'Keycloak Users',
|
|
23
|
+
},
|
|
24
|
+
Component: () => import('./pages/App'),
|
|
25
|
+
permissions: [],
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async registerTrads({ locales }) {
|
|
30
|
+
const importedTrads = await Promise.all(
|
|
31
|
+
locales.map((locale) => {
|
|
32
|
+
return import(`./translations/${locale}.json`)
|
|
33
|
+
.then(({ default: data }) => ({
|
|
34
|
+
data,
|
|
35
|
+
locale,
|
|
36
|
+
}))
|
|
37
|
+
.catch(() => ({
|
|
38
|
+
data: {},
|
|
39
|
+
locale,
|
|
40
|
+
}));
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return importedTrads;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router-dom';
|
|
2
|
+
|
|
3
|
+
import RealmsPage from './RealmsPage';
|
|
4
|
+
import UsersPage from './UsersPage';
|
|
5
|
+
import AuditPage from './AuditPage';
|
|
6
|
+
|
|
7
|
+
const App = () => {
|
|
8
|
+
return (
|
|
9
|
+
<Routes>
|
|
10
|
+
<Route index element={<RealmsPage.List />} />
|
|
11
|
+
<Route path="realms/create" element={<RealmsPage.Edit />} />
|
|
12
|
+
<Route path="realms/:id" element={<RealmsPage.Edit />} />
|
|
13
|
+
<Route path="realms/:realmId/users" element={<UsersPage.List />} />
|
|
14
|
+
<Route path="realms/:realmId/users/create" element={<UsersPage.Edit />} />
|
|
15
|
+
<Route path="realms/:realmId/users/:userId" element={<UsersPage.Edit />} />
|
|
16
|
+
<Route path="audit" element={<AuditPage />} />
|
|
17
|
+
</Routes>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default App;
|