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,413 @@
|
|
|
1
|
+
import { PLUGIN_ID, SERVICES, ERROR_MESSAGES } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
const userController = ({ strapi }) => ({
|
|
4
|
+
/**
|
|
5
|
+
* List users in a realm
|
|
6
|
+
*/
|
|
7
|
+
async find(ctx) {
|
|
8
|
+
const user = ctx.state.user;
|
|
9
|
+
const { realmId } = ctx.params;
|
|
10
|
+
const { search, page = 1, pageSize = 25 } = ctx.query;
|
|
11
|
+
|
|
12
|
+
if (!user) {
|
|
13
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
18
|
+
const result = await userService.listUsers(
|
|
19
|
+
realmId,
|
|
20
|
+
{ search, page: parseInt(page, 10), pageSize: parseInt(pageSize, 10) },
|
|
21
|
+
user
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
ctx.body = { data: result };
|
|
25
|
+
} catch (err) {
|
|
26
|
+
strapi.log.error(`[${PLUGIN_ID}] user.find error:`, err);
|
|
27
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get a single user
|
|
33
|
+
*/
|
|
34
|
+
async findOne(ctx) {
|
|
35
|
+
const user = ctx.state.user;
|
|
36
|
+
const { realmId, id } = ctx.params;
|
|
37
|
+
|
|
38
|
+
if (!user) {
|
|
39
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
44
|
+
const keycloakUser = await userService.getUser(realmId, id, user);
|
|
45
|
+
|
|
46
|
+
ctx.body = { data: keycloakUser };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
strapi.log.error(`[${PLUGIN_ID}] user.findOne error:`, err);
|
|
49
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a new user
|
|
55
|
+
*/
|
|
56
|
+
async create(ctx) {
|
|
57
|
+
const user = ctx.state.user;
|
|
58
|
+
const { realmId } = ctx.params;
|
|
59
|
+
const { data } = ctx.request.body;
|
|
60
|
+
|
|
61
|
+
if (!user) {
|
|
62
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
67
|
+
const keycloakUser = await userService.createUser(realmId, data, user);
|
|
68
|
+
|
|
69
|
+
ctx.body = { data: keycloakUser };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
strapi.log.error(`[${PLUGIN_ID}] user.create error:`, err);
|
|
72
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Update a user
|
|
78
|
+
*/
|
|
79
|
+
async update(ctx) {
|
|
80
|
+
const user = ctx.state.user;
|
|
81
|
+
const { realmId, id } = ctx.params;
|
|
82
|
+
const { data } = ctx.request.body;
|
|
83
|
+
|
|
84
|
+
if (!user) {
|
|
85
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
90
|
+
const keycloakUser = await userService.updateUser(realmId, id, data, user);
|
|
91
|
+
|
|
92
|
+
ctx.body = { data: keycloakUser };
|
|
93
|
+
} catch (err) {
|
|
94
|
+
strapi.log.error(`[${PLUGIN_ID}] user.update error:`, err);
|
|
95
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Delete a user
|
|
101
|
+
*/
|
|
102
|
+
async delete(ctx) {
|
|
103
|
+
const user = ctx.state.user;
|
|
104
|
+
const { realmId, id } = ctx.params;
|
|
105
|
+
|
|
106
|
+
if (!user) {
|
|
107
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
112
|
+
await userService.deleteUser(realmId, id, user);
|
|
113
|
+
|
|
114
|
+
ctx.body = { data: { success: true } };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
strapi.log.error(`[${PLUGIN_ID}] user.delete error:`, err);
|
|
117
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reset user password
|
|
123
|
+
*/
|
|
124
|
+
async resetPassword(ctx) {
|
|
125
|
+
const user = ctx.state.user;
|
|
126
|
+
const { realmId, id } = ctx.params;
|
|
127
|
+
const { data } = ctx.request.body;
|
|
128
|
+
|
|
129
|
+
if (!user) {
|
|
130
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const { password, temporary = true } = data;
|
|
135
|
+
|
|
136
|
+
if (!password) {
|
|
137
|
+
return ctx.badRequest('Password is required.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
141
|
+
await userService.resetPassword(realmId, id, password, temporary, user);
|
|
142
|
+
|
|
143
|
+
ctx.body = { data: { success: true } };
|
|
144
|
+
} catch (err) {
|
|
145
|
+
strapi.log.error(`[${PLUGIN_ID}] user.resetPassword error:`, err);
|
|
146
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Enable a user
|
|
152
|
+
*/
|
|
153
|
+
async enable(ctx) {
|
|
154
|
+
const user = ctx.state.user;
|
|
155
|
+
const { realmId, id } = ctx.params;
|
|
156
|
+
|
|
157
|
+
if (!user) {
|
|
158
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
163
|
+
await userService.enableUser(realmId, id, user);
|
|
164
|
+
|
|
165
|
+
ctx.body = { data: { success: true } };
|
|
166
|
+
} catch (err) {
|
|
167
|
+
strapi.log.error(`[${PLUGIN_ID}] user.enable error:`, err);
|
|
168
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Disable a user
|
|
174
|
+
*/
|
|
175
|
+
async disable(ctx) {
|
|
176
|
+
const user = ctx.state.user;
|
|
177
|
+
const { realmId, id } = ctx.params;
|
|
178
|
+
|
|
179
|
+
if (!user) {
|
|
180
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
185
|
+
await userService.disableUser(realmId, id, user);
|
|
186
|
+
|
|
187
|
+
ctx.body = { data: { success: true } };
|
|
188
|
+
} catch (err) {
|
|
189
|
+
strapi.log.error(`[${PLUGIN_ID}] user.disable error:`, err);
|
|
190
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Send verification email
|
|
196
|
+
*/
|
|
197
|
+
async sendVerifyEmail(ctx) {
|
|
198
|
+
const user = ctx.state.user;
|
|
199
|
+
const { realmId, id } = ctx.params;
|
|
200
|
+
|
|
201
|
+
if (!user) {
|
|
202
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
207
|
+
await userService.sendVerificationEmail(realmId, id, user);
|
|
208
|
+
|
|
209
|
+
ctx.body = { data: { success: true } };
|
|
210
|
+
} catch (err) {
|
|
211
|
+
strapi.log.error(`[${PLUGIN_ID}] user.sendVerifyEmail error:`, err);
|
|
212
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Send password reset email
|
|
218
|
+
*/
|
|
219
|
+
async sendResetPasswordEmail(ctx) {
|
|
220
|
+
const user = ctx.state.user;
|
|
221
|
+
const { realmId, id } = ctx.params;
|
|
222
|
+
|
|
223
|
+
if (!user) {
|
|
224
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
229
|
+
await userService.sendResetPasswordEmail(realmId, id, user);
|
|
230
|
+
|
|
231
|
+
ctx.body = { data: { success: true } };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
strapi.log.error(`[${PLUGIN_ID}] user.sendResetPasswordEmail error:`, err);
|
|
234
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get realm roles
|
|
240
|
+
*/
|
|
241
|
+
async getRoles(ctx) {
|
|
242
|
+
const user = ctx.state.user;
|
|
243
|
+
const { realmId } = ctx.params;
|
|
244
|
+
|
|
245
|
+
if (!user) {
|
|
246
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
251
|
+
const roles = await userService.getRoles(realmId, user);
|
|
252
|
+
|
|
253
|
+
ctx.body = { data: roles };
|
|
254
|
+
} catch (err) {
|
|
255
|
+
strapi.log.error(`[${PLUGIN_ID}] user.getRoles error:`, err);
|
|
256
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get user's roles
|
|
262
|
+
*/
|
|
263
|
+
async getUserRoles(ctx) {
|
|
264
|
+
const user = ctx.state.user;
|
|
265
|
+
const { realmId, id } = ctx.params;
|
|
266
|
+
|
|
267
|
+
if (!user) {
|
|
268
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
273
|
+
const roles = await userService.getUserRoles(realmId, id, user);
|
|
274
|
+
|
|
275
|
+
ctx.body = { data: roles };
|
|
276
|
+
} catch (err) {
|
|
277
|
+
strapi.log.error(`[${PLUGIN_ID}] user.getUserRoles error:`, err);
|
|
278
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Assign roles to user
|
|
284
|
+
*/
|
|
285
|
+
async assignRoles(ctx) {
|
|
286
|
+
const user = ctx.state.user;
|
|
287
|
+
const { realmId, id } = ctx.params;
|
|
288
|
+
const { data } = ctx.request.body;
|
|
289
|
+
|
|
290
|
+
if (!user) {
|
|
291
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const { roles } = data;
|
|
296
|
+
|
|
297
|
+
if (!roles || !Array.isArray(roles)) {
|
|
298
|
+
return ctx.badRequest('Roles array is required.');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
302
|
+
await userService.assignRoles(realmId, id, roles, user);
|
|
303
|
+
|
|
304
|
+
ctx.body = { data: { success: true } };
|
|
305
|
+
} catch (err) {
|
|
306
|
+
strapi.log.error(`[${PLUGIN_ID}] user.assignRoles error:`, err);
|
|
307
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Remove roles from user
|
|
313
|
+
*/
|
|
314
|
+
async removeRoles(ctx) {
|
|
315
|
+
const user = ctx.state.user;
|
|
316
|
+
const { realmId, id } = ctx.params;
|
|
317
|
+
const { data } = ctx.request.body;
|
|
318
|
+
|
|
319
|
+
if (!user) {
|
|
320
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const { roles } = data;
|
|
325
|
+
|
|
326
|
+
if (!roles || !Array.isArray(roles)) {
|
|
327
|
+
return ctx.badRequest('Roles array is required.');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
331
|
+
await userService.removeRoles(realmId, id, roles, user);
|
|
332
|
+
|
|
333
|
+
ctx.body = { data: { success: true } };
|
|
334
|
+
} catch (err) {
|
|
335
|
+
strapi.log.error(`[${PLUGIN_ID}] user.removeRoles error:`, err);
|
|
336
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Bulk import users
|
|
342
|
+
*/
|
|
343
|
+
async bulkImport(ctx) {
|
|
344
|
+
const user = ctx.state.user;
|
|
345
|
+
const { realmId } = ctx.params;
|
|
346
|
+
const { data } = ctx.request.body;
|
|
347
|
+
|
|
348
|
+
if (!user) {
|
|
349
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const { users } = data;
|
|
354
|
+
|
|
355
|
+
if (!users || !Array.isArray(users)) {
|
|
356
|
+
return ctx.badRequest('Users array is required.');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
360
|
+
const result = await userService.bulkImport(realmId, users, user);
|
|
361
|
+
|
|
362
|
+
ctx.body = { data: result };
|
|
363
|
+
} catch (err) {
|
|
364
|
+
strapi.log.error(`[${PLUGIN_ID}] user.bulkImport error:`, err);
|
|
365
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Export users
|
|
371
|
+
*/
|
|
372
|
+
async exportUsers(ctx) {
|
|
373
|
+
const user = ctx.state.user;
|
|
374
|
+
const { realmId } = ctx.params;
|
|
375
|
+
const { format = 'json' } = ctx.query;
|
|
376
|
+
|
|
377
|
+
if (!user) {
|
|
378
|
+
return ctx.unauthorized(ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const userService = strapi.plugin(PLUGIN_ID).service(SERVICES.USER);
|
|
383
|
+
const users = await userService.exportUsers(realmId, user);
|
|
384
|
+
|
|
385
|
+
if (format === 'csv') {
|
|
386
|
+
// Convert to CSV
|
|
387
|
+
const headers = ['id', 'username', 'email', 'firstName', 'lastName', 'enabled', 'emailVerified'];
|
|
388
|
+
const csvRows = [headers.join(',')];
|
|
389
|
+
|
|
390
|
+
for (const u of users) {
|
|
391
|
+
const row = headers.map((h) => {
|
|
392
|
+
const val = u[h];
|
|
393
|
+
if (val === undefined || val === null) return '';
|
|
394
|
+
if (typeof val === 'string' && val.includes(',')) return `"${val}"`;
|
|
395
|
+
return String(val);
|
|
396
|
+
});
|
|
397
|
+
csvRows.push(row.join(','));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
ctx.set('Content-Type', 'text/csv');
|
|
401
|
+
ctx.set('Content-Disposition', `attachment; filename="keycloak-users-${realmId}.csv"`);
|
|
402
|
+
ctx.body = csvRows.join('\n');
|
|
403
|
+
} else {
|
|
404
|
+
ctx.body = { data: users };
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
strapi.log.error(`[${PLUGIN_ID}] user.exportUsers error:`, err);
|
|
408
|
+
ctx.throw(400, err.sanitizedMessage || ERROR_MESSAGES.UNKNOWN_ERROR);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
export default userController;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import bootstrap from './bootstrap.js';
|
|
2
|
+
import register from './register.js';
|
|
3
|
+
import destroy from './destroy.js';
|
|
4
|
+
import config from './config/index.js';
|
|
5
|
+
import contentTypes from './content-types/index.js';
|
|
6
|
+
import controllers from './controllers/index.js';
|
|
7
|
+
import services from './services/index.js';
|
|
8
|
+
import policies from './policies/index.js';
|
|
9
|
+
import routes from './routes/index.js';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
bootstrap,
|
|
13
|
+
register,
|
|
14
|
+
destroy,
|
|
15
|
+
config,
|
|
16
|
+
contentTypes,
|
|
17
|
+
controllers,
|
|
18
|
+
services,
|
|
19
|
+
policies,
|
|
20
|
+
routes,
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PLUGIN_ID, SERVICES } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
const canAccessRealm = async (policyContext, config, { strapi }) => {
|
|
4
|
+
const { realmId, id } = policyContext.params;
|
|
5
|
+
const user = policyContext.state.user;
|
|
6
|
+
|
|
7
|
+
// Determine which parameter holds the realm ID
|
|
8
|
+
const targetRealmId = realmId || id;
|
|
9
|
+
|
|
10
|
+
if (!user || !targetRealmId) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const permissionService = strapi.plugin(PLUGIN_ID).service(SERVICES.PERMISSION);
|
|
15
|
+
|
|
16
|
+
// Super admins can access all realms
|
|
17
|
+
if (permissionService.isSuperAdmin(user)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Check specific permission
|
|
22
|
+
const permission = config?.permission || 'canRead';
|
|
23
|
+
return permissionService.canAccessRealm(user, targetRealmId, permission);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default canAccessRealm;
|