@culturefy/shared 1.0.28 → 1.0.29

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.
Files changed (106) hide show
  1. package/build/cjs/dto/index.js +8 -0
  2. package/build/cjs/dto/index.js.map +1 -1
  3. package/build/cjs/index.js +6 -0
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/cjs/interfaces/index.js +16 -0
  6. package/build/cjs/interfaces/index.js.map +1 -0
  7. package/build/cjs/interfaces/keycloak.js +2 -0
  8. package/build/cjs/interfaces/keycloak.js.map +1 -0
  9. package/build/cjs/interfaces/user.js +2 -0
  10. package/build/cjs/interfaces/user.js.map +1 -0
  11. package/build/cjs/middlewares/index.js +10 -0
  12. package/build/cjs/middlewares/index.js.map +1 -0
  13. package/build/cjs/middlewares/token-validation.js +485 -0
  14. package/build/cjs/middlewares/token-validation.js.map +1 -0
  15. package/build/cjs/models/user.model.js +507 -0
  16. package/build/cjs/models/user.model.js.map +1 -0
  17. package/build/cjs/service/keycloak.service.js +1003 -0
  18. package/build/cjs/service/keycloak.service.js.map +1 -0
  19. package/build/cjs/service/user.service.js +95 -0
  20. package/build/cjs/service/user.service.js.map +1 -0
  21. package/build/cjs/utils/index.js +6 -0
  22. package/build/cjs/utils/index.js.map +1 -1
  23. package/build/cjs/utils/jwt.js +14 -0
  24. package/build/cjs/utils/jwt.js.map +1 -0
  25. package/build/cjs/utils/response.js +19 -0
  26. package/build/cjs/utils/response.js.map +1 -1
  27. package/build/esm/dto/index.js +1 -1
  28. package/build/esm/dto/index.js.map +1 -1
  29. package/build/esm/index.js +1 -0
  30. package/build/esm/index.js.map +1 -1
  31. package/build/esm/interfaces/index.js +3 -0
  32. package/build/esm/interfaces/index.js.map +1 -0
  33. package/build/esm/interfaces/keycloak.js +2 -0
  34. package/build/esm/interfaces/keycloak.js.map +1 -0
  35. package/build/esm/interfaces/user.js +2 -0
  36. package/build/esm/interfaces/user.js.map +1 -0
  37. package/build/esm/middlewares/index.js +2 -0
  38. package/build/esm/middlewares/index.js.map +1 -0
  39. package/build/esm/middlewares/token-validation.js +481 -0
  40. package/build/esm/middlewares/token-validation.js.map +1 -0
  41. package/build/esm/models/user.model.js +497 -0
  42. package/build/esm/models/user.model.js.map +1 -0
  43. package/build/esm/service/keycloak.service.js +996 -0
  44. package/build/esm/service/keycloak.service.js.map +1 -0
  45. package/build/esm/service/user.service.js +91 -0
  46. package/build/esm/service/user.service.js.map +1 -0
  47. package/build/esm/utils/index.js +1 -0
  48. package/build/esm/utils/index.js.map +1 -1
  49. package/build/esm/utils/jwt.js +10 -0
  50. package/build/esm/utils/jwt.js.map +1 -0
  51. package/build/esm/utils/response.js +18 -0
  52. package/build/esm/utils/response.js.map +1 -1
  53. package/build/package.json +13 -2
  54. package/build/src/dto/index.d.ts +1 -0
  55. package/build/src/dto/index.js +3 -0
  56. package/build/src/dto/index.js.map +1 -1
  57. package/build/src/index.d.ts +1 -0
  58. package/build/src/index.js +1 -0
  59. package/build/src/index.js.map +1 -1
  60. package/build/src/interfaces/index.d.ts +2 -0
  61. package/build/src/interfaces/index.js +6 -0
  62. package/build/src/interfaces/index.js.map +1 -0
  63. package/build/src/interfaces/keycloak.d.ts +151 -0
  64. package/build/src/interfaces/keycloak.js +3 -0
  65. package/build/src/interfaces/keycloak.js.map +1 -0
  66. package/build/src/interfaces/user.d.ts +5 -0
  67. package/build/src/interfaces/user.js +3 -0
  68. package/build/src/interfaces/user.js.map +1 -0
  69. package/build/src/middlewares/index.d.ts +1 -0
  70. package/build/src/middlewares/index.js +5 -0
  71. package/build/src/middlewares/index.js.map +1 -0
  72. package/build/src/middlewares/token-validation.d.ts +17 -0
  73. package/build/src/middlewares/token-validation.js +329 -0
  74. package/build/src/middlewares/token-validation.js.map +1 -0
  75. package/build/src/models/user.model.d.ts +193 -0
  76. package/build/src/models/user.model.js +401 -0
  77. package/build/src/models/user.model.js.map +1 -0
  78. package/build/src/service/keycloak.service.d.ts +88 -0
  79. package/build/src/service/keycloak.service.js +1059 -0
  80. package/build/src/service/keycloak.service.js.map +1 -0
  81. package/build/src/service/user.service.d.ts +11 -0
  82. package/build/src/service/user.service.js +118 -0
  83. package/build/src/service/user.service.js.map +1 -0
  84. package/build/src/utils/index.d.ts +1 -0
  85. package/build/src/utils/index.js +1 -0
  86. package/build/src/utils/index.js.map +1 -1
  87. package/build/src/utils/jwt.d.ts +1 -0
  88. package/build/src/utils/jwt.js +14 -0
  89. package/build/src/utils/jwt.js.map +1 -0
  90. package/build/src/utils/response.d.ts +1 -0
  91. package/build/src/utils/response.js +19 -0
  92. package/build/src/utils/response.js.map +1 -1
  93. package/package.json +13 -2
  94. package/src/dto/index.ts +1 -0
  95. package/src/index.ts +2 -1
  96. package/src/interfaces/index.ts +2 -0
  97. package/src/interfaces/keycloak.ts +161 -0
  98. package/src/interfaces/user.ts +5 -0
  99. package/src/middlewares/index.ts +1 -0
  100. package/src/middlewares/token-validation.ts +456 -0
  101. package/src/models/user.model.ts +488 -0
  102. package/src/service/keycloak.service.ts +1104 -0
  103. package/src/service/user.service.ts +99 -0
  104. package/src/utils/index.ts +2 -1
  105. package/src/utils/jwt.ts +10 -0
  106. package/src/utils/response.ts +19 -0
@@ -0,0 +1,1003 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.KeycloakAdminService = void 0;
5
+ var _axios = _interopRequireDefault(require("axios"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ // keycloak-admin.service.ts
8
+
9
+ class KeycloakAdminService {
10
+ constructor(context, config) {
11
+ this.context = void 0;
12
+ this.baseUrl = void 0;
13
+ this.adminClientId = void 0;
14
+ this.adminClientSecret = void 0;
15
+ this.axiosInstance = void 0;
16
+ this.accessToken = null;
17
+ this.context = context;
18
+ if (!config.baseUrl || !config.adminClientId || !config.adminClientSecret) {
19
+ this.context.error("Missing required configuration");
20
+ throw new Error('Missing required configuration');
21
+ }
22
+ this.baseUrl = config.baseUrl;
23
+ this.adminClientId = config.adminClientId;
24
+ this.adminClientSecret = config.adminClientSecret;
25
+ try {
26
+ this.axiosInstance = _axios.default.create({
27
+ baseURL: this.baseUrl
28
+ });
29
+ } catch (error) {
30
+ this.context.error("Error in createAxiosInstance", error);
31
+ throw error;
32
+ }
33
+ }
34
+ async authenticate() {
35
+ try {
36
+ const params = new URLSearchParams();
37
+ params.append('grant_type', 'client_credentials');
38
+ params.append('client_id', this.adminClientId);
39
+ params.append('client_secret', this.adminClientSecret);
40
+ this.context.log(`Params: ${JSON.stringify(params)}`);
41
+ const response = await this.axiosInstance.post('/realms/master/protocol/openid-connect/token', params, {
42
+ headers: {
43
+ 'Content-Type': 'application/x-www-form-urlencoded'
44
+ }
45
+ });
46
+ if (response.status !== 200) {
47
+ throw new Error(`Failed to authenticate: ${JSON.stringify(response.data)}`);
48
+ }
49
+ // this.context.log(`Response: ${JSON.stringify(response.data)}`);
50
+
51
+ const data = response.data;
52
+ this.accessToken = data.access_token;
53
+ this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.accessToken}`;
54
+ return data;
55
+ } catch (error) {
56
+ this.context.error("Error in authenticate", error);
57
+ throw error;
58
+ }
59
+ }
60
+ async authenticateRelmClient(realm, clientId) {
61
+ try {
62
+ const clientUUID = await this.getClientUUID(realm, clientId);
63
+ if (!clientUUID) {
64
+ throw new Error(`Client UUID for "${clientId}" not found`);
65
+ }
66
+ const clientSecret = await this.getClientSecret(realm, clientId);
67
+ if (!clientSecret) {
68
+ throw new Error(`Client secret for "${clientId}" not found`);
69
+ }
70
+ const params = new URLSearchParams();
71
+ params.append('grant_type', 'client_credentials');
72
+ params.append('client_id', clientId);
73
+ params.append('client_secret', clientSecret);
74
+ const response = await this.axiosInstance.post(`/realms/${realm}/protocol/openid-connect/token`, params, {
75
+ headers: {
76
+ 'Content-Type': 'application/x-www-form-urlencoded'
77
+ }
78
+ });
79
+ if (response.status !== 200) {
80
+ throw new Error(`Failed to authenticate realm client: ${JSON.stringify(response.data)}`);
81
+ }
82
+ // this.context.log(`Response: ${JSON.stringify(response.data)}`);
83
+
84
+ const data = response.data;
85
+ this.accessToken = data.access_token;
86
+ this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.accessToken}`;
87
+ return data;
88
+ } catch (error) {
89
+ this.context.error("Error in authenticateRelmClient", error);
90
+ throw error;
91
+ }
92
+ }
93
+ async createRealm(realm) {
94
+ try {
95
+ var _realm$enabled;
96
+ this.context.log(`Realm: ${JSON.stringify(realm)}`);
97
+ await this.authenticate();
98
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
99
+ const response = await this.axiosInstance.post('/admin/realms', {
100
+ realm: realm.realm,
101
+ id: realm.realm,
102
+ displayName: realm.displayName || realm.realm,
103
+ enabled: (_realm$enabled = realm.enabled) != null ? _realm$enabled : true,
104
+ sslRequired: 'external',
105
+ bruteForceProtected: true,
106
+ eventsEnabled: false
107
+ });
108
+ if (response.status !== 201) {
109
+ throw new Error(`Failed to create realm: ${JSON.stringify(response.data)}`);
110
+ }
111
+ this.context.log(`Response: Successfully created realm`);
112
+ return;
113
+ } catch (error) {
114
+ this.context.error("Error in createRealm", error);
115
+ throw error;
116
+ }
117
+ }
118
+ async createClient(realm, client) {
119
+ try {
120
+ var _client$publicClient, _client$directAccessG, _client$standardFlowE, _client$implicitFlowE, _client$serviceAccoun, _client$authorization, _client$bearerOnly, _client$consentRequir, _client$fullScopeAllo;
121
+ this.context.log(`Realm name: ${realm}`);
122
+ this.context.log(`Creating client: ${JSON.stringify(client)}`);
123
+ await this.authenticate();
124
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
125
+ const response = await this.axiosInstance.post(`/admin/realms/${realm}/clients`, {
126
+ clientId: client.clientId,
127
+ name: client.name,
128
+ description: client.description,
129
+ enabled: client.enabled,
130
+ protocol: client.protocol || 'openid-connect',
131
+ publicClient: (_client$publicClient = client.publicClient) != null ? _client$publicClient : false,
132
+ secret: client.secret || null,
133
+ directAccessGrantsEnabled: (_client$directAccessG = client.directAccessGrantsEnabled) != null ? _client$directAccessG : true,
134
+ standardFlowEnabled: (_client$standardFlowE = client.standardFlowEnabled) != null ? _client$standardFlowE : true,
135
+ implicitFlowEnabled: (_client$implicitFlowE = client.implicitFlowEnabled) != null ? _client$implicitFlowE : false,
136
+ serviceAccountsEnabled: (_client$serviceAccoun = client.serviceAccountsEnabled) != null ? _client$serviceAccoun : true,
137
+ authorizationServicesEnabled: (_client$authorization = client.authorizationServicesEnabled) != null ? _client$authorization : true,
138
+ redirectUris: client.redirectUris || [],
139
+ webOrigins: client.webOrigins || [],
140
+ bearerOnly: (_client$bearerOnly = client.bearerOnly) != null ? _client$bearerOnly : false,
141
+ consentRequired: (_client$consentRequir = client.consentRequired) != null ? _client$consentRequir : false,
142
+ fullScopeAllowed: (_client$fullScopeAllo = client.fullScopeAllowed) != null ? _client$fullScopeAllo : true,
143
+ attributes: client.attributes || {
144
+ tls_client_certificate_bound_access_tokens: "false",
145
+ client_credentials_use_refresh_token: "true"
146
+ }
147
+ });
148
+ if (response.status !== 201) {
149
+ throw new Error(`Failed to create client: ${JSON.stringify(response.data)}`);
150
+ }
151
+ this.context.log(`Response: ${JSON.stringify(response.data)}`);
152
+ return;
153
+ } catch (error) {
154
+ this.context.error("Error in createClient", error);
155
+ throw error;
156
+ }
157
+ }
158
+ async getClient(realm, clientId, clientUUID) {
159
+ try {
160
+ this.context.log(`Realm: ${realm}`);
161
+ this.context.log(`Client ID: ${clientId}`);
162
+ await this.authenticateRelmClient(realm, clientId);
163
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
164
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/clients/${clientUUID}`);
165
+ if (response.status !== 200) {
166
+ throw new Error(`Failed to get client: ${JSON.stringify(response.data)}`);
167
+ }
168
+ this.context.log(`(getClient) Client: ${JSON.stringify(response.data)}`);
169
+ const data = response.data;
170
+ return data;
171
+ } catch (error) {
172
+ this.context.error("Error in getClient", error);
173
+ throw error;
174
+ }
175
+ }
176
+ async getClientUUID(realm, clientId) {
177
+ try {
178
+ this.context.log(`Realm: ${realm}`);
179
+ this.context.log(`Client ID: ${clientId}`);
180
+ await this.authenticate();
181
+ // this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
182
+
183
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/clients`, {
184
+ params: {
185
+ clientId
186
+ }
187
+ });
188
+ if (response.status !== 200) {
189
+ throw new Error(`Failed to get clients: ${JSON.stringify(response.data)}`);
190
+ }
191
+ this.context.log(`Clients: ${JSON.stringify(response.data)}`);
192
+ const data = response.data;
193
+ const client = data.find(c => c.clientId == clientId);
194
+ this.context.log(`(getClientUUID) Client: ${JSON.stringify(client)}`);
195
+ if (!client) {
196
+ throw new Error(`Client "${clientId}" not found`);
197
+ }
198
+ return client.id;
199
+ } catch (error) {
200
+ this.context.error("Error in getClientUUID", error);
201
+ throw error;
202
+ }
203
+ }
204
+ async getServiceAccountUserId(realm, clientUUID) {
205
+ try {
206
+ await this.authenticate();
207
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
208
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/clients/${clientUUID}/service-account-user`);
209
+ if (response.status !== 200) {
210
+ throw new Error(`Failed to get service account user: ${JSON.stringify(response.data)}`);
211
+ }
212
+ this.context.log(`Service account user: ${JSON.stringify(response.data)}`);
213
+ return response.data.id;
214
+ } catch (error) {
215
+ this.context.error("Error in getServiceAccountUserId", error);
216
+ throw error;
217
+ }
218
+ }
219
+ async getAllRoles(realm, clientUUID) {
220
+ try {
221
+ await this.authenticate();
222
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
223
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/clients/${clientUUID}/roles`);
224
+ if (response.status !== 200) {
225
+ throw new Error(`Failed to get all roles: ${JSON.stringify(response.data)}`);
226
+ }
227
+ this.context.log(`All roles: ${JSON.stringify(response.data)}`);
228
+ const data = response.data;
229
+ return data;
230
+ } catch (error) {
231
+ this.context.error("Error in getAllRoles", error);
232
+ throw error;
233
+ }
234
+ }
235
+ async getAssignedRoles(realm, userId, clientUUID) {
236
+ try {
237
+ await this.authenticate();
238
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
239
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users/${userId}/role-mappings/clients/${clientUUID}`);
240
+ if (response.status !== 200) {
241
+ throw new Error(`Failed to get assigned roles: ${JSON.stringify(response.data)}`);
242
+ }
243
+ this.context.log(`Assigned roles: ${JSON.stringify(response.data)}`);
244
+ const data = response.data;
245
+ return data;
246
+ } catch (error) {
247
+ this.context.error("Error in getAssignedRoles", error);
248
+ throw error;
249
+ }
250
+ }
251
+ async assignRolesToServiceAccount(realm, userId, clientUUID, roles) {
252
+ try {
253
+ await this.authenticate();
254
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
255
+
256
+ // According to Keycloak REST API documentation, we need to send role objects, not just IDs
257
+ const roleMappings = roles.map(role => ({
258
+ id: role.id,
259
+ name: role.name,
260
+ description: role.description,
261
+ composite: role.composite,
262
+ clientRole: role.clientRole,
263
+ containerId: role.containerId
264
+ }));
265
+ const response = await this.axiosInstance.post(`/admin/realms/${realm}/users/${userId}/role-mappings/clients/${clientUUID}`, roleMappings);
266
+ if (response.status !== 204) {
267
+ throw new Error(`Failed to assign roles to service account: ${JSON.stringify(response.data)}`);
268
+ }
269
+ this.context.log(`Response: ${JSON.stringify(response.data)}`);
270
+ return;
271
+ } catch (error) {
272
+ this.context.error("Error in assignRolesToServiceAccount", error);
273
+ throw error;
274
+ }
275
+ }
276
+ async assignAllRealmMgmtRolesToClient(realm, clientId) {
277
+ try {
278
+ this.context.log(`🔍 Getting UUID for clientId = ${clientId}`);
279
+ const clientUUID = await this.getClientUUID(realm, clientId);
280
+ this.context.log(`🔍 Getting UUID for realm-management`);
281
+ const realmMgmtUUID = await this.getClientUUID(realm, "realm-management");
282
+ this.context.log(`🔍 Getting service account user for ${clientId}`);
283
+ const serviceUserId = await this.getServiceAccountUserId(realm, clientUUID);
284
+ this.context.log(`🔍 Fetching all roles from realm-management`);
285
+ const allRoles = await this.getAllRoles(realm, realmMgmtUUID);
286
+ this.context.log(`🔍 Fetching already assigned roles`);
287
+ const assignedRoles = await this.getAssignedRoles(realm, serviceUserId, realmMgmtUUID);
288
+ const assignedNames = new Set(assignedRoles.map(r => r.name));
289
+ const unassignedRoles = allRoles.filter(r => !assignedNames.has(r.name));
290
+ this.context.log(`✅ Found ${unassignedRoles.length} unassigned roles`);
291
+ if (unassignedRoles.length) {
292
+ this.context.log(`🚀 Assigning roles to service account`);
293
+ await this.assignRolesToServiceAccount(realm, serviceUserId, realmMgmtUUID, unassignedRoles);
294
+ this.context.log("✅ Roles successfully assigned!");
295
+ } else {
296
+ this.context.log("ℹ️ All roles are already assigned.");
297
+ }
298
+ } catch (error) {
299
+ this.context.error("Error in assignAllRealmMgmtRolesToClient", error);
300
+ throw error;
301
+ }
302
+ }
303
+ async assignAllAccountMgmtRolesToClient(realm, clientId) {
304
+ try {
305
+ this.context.log(`🔍 Getting UUID for clientId = ${clientId}`);
306
+ const clientUUID = await this.getClientUUID(realm, clientId);
307
+ this.context.log(`🔍 Getting UUID for account`);
308
+ const accountMgmtUUID = await this.getClientUUID(realm, "account");
309
+ this.context.log(`🔍 Getting service account user for ${clientId}`);
310
+ const serviceUserId = await this.getServiceAccountUserId(realm, clientUUID);
311
+ this.context.log(`🔍 Fetching all roles from account`);
312
+ const allRoles = await this.getAllRoles(realm, accountMgmtUUID);
313
+ this.context.log(`🔍 Fetching already assigned roles`);
314
+ const assignedRoles = await this.getAssignedRoles(realm, serviceUserId, accountMgmtUUID);
315
+ const assignedNames = new Set(assignedRoles.map(r => r.name));
316
+ const unassignedRoles = allRoles.filter(r => !assignedNames.has(r.name));
317
+ this.context.log(`✅ Found ${unassignedRoles.length} unassigned roles`);
318
+ if (unassignedRoles.length) {
319
+ this.context.log(`🚀 Assigning roles to service account`);
320
+ await this.assignRolesToServiceAccount(realm, serviceUserId, accountMgmtUUID, unassignedRoles);
321
+ this.context.log("✅ Roles successfully assigned!");
322
+ } else {
323
+ this.context.log("ℹ️ All roles are already assigned.");
324
+ }
325
+ } catch (error) {
326
+ this.context.error("Error in assignAllAccountMgmtRolesToClient", error);
327
+ throw error;
328
+ }
329
+ }
330
+ async assignAllBrokerMgmtRolesToClient(realm, clientId) {
331
+ try {
332
+ this.context.log(`🔍 Getting UUID for clientId = ${clientId}`);
333
+ const clientUUID = await this.getClientUUID(realm, clientId);
334
+ this.context.log(`🔍 Getting UUID for broker`);
335
+ const brokerMgmtUUID = await this.getClientUUID(realm, "broker");
336
+ this.context.log(`🔍 Getting service broker user for ${clientId}`);
337
+ const serviceUserId = await this.getServiceAccountUserId(realm, clientUUID);
338
+ this.context.log(`🔍 Fetching all roles from broker`);
339
+ const allRoles = await this.getAllRoles(realm, brokerMgmtUUID);
340
+ this.context.log(`🔍 Fetching already assigned roles`);
341
+ const assignedRoles = await this.getAssignedRoles(realm, serviceUserId, brokerMgmtUUID);
342
+ const assignedNames = new Set(assignedRoles.map(r => r.name));
343
+ const unassignedRoles = allRoles.filter(r => !assignedNames.has(r.name));
344
+ this.context.log(`✅ Found ${unassignedRoles.length} unassigned roles`);
345
+ if (unassignedRoles.length) {
346
+ this.context.log(`🚀 Assigning roles to service account`);
347
+ await this.assignRolesToServiceAccount(realm, serviceUserId, brokerMgmtUUID, unassignedRoles);
348
+ this.context.log("✅ Roles successfully assigned!");
349
+ } else {
350
+ this.context.log("ℹ️ All roles are already assigned.");
351
+ }
352
+ } catch (error) {
353
+ this.context.error("Error in assignAllBrokerMgmtRolesToClient", error);
354
+ throw error;
355
+ }
356
+ }
357
+ async getClientSecret(realm, clientId) {
358
+ try {
359
+ this.context.log(`Realm: ${realm}`);
360
+ this.context.log(`Client ID: ${clientId}`);
361
+ await this.authenticate();
362
+ // this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
363
+
364
+ const clientUUID = await this.getClientUUID(realm, clientId);
365
+ if (!clientUUID) {
366
+ throw new Error(`Failed to get UUID for client "${clientId}"`);
367
+ }
368
+ this.context.log(`Client UUID: ${clientUUID}`);
369
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/clients/${clientUUID}/client-secret`);
370
+ if (response.status !== 200) {
371
+ throw new Error(`Failed to get client secret: ${JSON.stringify(response.data)}`);
372
+ }
373
+ this.context.log(`Client secret: ${JSON.stringify(response.data)}`);
374
+ const data = response.data;
375
+ return data.value;
376
+ } catch (error) {
377
+ this.context.error("Error in getClientSecret", error);
378
+ throw error;
379
+ }
380
+ }
381
+ async createUser(realm, clientId, user) {
382
+ try {
383
+ this.context.log(`Realm name: ${realm}`);
384
+ this.context.log(`Client ID: ${clientId}`);
385
+ this.context.log(`User: ${JSON.stringify(user)}`);
386
+ await this.authenticateRelmClient(realm, clientId);
387
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
388
+ const response = await this.axiosInstance.post(`/admin/realms/${realm}/users`, {
389
+ username: user.username,
390
+ email: user.email,
391
+ firstName: user.firstName,
392
+ lastName: user.lastName,
393
+ enabled: true,
394
+ emailVerified: true,
395
+ credentials: [{
396
+ type: "password",
397
+ value: user.password,
398
+ temporary: true // true if user must reset on first login
399
+ }],
400
+ requiredActions: [
401
+ // Options: "VERIFY_EMAIL", "UPDATE_PROFILE", "UPDATE_PASSWORD", "CONFIGURE_TOTP"
402
+ "UPDATE_PASSWORD"],
403
+ realmRoles: [
404
+ // Optional: assign realm-level roles
405
+ "offline_access", "uma_authorization"],
406
+ groups: [
407
+ // Optional: assign to realm groups
408
+ // "/devs",
409
+ // "/admins"
410
+ ],
411
+ federationLink: null,
412
+ // for federated identity provider
413
+ totp: false,
414
+ // TOTP is not configured initially
415
+ disableableCredentialTypes: [],
416
+ // Optional advanced fields
417
+ notBefore: 0,
418
+ access: {
419
+ manage: true
420
+ }
421
+ });
422
+ if (response.status !== 201) {
423
+ throw new Error(`Failed to create user: ${JSON.stringify(response.data)}`);
424
+ }
425
+ this.context.log(`Response: ${JSON.stringify(response.data)}`);
426
+ return;
427
+ } catch (error) {
428
+ this.context.error("Error in createUser", error);
429
+ throw error;
430
+ }
431
+ }
432
+ async listUsers(realm) {
433
+ try {
434
+ await this.authenticate();
435
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
436
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users`);
437
+ if (response.status !== 200) {
438
+ throw new Error(`Failed to list users: ${JSON.stringify(response.data)}`);
439
+ }
440
+ this.context.log(`Users: ${JSON.stringify(response.data)}`);
441
+ const data = response.data;
442
+ return data;
443
+ } catch (error) {
444
+ this.context.error("Error in listUsers", error);
445
+ throw error;
446
+ }
447
+ }
448
+ async getUser(realm, userId) {
449
+ try {
450
+ await this.authenticate();
451
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
452
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users/${userId}`);
453
+ if (response.status !== 200) {
454
+ throw new Error(`Failed to get user: ${JSON.stringify(response.data)}`);
455
+ }
456
+ this.context.log(`User: ${JSON.stringify(response.data)}`);
457
+ const data = response.data;
458
+ return data;
459
+ } catch (error) {
460
+ this.context.error("Error in getUser", error);
461
+ throw error;
462
+ }
463
+ }
464
+ async getUserByUsername(realm, username) {
465
+ try {
466
+ await this.authenticate();
467
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
468
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users`, {
469
+ params: {
470
+ username
471
+ }
472
+ });
473
+ if (response.status !== 200) {
474
+ throw new Error(`Failed to get user by username: ${JSON.stringify(response.data)}`);
475
+ }
476
+ this.context.log(`User: ${JSON.stringify(response.data)}`);
477
+ if (response.data.length === 0) {
478
+ throw new Error(`User "${username}" not found`);
479
+ }
480
+ const data = response.data[0];
481
+ return data;
482
+ } catch (error) {
483
+ this.context.error("Error in getUserByUsername", error);
484
+ throw error;
485
+ }
486
+ }
487
+ async getUserByEmail(realm, email) {
488
+ try {
489
+ await this.authenticate();
490
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
491
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users`, {
492
+ params: {
493
+ email
494
+ }
495
+ });
496
+ if (response.status !== 200) {
497
+ throw new Error(`Failed to get user by email: ${JSON.stringify(response.data)}`);
498
+ }
499
+ this.context.log(`User: ${JSON.stringify(response.data)}`);
500
+ if (response.data.length === 0) {
501
+ throw new Error(`User "${email}" not found`);
502
+ }
503
+ const data = response.data[0];
504
+ return data;
505
+ } catch (error) {
506
+ this.context.error("Error in getUserByEmail", error);
507
+ throw error;
508
+ }
509
+ }
510
+ async login(realm, clientId, user) {
511
+ try {
512
+ this.context.log(`Realm name: ${realm}`);
513
+ this.context.log(`Client ID: ${clientId}`);
514
+ this.context.log(`User: ${JSON.stringify(user)}`);
515
+ await this.authenticateRelmClient(realm, clientId);
516
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
517
+ const clientSecret = await this.getClientSecret(realm, clientId);
518
+ if (!clientSecret) {
519
+ throw new Error(`Client secret for "${clientId}" not found`);
520
+ }
521
+ const loginIdentifier = user.username || user.email;
522
+ const params = new URLSearchParams();
523
+ params.append('grant_type', 'password');
524
+ params.append('username', loginIdentifier);
525
+ params.append('password', user.password);
526
+ params.append('client_id', clientId);
527
+ params.append('client_secret', clientSecret);
528
+ this.context.log(`Params: ${JSON.stringify(params)}`);
529
+ const response = await this.axiosInstance.post(`/realms/${realm}/protocol/openid-connect/token`, params, {
530
+ headers: {
531
+ 'Content-Type': 'application/x-www-form-urlencoded'
532
+ }
533
+ });
534
+ if (response.status !== 200) {
535
+ this.context.error(`Response: ${JSON.stringify(response.data)}`);
536
+ if (response.data.error === 'invalid_grant') {
537
+ if (response.data.error_description === 'Account is not fully set up') {
538
+ throw new Error(`Please reset your password`);
539
+ }
540
+ if (response.data.error_description === 'Invalid user credentials') {
541
+ throw new Error(`Invalid username or password`);
542
+ }
543
+ } else if (response.data.error === 'invalid_token') {
544
+ throw new Error(`Invalid token`);
545
+ } else if (response.data.error === 'invalid_client') {
546
+ throw new Error(`Invalid client`);
547
+ } else if (response.data.error === 'invalid_request') {
548
+ throw new Error(`Invalid request`);
549
+ }
550
+ throw new Error(`Failed to login user using keycloak`);
551
+ }
552
+ this.context.log(`Response: ${JSON.stringify(response.data)}`);
553
+ const data = response.data;
554
+ return data;
555
+ } catch (error) {
556
+ this.context.error("Error in login", error);
557
+ if ((error == null ? void 0 : error.response.data.error) === 'invalid_grant') {
558
+ if ((error == null ? void 0 : error.response.data.error_description) === 'Account is not fully set up') {
559
+ throw new Error(`Please reset your password`);
560
+ }
561
+ if ((error == null ? void 0 : error.response.data.error_description) === 'Invalid user credentials') {
562
+ throw new Error(`Invalid username or password`);
563
+ }
564
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_token') {
565
+ throw new Error(`Invalid token`);
566
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_client') {
567
+ throw new Error(`Invalid client`);
568
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_request') {
569
+ throw new Error(`Invalid request`);
570
+ }
571
+ throw new Error(`Failed to login user using keycloak`);
572
+ }
573
+ }
574
+ async resetPassword(realm, userId, password) {
575
+ try {
576
+ await this.authenticate();
577
+ this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
578
+ const response = await this.axiosInstance.put(`/admin/realms/${realm}/users/${userId}/reset-password`, {
579
+ type: "password",
580
+ value: password,
581
+ temporary: false
582
+ });
583
+ if (response.status !== 204) {
584
+ throw new Error(`Failed to reset password: ${JSON.stringify(response.data)}`);
585
+ }
586
+ return;
587
+ } catch (error) {
588
+ var _error$response;
589
+ this.context.error("Error in resetPassword", error);
590
+ if (error != null && (_error$response = error.response) != null && (_error$response = _error$response.data) != null && _error$response.error_description) {
591
+ var _error$response2;
592
+ throw new Error(error == null || (_error$response2 = error.response) == null || (_error$response2 = _error$response2.data) == null ? void 0 : _error$response2.error_description);
593
+ }
594
+ if ((error == null ? void 0 : error.response.data.error) === 'invalid_grant') {
595
+ throw new Error(`Invalid grant`);
596
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_token') {
597
+ throw new Error(`Invalid token`);
598
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_client') {
599
+ throw new Error(`Invalid client`);
600
+ } else if ((error == null ? void 0 : error.response.data.error) === 'invalid_request') {
601
+ throw new Error(`Invalid request`);
602
+ }
603
+ throw error;
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Logout culturefy and revoke refresh token
609
+ * This method uses Keycloak's standard OpenID Connect logout endpoint
610
+ * which both logs out the culturefy and invalidates the refresh token
611
+ *
612
+ * @param realm - The Keycloak realm name
613
+ * @param clientId - The client ID
614
+ * @param refreshToken - The refresh token to revoke
615
+ * @returns Promise<void>
616
+ */
617
+ async logout(realm, clientId, refreshToken) {
618
+ try {
619
+ this.context.log(`Culturefy Logout - Realm: ${realm}, Client: ${clientId}`);
620
+ this.context.log(`Culturefy Logout - Refresh Token: ${refreshToken}`);
621
+ await this.authenticateRelmClient(realm, clientId);
622
+ // this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
623
+
624
+ // Try to get client secret, but handle the case where it might not exist
625
+ let clientSecret = null;
626
+ try {
627
+ clientSecret = await this.getClientSecret(realm, clientId);
628
+ } catch (error) {
629
+ this.context.warn(`Could not retrieve client secret for ${clientId}:`, error);
630
+ // For admin-cli, we might not need a client secret
631
+ clientSecret = null;
632
+ }
633
+ const params = new URLSearchParams();
634
+ params.append('refresh_token', refreshToken || '');
635
+ params.append('client_id', clientId || '');
636
+
637
+ // Only add client_secret if it exists
638
+ if (clientSecret) {
639
+ params.append('client_secret', clientSecret || '');
640
+ }
641
+ this.context.log(`Culturefy Logout Params: ${JSON.stringify(params)}`);
642
+ const response = await this.axiosInstance.post(`/realms/${realm}/protocol/openid-connect/logout`, params, {
643
+ headers: {
644
+ 'Content-Type': 'application/x-www-form-urlencoded'
645
+ }
646
+ });
647
+ if (response.status !== 200) {
648
+ this.context.error(`Culturefy Logout Response: ${JSON.stringify(response.data)}`);
649
+
650
+ // Handle specific error cases
651
+ if (response.data.error === 'invalid_grant') {
652
+ throw new Error(`Invalid refresh token`);
653
+ } else if (response.data.error === 'invalid_token') {
654
+ throw new Error(`Invalid token`);
655
+ } else if (response.data.error === 'invalid_client') {
656
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
657
+ } else if (response.data.error === 'invalid_request') {
658
+ throw new Error(`Invalid request`);
659
+ }
660
+ throw new Error(`Failed to logout culturefy - check admin-cli client configuration`);
661
+ }
662
+ this.context.log(`Culturefy Logout Response: ${JSON.stringify(response.data)}`);
663
+ return;
664
+ } catch (error) {
665
+ var _error$response3, _error$response4, _error$response5, _error$response6, _error$response7;
666
+ this.context.error("Error in logoutCulturefy", error);
667
+
668
+ // Handle specific error cases
669
+ if ((error == null || (_error$response3 = error.response) == null || (_error$response3 = _error$response3.data) == null ? void 0 : _error$response3.error) === 'invalid_grant') {
670
+ throw new Error(`Invalid refresh token`);
671
+ } else if ((error == null || (_error$response4 = error.response) == null || (_error$response4 = _error$response4.data) == null ? void 0 : _error$response4.error) === 'invalid_token') {
672
+ throw new Error(`Invalid token`);
673
+ } else if ((error == null || (_error$response5 = error.response) == null || (_error$response5 = _error$response5.data) == null ? void 0 : _error$response5.error) === 'invalid_client') {
674
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
675
+ } else if ((error == null || (_error$response6 = error.response) == null || (_error$response6 = _error$response6.data) == null ? void 0 : _error$response6.error) === 'invalid_request') {
676
+ throw new Error(`Invalid request`);
677
+ }
678
+ if (error != null && (_error$response7 = error.response) != null && (_error$response7 = _error$response7.data) != null && _error$response7.error_description) {
679
+ var _error$response8;
680
+ throw new Error(error == null || (_error$response8 = error.response) == null || (_error$response8 = _error$response8.data) == null ? void 0 : _error$response8.error_description);
681
+ }
682
+ throw new Error(`Failed to logout culturefy - check admin-cli client configuration`);
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Refresh culturefy access token using refresh token
688
+ * This method uses Keycloak's standard OpenID Connect token endpoint
689
+ * to refresh the access token for culturefy users
690
+ *
691
+ * @param realm - The Keycloak realm name
692
+ * @param clientId - The client ID
693
+ * @param refreshToken - The refresh token to use for getting new access token
694
+ * @returns Promise<KeycloakUserLoginResponse> - New access and refresh tokens
695
+ */
696
+ async refreshToken(realm, clientId, refreshToken) {
697
+ try {
698
+ this.context.log(`Culturefy Token Refresh - Realm: ${realm}, Client: ${clientId}`);
699
+ await this.authenticate();
700
+ // this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
701
+
702
+ // Try to get client secret, but handle the case where it might not exist
703
+ let clientSecret = null;
704
+ try {
705
+ clientSecret = await this.getClientSecret(realm, clientId);
706
+ } catch (error) {
707
+ this.context.warn(`Could not retrieve client secret for ${clientId}:`, error);
708
+ // For admin-cli, we might not need a client secret
709
+ clientSecret = null;
710
+ }
711
+ const params = new URLSearchParams();
712
+ params.append('grant_type', 'refresh_token');
713
+ params.append('refresh_token', refreshToken);
714
+ params.append('client_id', clientId);
715
+
716
+ // Only add client_secret if it exists
717
+ if (clientSecret) {
718
+ params.append('client_secret', clientSecret);
719
+ }
720
+ this.context.log(`Culturefy Token Refresh Params: ${JSON.stringify(params)}`);
721
+ const response = await this.axiosInstance.post(`/realms/${realm}/protocol/openid-connect/token`, params, {
722
+ headers: {
723
+ 'Content-Type': 'application/x-www-form-urlencoded'
724
+ }
725
+ });
726
+ if (response.status !== 200) {
727
+ this.context.error(`Culturefy Token Refresh Response: ${JSON.stringify(response.data)}`);
728
+
729
+ // Handle specific error cases
730
+ if (response.data.error === 'invalid_grant') {
731
+ if (response.data.error_description === 'Token is not active') {
732
+ throw new Error(`Refresh token has expired, please login again`);
733
+ }
734
+ if (response.data.error_description === 'Invalid refresh token') {
735
+ throw new Error(`Invalid refresh token`);
736
+ }
737
+ throw new Error(`Invalid refresh token`);
738
+ } else if (response.data.error === 'invalid_token') {
739
+ throw new Error(`Invalid token`);
740
+ } else if (response.data.error === 'invalid_client') {
741
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
742
+ } else if (response.data.error === 'invalid_request') {
743
+ throw new Error(`Invalid request`);
744
+ }
745
+ throw new Error(`Failed to refresh culturefy token - check admin-cli client configuration`);
746
+ }
747
+ this.context.log(`Culturefy Token Refresh Response: ${JSON.stringify(response.data)}`);
748
+ const data = response.data;
749
+ return data;
750
+ } catch (error) {
751
+ var _error$response9, _error$response10, _error$response11, _error$response12, _error$response13;
752
+ this.context.error("Error in refreshCulturefyToken", error);
753
+
754
+ // Handle specific error cases
755
+ if ((error == null || (_error$response9 = error.response) == null || (_error$response9 = _error$response9.data) == null ? void 0 : _error$response9.error) === 'invalid_grant') {
756
+ var _error$response0, _error$response1;
757
+ if ((error == null || (_error$response0 = error.response) == null || (_error$response0 = _error$response0.data) == null ? void 0 : _error$response0.error_description) === 'Token is not active') {
758
+ throw new Error(`Refresh token has expired, please login again`);
759
+ }
760
+ if ((error == null || (_error$response1 = error.response) == null || (_error$response1 = _error$response1.data) == null ? void 0 : _error$response1.error_description) === 'Invalid refresh token') {
761
+ throw new Error(`Invalid refresh token`);
762
+ }
763
+ throw new Error(`Invalid refresh token`);
764
+ } else if ((error == null || (_error$response10 = error.response) == null || (_error$response10 = _error$response10.data) == null ? void 0 : _error$response10.error) === 'invalid_token') {
765
+ throw new Error(`Invalid token`);
766
+ } else if ((error == null || (_error$response11 = error.response) == null || (_error$response11 = _error$response11.data) == null ? void 0 : _error$response11.error) === 'invalid_client') {
767
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
768
+ } else if ((error == null || (_error$response12 = error.response) == null || (_error$response12 = _error$response12.data) == null ? void 0 : _error$response12.error) === 'invalid_request') {
769
+ throw new Error(`Invalid request`);
770
+ }
771
+ if (error != null && (_error$response13 = error.response) != null && (_error$response13 = _error$response13.data) != null && _error$response13.error_description) {
772
+ var _error$response14;
773
+ throw new Error(error == null || (_error$response14 = error.response) == null || (_error$response14 = _error$response14.data) == null ? void 0 : _error$response14.error_description);
774
+ }
775
+ throw new Error(`Failed to refresh culturefy token - check admin-cli client configuration`);
776
+ }
777
+ }
778
+
779
+ /**
780
+ * Introspect culturefy token to check if it's active
781
+ * This method uses Keycloak's standard OpenID Connect token introspection endpoint
782
+ * to verify the active state of a culturefy token
783
+ *
784
+ * @param realm - The Keycloak realm name
785
+ * @param clientId - The client ID
786
+ * @param token - The token to introspect
787
+ * @returns Promise<KeycloakIntrospectTokenResponse> - Token introspection result
788
+ */
789
+ async introspectToken(realm, clientId, token) {
790
+ try {
791
+ this.context.log(`Culturefy Token Introspection - Realm: ${realm}, Client: ${clientId}`);
792
+ await this.authenticateRelmClient(realm, clientId);
793
+ // this.context.log(`Authenticated`, JSON.stringify(this.accessToken));
794
+
795
+ // Try to get client secret, but handle the case where it might not exist
796
+ let clientSecret = null;
797
+ try {
798
+ clientSecret = await this.getClientSecret(realm, clientId);
799
+ } catch (error) {
800
+ this.context.warn(`Could not retrieve client secret for ${clientId}:`, error);
801
+ // For admin-cli, we might not need a client secret
802
+ clientSecret = null;
803
+ }
804
+
805
+ // Create introspection parameters
806
+ const params = new URLSearchParams();
807
+ params.append('token', token);
808
+ this.context.log(`Culturefy Token Introspection Params: ${JSON.stringify(params)}`);
809
+
810
+ // Perform the introspection request
811
+ const response = await this.axiosInstance.post(`/realms/${realm}/protocol/openid-connect/token/introspect`, params, {
812
+ headers: {
813
+ 'Content-Type': 'application/x-www-form-urlencoded'
814
+ },
815
+ auth: {
816
+ username: clientId,
817
+ password: clientSecret || '' // Use empty string if no client secret
818
+ }
819
+ });
820
+ if (response.status !== 200) {
821
+ this.context.error(`Culturefy Token Introspection Response: ${JSON.stringify(response.data)}`);
822
+
823
+ // Handle specific error cases
824
+ if (response.data.error === 'invalid_token') {
825
+ throw new Error(`Invalid token`);
826
+ } else if (response.data.error === 'invalid_client') {
827
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
828
+ } else if (response.data.error === 'invalid_request') {
829
+ throw new Error(`Invalid request`);
830
+ }
831
+ throw new Error(`Failed to introspect culturefy token - check admin-cli client configuration`);
832
+ }
833
+ this.context.log(`Culturefy Token Introspection Response: ${JSON.stringify(response.data)}`);
834
+ const data = response.data;
835
+
836
+ // Check if token is active
837
+ if (!data.active) {
838
+ throw new Error(`Token is not active or has expired`);
839
+ }
840
+ return data;
841
+ } catch (error) {
842
+ var _error$response15, _error$response16, _error$response17, _error$response18;
843
+ this.context.error("Error in introspectCulturefyToken", error);
844
+
845
+ // Handle specific error cases
846
+ if ((error == null || (_error$response15 = error.response) == null || (_error$response15 = _error$response15.data) == null ? void 0 : _error$response15.error) === 'invalid_token') {
847
+ throw new Error(`Invalid token`);
848
+ } else if ((error == null || (_error$response16 = error.response) == null || (_error$response16 = _error$response16.data) == null ? void 0 : _error$response16.error) === 'invalid_client') {
849
+ throw new Error(`Invalid client configuration - admin-cli client may not be properly configured`);
850
+ } else if ((error == null || (_error$response17 = error.response) == null || (_error$response17 = _error$response17.data) == null ? void 0 : _error$response17.error) === 'invalid_request') {
851
+ throw new Error(`Invalid request`);
852
+ }
853
+ if (error != null && (_error$response18 = error.response) != null && (_error$response18 = _error$response18.data) != null && _error$response18.error_description) {
854
+ var _error$response19;
855
+ throw new Error(error == null || (_error$response19 = error.response) == null || (_error$response19 = _error$response19.data) == null ? void 0 : _error$response19.error_description);
856
+ }
857
+ throw new Error(`Failed to introspect culturefy token - check admin-cli client configuration`);
858
+ }
859
+ }
860
+
861
+ /**
862
+ * Get user details from a token using Keycloak's userinfo endpoint
863
+ * This method uses Keycloak's standard OpenID Connect userinfo endpoint
864
+ * to get the user details associated with the token
865
+ *
866
+ * @param realm - The Keycloak realm name
867
+ * @param clientId - The client ID
868
+ * @param token - The access token to get user info for
869
+ * @returns Promise<KeycloakUserResponse> - User details from the token
870
+ */
871
+ async getUserByToken(realm, token) {
872
+ try {
873
+ try {
874
+ const response = await this.axiosInstance.get(`/realms/${realm}/protocol/openid-connect/userinfo`, {
875
+ headers: {
876
+ 'Authorization': `Bearer ${token}`,
877
+ 'Content-Type': 'application/json'
878
+ }
879
+ });
880
+ if (response.status === 200) {
881
+ this.context.log(`User info from userinfo endpoint: ${JSON.stringify(response.data)}`);
882
+
883
+ // Transform the userinfo response to match KeycloakUserResponse format
884
+ const userInfo = response.data;
885
+ const userResponse = {
886
+ id: userInfo.sub || userInfo.user_id,
887
+ username: userInfo.preferred_username || userInfo.username,
888
+ email: userInfo.email,
889
+ firstName: userInfo.given_name || userInfo.first_name,
890
+ lastName: userInfo.family_name || userInfo.last_name,
891
+ emailVerified: userInfo.email_verified || false,
892
+ enabled: true,
893
+ // userinfo doesn't provide this, assume true if token is valid
894
+ createdTimestamp: userInfo.iat ? userInfo.iat * 1000 : 0,
895
+ totp: false,
896
+ disableableCredentialTypes: [],
897
+ requiredActions: [],
898
+ notBefore: 0,
899
+ access: {
900
+ manage: true
901
+ }
902
+ };
903
+ return userResponse;
904
+ }
905
+ } catch (userinfoError) {
906
+ this.context.warn(`Userinfo endpoint failed, trying alternative method:`, userinfoError);
907
+ }
908
+ // Method 2: Decode JWT token and extract user ID, then get user details
909
+ try {
910
+ // Decode the JWT token to get the user ID
911
+ const decoded = this.decodeJwtToken(token);
912
+ if (!decoded || !decoded.sub) {
913
+ throw new Error('Invalid token or missing user ID');
914
+ }
915
+ const userId = decoded.sub;
916
+ this.context.log(`Extracted user ID from token: ${userId}`);
917
+
918
+ // Get user details using the admin API
919
+ await this.authenticate();
920
+ const response = await this.axiosInstance.get(`/admin/realms/${realm}/users/${userId}`);
921
+ if (response.status === 200) {
922
+ this.context.log(`User details from admin API: ${JSON.stringify(response.data)}`);
923
+ const userResponse = response.data;
924
+ return userResponse;
925
+ }
926
+ } catch (adminApiError) {
927
+ this.context.warn(`Admin API method failed:`, adminApiError);
928
+ }
929
+
930
+ // Method 3: Extract user details directly from JWT token claims
931
+ try {
932
+ const decoded = this.decodeJwtToken(token);
933
+ if (decoded) {
934
+ this.context.log(`Extracting user details from JWT claims: ${JSON.stringify(decoded)}`);
935
+ const userResponse = {
936
+ id: decoded.sub,
937
+ username: decoded.preferred_username || decoded.username,
938
+ email: decoded.email,
939
+ firstName: decoded.given_name || decoded.first_name,
940
+ lastName: decoded.family_name || decoded.last_name,
941
+ emailVerified: decoded.email_verified || false,
942
+ enabled: true,
943
+ createdTimestamp: decoded.iat ? decoded.iat * 1000 : 0,
944
+ totp: false,
945
+ disableableCredentialTypes: [],
946
+ requiredActions: [],
947
+ notBefore: 0,
948
+ access: {
949
+ manage: true
950
+ }
951
+ // Note: Additional user attributes are available in decoded token but not in KeycloakUserResponse interface
952
+ };
953
+ return userResponse;
954
+ }
955
+ } catch (jwtError) {
956
+ this.context.error(`JWT decoding failed:`, jwtError);
957
+ }
958
+ throw new Error('Failed to get user details from culturefy token');
959
+ } catch (error) {
960
+ var _error$response20, _error$response21, _error$response22, _error$response23;
961
+ this.context.error("Error in getUserByCulturefyToken", error);
962
+ if ((error == null || (_error$response20 = error.response) == null || (_error$response20 = _error$response20.data) == null ? void 0 : _error$response20.error) === 'invalid_token') {
963
+ throw new Error(`Invalid token`);
964
+ } else if ((error == null || (_error$response21 = error.response) == null || (_error$response21 = _error$response21.data) == null ? void 0 : _error$response21.error) === 'insufficient_scope') {
965
+ throw new Error(`Token does not have required scope to access user info`);
966
+ } else if ((error == null || (_error$response22 = error.response) == null || (_error$response22 = _error$response22.data) == null ? void 0 : _error$response22.error) === 'invalid_request') {
967
+ throw new Error(`Invalid request`);
968
+ }
969
+ if (error != null && (_error$response23 = error.response) != null && (_error$response23 = _error$response23.data) != null && _error$response23.error_description) {
970
+ var _error$response24;
971
+ throw new Error(error == null || (_error$response24 = error.response) == null || (_error$response24 = _error$response24.data) == null ? void 0 : _error$response24.error_description);
972
+ }
973
+ throw new Error(`Failed to get user details from culturefy token`);
974
+ }
975
+ }
976
+
977
+ /**
978
+ * Decode JWT token without verification (for extracting claims)
979
+ * This is used to extract user information from the token
980
+ *
981
+ * @param token - The JWT token to decode
982
+ * @returns any - Decoded token payload
983
+ */
984
+ decodeJwtToken(token) {
985
+ try {
986
+ // Split the token and get the payload part
987
+ const parts = token.split('.');
988
+ if (parts.length !== 3) {
989
+ throw new Error('Invalid JWT token format');
990
+ }
991
+
992
+ // Decode the payload (second part)
993
+ const payload = parts[1];
994
+ const decodedPayload = Buffer.from(payload, 'base64').toString('utf-8');
995
+ return JSON.parse(decodedPayload);
996
+ } catch (error) {
997
+ this.context.error("Error decoding JWT token:", error);
998
+ throw new Error('Failed to decode JWT token');
999
+ }
1000
+ }
1001
+ }
1002
+ exports.KeycloakAdminService = KeycloakAdminService;
1003
+ //# sourceMappingURL=keycloak.service.js.map