keycloak-api-manager 4.0.0 → 5.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.
Files changed (48) hide show
  1. package/Handlers/attackDetectionHandler.js +64 -0
  2. package/Handlers/clientPoliciesHandler.js +120 -0
  3. package/Handlers/groupsHandler.js +32 -0
  4. package/Handlers/organizationsHandler.js +243 -0
  5. package/Handlers/serverInfoHandler.js +36 -0
  6. package/Handlers/userProfileHandler.js +121 -0
  7. package/README.md +83 -7157
  8. package/docs/architecture.md +47 -0
  9. package/docs/deployment.md +32 -0
  10. package/docs/keycloak-setup.md +47 -0
  11. package/docs/test-configuration.md +43 -0
  12. package/docs/testing.md +60 -0
  13. package/index.js +156 -240
  14. package/package.json +28 -15
  15. package/test/.mocharc.json +2 -2
  16. package/test/config/secrets.json +12 -0
  17. package/test/docker-keycloak/certs/keycloak.crt +58 -0
  18. package/test/docker-keycloak/certs/keycloak.key +28 -0
  19. package/test/docker-keycloak/docker-compose-https.yml +2 -0
  20. package/test/docker-keycloak/docker-compose.yml +4 -4
  21. package/test/helpers/matrix.js +16 -0
  22. package/test/matrix/auth.json +27 -0
  23. package/test/matrix/clients.json +45 -0
  24. package/test/matrix/realms-components-idp.json +37 -0
  25. package/test/matrix/users-roles-groups.json +26 -0
  26. package/test/package-lock.json +3032 -0
  27. package/test/specs/attackDetection.test.js +102 -0
  28. package/test/specs/clientCredentials.test.js +79 -0
  29. package/test/specs/clientPolicies.test.js +162 -0
  30. package/test/specs/{debugClientLibrary.test.js → diagnostics/debugClientLibrary.test.js} +2 -2
  31. package/test/specs/groupPermissions.test.js +87 -0
  32. package/test/specs/matrix/matrix-auth.test.js +112 -0
  33. package/test/specs/matrix/matrix-clients.test.js +59 -0
  34. package/test/specs/matrix/matrix-realms-components-idp.test.js +111 -0
  35. package/test/specs/matrix/matrix-users-roles-groups.test.js +68 -0
  36. package/test/specs/organizations.test.js +183 -0
  37. package/test/specs/serverInfo.test.js +140 -0
  38. package/test/specs/userProfile.test.js +135 -0
  39. package/test/{enableServerFeatures.js → support/enableServerFeatures.js} +43 -26
  40. package/test/{setup.js → support/setup.js} +3 -3
  41. package/test/support/testConfig.js +69 -0
  42. package/test/testConfig.js +1 -69
  43. package/test-output.log +72 -0
  44. package/test/TESTING.md +0 -327
  45. package/test/config/CONFIGURATION.md +0 -170
  46. package/test/diagnostic-protocol-mappers.js +0 -189
  47. package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +0 -262
  48. package/test/helpers/setup.js +0 -186
@@ -0,0 +1,64 @@
1
+ /**
2
+ * **************************************************************************************************
3
+ * **************************************************************************************************
4
+ * The Attack Detection entity provides functionality for managing brute force attack detection
5
+ * in Keycloak. This allows you to detect and prevent brute force login attempts, lock users,
6
+ * and clear login failures.
7
+ * **************************************************************************************************
8
+ * **************************************************************************************************
9
+ */
10
+ let kcAdminClientHandler = null;
11
+
12
+ exports.setKcAdminClient = function(kcAdminClient) {
13
+ kcAdminClientHandler = kcAdminClient;
14
+ }
15
+
16
+ /**
17
+ * ***************************** - getBruteForceStatus - *******************************
18
+ * Get brute force detection status for all users in a realm
19
+ *
20
+ * @parameters:
21
+ * - realm: (string, required) - The realm name
22
+ * @returns: Array of user brute force status objects
23
+ */
24
+ exports.getBruteForceStatus = function(filter) {
25
+ return kcAdminClientHandler.attackDetection.findAll(filter);
26
+ }
27
+
28
+ /**
29
+ * ***************************** - getUserBruteForceStatus - *******************************
30
+ * Get brute force detection status for a specific user
31
+ *
32
+ * @parameters:
33
+ * - realm: (string, required) - The realm name
34
+ * - id: (string, required) - The user ID
35
+ * @returns: User brute force status object
36
+ */
37
+ exports.getUserBruteForceStatus = function(filter) {
38
+ return kcAdminClientHandler.attackDetection.findOne(filter);
39
+ }
40
+
41
+ /**
42
+ * ***************************** - clearUserLoginFailures - *******************************
43
+ * Clear all login failures for a specific user
44
+ *
45
+ * @parameters:
46
+ * - realm: (string, required) - The realm name
47
+ * - id: (string, required) - The user ID
48
+ * @returns: Promise
49
+ */
50
+ exports.clearUserLoginFailures = function(filter) {
51
+ return kcAdminClientHandler.attackDetection.del(filter);
52
+ }
53
+
54
+ /**
55
+ * ***************************** - clearAllLoginFailures - *******************************
56
+ * Clear all login failures for all users in a realm
57
+ *
58
+ * @parameters:
59
+ * - realm: (string, required) - The realm name
60
+ * @returns: Promise
61
+ */
62
+ exports.clearAllLoginFailures = function(filter) {
63
+ return kcAdminClientHandler.attackDetection.delAll(filter);
64
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * **************************************************************************************************
3
+ * **************************************************************************************************
4
+ * The Client Policies entity (Keycloak 12+) provides governance and security policies
5
+ * for client applications. Client policies allow administrators to enforce security
6
+ * requirements, configure client behavior, and ensure compliance across all clients.
7
+ * **************************************************************************************************
8
+ * **************************************************************************************************
9
+ */
10
+ let kcAdminClientHandler = null;
11
+
12
+ exports.setKcAdminClient = function(kcAdminClient) {
13
+ kcAdminClientHandler = kcAdminClient;
14
+ }
15
+
16
+ /**
17
+ * ***************************** - getPolicies - *******************************
18
+ * Get all client policies for a realm
19
+ *
20
+ * @parameters:
21
+ * - filter: [optional] parameter
22
+ * - realm: (string, optional) - The realm name
23
+ * @returns: Object containing policies array
24
+ */
25
+ exports.getPolicies = function(filter) {
26
+ return kcAdminClientHandler.clientPolicies.listPolicies(filter);
27
+ }
28
+
29
+ /**
30
+ * ***************************** - updatePolicies - *******************************
31
+ * Update client policies configuration
32
+ *
33
+ * @parameters:
34
+ * - filter: [optional] parameter
35
+ * - realm: (string, optional) - The realm name
36
+ * - policiesRepresentation: Object with policies array
37
+ * - policies: (array) - Array of policy objects
38
+ * - name: (string) - Policy name
39
+ * - description: (string) - Policy description
40
+ * - enabled: (boolean) - Whether policy is enabled
41
+ * - conditions: (array) - Conditions that trigger the policy
42
+ * - profiles: (array) - Profiles to apply when policy matches
43
+ */
44
+ exports.updatePolicies = async function(filter, policiesRepresentation) {
45
+ // Direct API call since @keycloak/keycloak-admin-client doesn't support this
46
+ const realm = filter?.realm || kcAdminClientHandler.realmName;
47
+ const baseUrl = kcAdminClientHandler.baseUrl;
48
+ const token = kcAdminClientHandler.accessToken;
49
+
50
+ const url = `${baseUrl}/admin/realms/${realm}/client-policies/policies`;
51
+
52
+ const response = await fetch(url, {
53
+ method: 'PUT',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Authorization': `Bearer ${token}`
57
+ },
58
+ body: JSON.stringify(policiesRepresentation)
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.text();
63
+ throw new Error(`Failed to update client policies: ${error}`);
64
+ }
65
+
66
+ return response.status === 204 ? undefined : response.json();
67
+ }
68
+
69
+ /**
70
+ * ***************************** - getProfiles - *******************************
71
+ * Get all client profiles for a realm
72
+ *
73
+ * @parameters:
74
+ * - filter: [optional] parameter
75
+ * - realm: (string, optional) - The realm name
76
+ * @returns: Object containing profiles array
77
+ */
78
+ exports.getProfiles = function(filter) {
79
+ return kcAdminClientHandler.clientPolicies.listProfiles(filter);
80
+ }
81
+
82
+ /**
83
+ * ***************************** - updateProfiles - *******************************
84
+ * Update client profiles configuration
85
+ *
86
+ * @parameters:
87
+ * - filter: [optional] parameter
88
+ * - realm: (string, optional) - The realm name
89
+ * - profilesRepresentation: Object with profiles array
90
+ * - profiles: (array) - Array of profile objects
91
+ * - name: (string) - Profile name
92
+ * - description: (string) - Profile description
93
+ * - executors: (array) - Executors that enforce security requirements
94
+ * - executor: (string) - Executor type
95
+ * - configuration: (object) - Executor-specific configuration
96
+ */
97
+ exports.updateProfiles = async function(filter, profilesRepresentation) {
98
+ // Direct API call since @keycloak/keycloak-admin-client doesn't support this
99
+ const realm = filter?.realm || kcAdminClientHandler.realmName;
100
+ const baseUrl = kcAdminClientHandler.baseUrl;
101
+ const token = kcAdminClientHandler.accessToken;
102
+
103
+ const url = `${baseUrl}/admin/realms/${realm}/client-policies/profiles`;
104
+
105
+ const response = await fetch(url, {
106
+ method: 'PUT',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'Authorization': `Bearer ${token}`
110
+ },
111
+ body: JSON.stringify(profilesRepresentation)
112
+ });
113
+
114
+ if (!response.ok) {
115
+ const error = await response.text();
116
+ throw new Error(`Failed to update client profiles: ${error}`);
117
+ }
118
+
119
+ return response.status === 204 ? undefined : response.json();
120
+ }
@@ -306,3 +306,35 @@ exports.delClientRoleMappings=function(filters){
306
306
  return (kcAdminClientHandler.groups.delClientRoleMappings(filters));
307
307
  }
308
308
 
309
+
310
+ /**
311
+ * ***************************** - setPermissions - *******************************
312
+ * Enable or update permission settings for a group.
313
+ * This allows fine-grained control over who can manage group membership and permissions.
314
+ * @parameters:
315
+ * - filters: parameter provided as a JSON object that accepts the following parameters:
316
+ * - id: [required] The ID of the group
317
+ * - permissionRepresentation: Object with permission settings
318
+ * - enabled: [required] (boolean) Whether permissions are enabled
319
+ */
320
+ exports.setPermissions=function(filters, permissionRepresentation){
321
+ return (kcAdminClientHandler.groups.updatePermission(filters, permissionRepresentation));
322
+ }
323
+
324
+
325
+ /**
326
+ * ***************************** - listPermissions - *******************************
327
+ * Get the current permission settings for a group.
328
+ * Returns information about who can manage the group, view members, etc.
329
+ * @parameters:
330
+ * - filters: parameter provided as a JSON object that accepts the following parameters:
331
+ * - id: [required] The ID of the group
332
+ * @returns: Object with permission configuration including:
333
+ * - enabled: (boolean) Whether permissions are enabled
334
+ * - resource: (string) Associated authorization resource ID
335
+ * - scopePermissions: (object) Map of scope permissions
336
+ */
337
+ exports.listPermissions=function(filters){
338
+ return (kcAdminClientHandler.groups.listPermissions(filters));
339
+ }
340
+
@@ -0,0 +1,243 @@
1
+ /**
2
+ * **************************************************************************************************
3
+ * **************************************************************************************************
4
+ * The Organizations entity (Keycloak 25+) allows managing organizations for multi-tenancy.
5
+ * Organizations provide a way to group users, identity providers, and domains together,
6
+ * enabling better isolation and management of different organizational units.
7
+ *
8
+ * NOTE: Some Organizations APIs are not fully supported by @keycloak/keycloak-admin-client
9
+ * so this handler uses direct REST API calls for those endpoints.
10
+ * **************************************************************************************************
11
+ * **************************************************************************************************
12
+ */
13
+ let kcAdminClientHandler = null;
14
+
15
+ exports.setKcAdminClient = function(kcAdminClient) {
16
+ kcAdminClientHandler = kcAdminClient;
17
+ }
18
+
19
+ /**
20
+ * Helper function to make direct API calls to Keycloak
21
+ */
22
+ async function makeDirectApiCall(method, endpoint, body = null) {
23
+ const baseUrl = kcAdminClientHandler.baseUrl;
24
+ const realmName = kcAdminClientHandler.realmName;
25
+ const accessToken = kcAdminClientHandler.accessToken;
26
+
27
+ const url = `${baseUrl}/admin/realms/${realmName}${endpoint}`;
28
+
29
+ const options = {
30
+ method,
31
+ headers: {
32
+ 'Authorization': `Bearer ${accessToken}`,
33
+ 'Content-Type': 'application/json'
34
+ }
35
+ };
36
+
37
+ if (body) {
38
+ options.body = JSON.stringify(body);
39
+ }
40
+
41
+ const response = await fetch(url, options);
42
+
43
+ if (!response.ok) {
44
+ const errorText = await response.text();
45
+ let errorMessage;
46
+ try {
47
+ const errorJson = JSON.parse(errorText);
48
+ errorMessage = errorJson.errorMessage || errorJson.error || errorText;
49
+ } catch (e) {
50
+ errorMessage = errorText;
51
+ }
52
+ throw new Error(errorMessage || `HTTP ${response.status}: ${response.statusText}`);
53
+ }
54
+
55
+ // Handle empty responses (DELETE, PUT requests may return 204 No Content)
56
+ if (response.status === 204 || response.headers.get('content-length') === '0') {
57
+ return null;
58
+ }
59
+
60
+ const responseText = await response.text();
61
+ if (!responseText) {
62
+ return null;
63
+ }
64
+
65
+ return JSON.parse(responseText);
66
+ }
67
+
68
+ /**
69
+ * ***************************** - create - *******************************
70
+ * Create a new organization
71
+ *
72
+ * @parameters:
73
+ * - organizationRepresentation: An object representing the organization
74
+ * - name: [required] (string) - Organization name
75
+ * - displayName: [optional] (string) - Display name
76
+ * - url: [optional] (string) - Organization URL
77
+ * - domains: [optional] (array) - List of domains
78
+ * - attributes: [optional] (object) - Custom attributes
79
+ */
80
+ exports.create = function(organizationRepresentation) {
81
+ return kcAdminClientHandler.organizations.create(organizationRepresentation);
82
+ }
83
+
84
+ /**
85
+ * ***************************** - find - *******************************
86
+ * Get all organizations in a realm
87
+ *
88
+ * @parameters:
89
+ * - filter: [optional] parameter for filtering
90
+ * - realm: (string, optional) - The realm name
91
+ * - search: (string, optional) - Search string
92
+ * - first: (number, optional) - First result index
93
+ * - max: (number, optional) - Maximum results
94
+ */
95
+ exports.find = function(filter) {
96
+ return kcAdminClientHandler.organizations.find(filter);
97
+ }
98
+
99
+ /**
100
+ * ***************************** - findOne - *******************************
101
+ * Get a specific organization by ID
102
+ *
103
+ * @parameters:
104
+ * - filter: parameter with organization ID
105
+ * - id: (string, required) - Organization ID
106
+ * - realm: (string, optional) - The realm name
107
+ */
108
+ exports.findOne = function(filter) {
109
+ return kcAdminClientHandler.organizations.findOne(filter);
110
+ }
111
+
112
+ /**
113
+ * ***************************** - update - *******************************
114
+ * Update an organization
115
+ *
116
+ * @parameters:
117
+ * - filter: parameter with organization ID
118
+ * - id: (string, required) - Organization ID
119
+ * - organizationRepresentation: Updated organization data
120
+ */
121
+ exports.update = async function(filter, organizationRepresentation) {
122
+ const { id } = filter;
123
+ const current = await exports.findOne({ id });
124
+ const merged = {
125
+ ...current,
126
+ ...organizationRepresentation,
127
+ id,
128
+ name: organizationRepresentation?.name || current?.name
129
+ };
130
+
131
+ return await makeDirectApiCall('PUT', `/organizations/${id}`, merged);
132
+ }
133
+
134
+ /**
135
+ * ***************************** - del - *******************************
136
+ * Delete an organization
137
+ *
138
+ * @parameters:
139
+ * - filter: parameter with organization ID
140
+ * - id: (string, required) - Organization ID
141
+ */
142
+ exports.del = async function(filter) {
143
+ return kcAdminClientHandler.organizations.delById(filter);
144
+ }
145
+
146
+ /**
147
+ * ***************************** - addMember - *******************************
148
+ * Add a user as member to an organization
149
+ *
150
+ * @parameters:
151
+ * - filter: parameter with organization ID and user ID
152
+ * - id: (string, required) - Organization ID
153
+ * - userId: (string, required) - User ID
154
+ */
155
+ exports.addMember = async function(filter) {
156
+ const { id, userId } = filter;
157
+ return kcAdminClientHandler.organizations.addMember({
158
+ orgId: id,
159
+ userId
160
+ });
161
+ }
162
+
163
+ /**
164
+ * ***************************** - listMembers - *******************************
165
+ * List all members of an organization
166
+ *
167
+ * @parameters:
168
+ * - filter: parameter with organization ID
169
+ * - id: (string, required) - Organization ID
170
+ * - first: (number, optional) - First result
171
+ * - max: (number, optional) - Max results
172
+ */
173
+ exports.listMembers = async function(filter) {
174
+ const { id, ...rest } = filter;
175
+ return kcAdminClientHandler.organizations.listMembers({
176
+ orgId: id,
177
+ ...rest
178
+ });
179
+ }
180
+
181
+ /**
182
+ * ***************************** - delMember - *******************************
183
+ * Remove a member from an organization
184
+ *
185
+ * @parameters:
186
+ * - filter: parameter with organization ID and user ID
187
+ * - id: (string, required) - Organization ID
188
+ * - userId: (string, required) - User ID
189
+ */
190
+ exports.delMember = async function(filter) {
191
+ const { id, userId } = filter;
192
+ return kcAdminClientHandler.organizations.delMember({
193
+ orgId: id,
194
+ userId
195
+ });
196
+ }
197
+
198
+ /**
199
+ * ***************************** - addIdentityProvider - *******************************
200
+ * Link an identity provider to an organization
201
+ *
202
+ * @parameters:
203
+ * - filter: parameter with organization ID and IDP alias
204
+ * - id: (string, required) - Organization ID
205
+ * - alias: (string, required) - Identity provider alias
206
+ */
207
+ exports.addIdentityProvider = async function(filter) {
208
+ const { id, alias } = filter;
209
+ return kcAdminClientHandler.organizations.linkIdp({
210
+ orgId: id,
211
+ alias
212
+ });
213
+ }
214
+
215
+ /**
216
+ * ***************************** - listIdentityProviders - *******************************
217
+ * List identity providers linked to an organization
218
+ *
219
+ * @parameters:
220
+ * - filter: parameter with organization ID
221
+ * - id: (string, required) - Organization ID
222
+ */
223
+ exports.listIdentityProviders = async function(filter) {
224
+ const { id } = filter;
225
+ return kcAdminClientHandler.organizations.listIdentityProviders({ orgId: id });
226
+ }
227
+
228
+ /**
229
+ * ***************************** - delIdentityProvider - *******************************
230
+ * Unlink an identity provider from an organization
231
+ *
232
+ * @parameters:
233
+ * - filter: parameter with organization ID and IDP alias
234
+ * - id: (string, required) - Organization ID
235
+ * - alias: (string, required) - Identity provider alias
236
+ */
237
+ exports.delIdentityProvider = async function(filter) {
238
+ const { id, alias } = filter;
239
+ return kcAdminClientHandler.organizations.unLinkIdp({
240
+ orgId: id,
241
+ alias
242
+ });
243
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * **************************************************************************************************
3
+ * **************************************************************************************************
4
+ * The Server Info entity provides information about the Keycloak server instance,
5
+ * including available providers, themes, system info, memory usage, and enabled features.
6
+ * This is useful for monitoring, diagnostics, and understanding server capabilities.
7
+ * **************************************************************************************************
8
+ * **************************************************************************************************
9
+ */
10
+ let kcAdminClientHandler = null;
11
+
12
+ exports.setKcAdminClient = function(kcAdminClient) {
13
+ kcAdminClientHandler = kcAdminClient;
14
+ }
15
+
16
+ /**
17
+ * ***************************** - getInfo - *******************************
18
+ * Get comprehensive server information
19
+ *
20
+ * @returns: Object containing:
21
+ * - systemInfo: System and environment information
22
+ * - memoryInfo: Memory usage statistics
23
+ * - profileInfo: Active profile information
24
+ * - themes: Available themes
25
+ * - providers: Available SPI providers
26
+ * - protocolMapperTypes: Available protocol mapper types
27
+ * - builtinProtocolMappers: Built-in protocol mappers
28
+ * - clientInstallations: Available client installation formats
29
+ * - componentTypes: Available component types
30
+ * - passwordPolicies: Available password policy types
31
+ * - enums: Various enum values used in Keycloak
32
+ * - cryptoInfo: Cryptographic information
33
+ */
34
+ exports.getInfo = function() {
35
+ return kcAdminClientHandler.serverInfo.find();
36
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * **************************************************************************************************
3
+ * **************************************************************************************************
4
+ * The User Profile entity (Keycloak 15+) allows managing declarative user profile configuration.
5
+ * This modern approach replaces the legacy attribute-based user management with a more
6
+ * structured and type-safe configuration model.
7
+ * **************************************************************************************************
8
+ * **************************************************************************************************
9
+ */
10
+ let kcAdminClientHandler = null;
11
+
12
+ exports.setKcAdminClient = function(kcAdminClient) {
13
+ kcAdminClientHandler = kcAdminClient;
14
+ }
15
+
16
+ /**
17
+ * ***************************** - getConfiguration - *******************************
18
+ * Get the user profile configuration for a realm
19
+ *
20
+ * @parameters:
21
+ * - filter: [optional] parameter
22
+ * - realm: (string, optional) - The realm name
23
+ * @returns: User profile configuration object with attributes, groups, etc.
24
+ */
25
+ exports.getConfiguration = async function(filter) {
26
+ // Direct API call for better compatibility
27
+ const realm = filter?.realm || kcAdminClientHandler.realmName;
28
+ const baseUrl = kcAdminClientHandler.baseUrl;
29
+ const token = kcAdminClientHandler.accessToken;
30
+
31
+ const url = `${baseUrl}/admin/realms/${realm}/users/profile`;
32
+
33
+ const response = await fetch(url, {
34
+ method: 'GET',
35
+ headers: {
36
+ 'Authorization': `Bearer ${token}`
37
+ }
38
+ });
39
+
40
+ if (!response.ok) {
41
+ const error = await response.text();
42
+ throw new Error(`Failed to get user profile: ${error}`);
43
+ }
44
+
45
+ return response.json();
46
+ }
47
+
48
+ /**
49
+ * ***************************** - updateConfiguration - *******************************
50
+ * Update the user profile configuration
51
+ *
52
+ * @parameters:
53
+ * - filter: [optional] parameter
54
+ * - realm: (string, optional) - The realm name
55
+ * - userProfileConfig: User profile configuration object
56
+ * - attributes: (array) - List of attribute configurations
57
+ * - name: (string) - Attribute name
58
+ * - displayName: (string) - Display name
59
+ * - validations: (object) - Validation rules
60
+ * - permissions: (object) - View/edit permissions
61
+ * - required: (object) - Required scopes
62
+ * - groups: (array) - Attribute groups
63
+ * - unmanagedAttributePolicy: (string) - Policy for unmanaged attributes
64
+ */
65
+ exports.updateConfiguration = async function(filter, userProfileConfig) {
66
+ // Direct API call for better compatibility
67
+ const realm = filter?.realm || kcAdminClientHandler.realmName;
68
+ const baseUrl = kcAdminClientHandler.baseUrl;
69
+ const token = kcAdminClientHandler.accessToken;
70
+
71
+ const url = `${baseUrl}/admin/realms/${realm}/users/profile`;
72
+
73
+ const response = await fetch(url, {
74
+ method: 'PUT',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ 'Authorization': `Bearer ${token}`
78
+ },
79
+ body: JSON.stringify(userProfileConfig)
80
+ });
81
+
82
+ if (!response.ok) {
83
+ const error = await response.text();
84
+ throw new Error(`Failed to update user profile: ${error}`);
85
+ }
86
+
87
+ return response.status === 204 ? undefined : response.json();
88
+ }
89
+
90
+ /**
91
+ * ***************************** - getMetadata - *******************************
92
+ * Get metadata about the user profile
93
+ * Returns information about available validators, attribute types, etc.
94
+ *
95
+ * @parameters:
96
+ * - filter: [optional] parameter
97
+ * - realm: (string, optional) - The realm name
98
+ * @returns: Metadata object with validators, attribute config, etc.
99
+ */
100
+ exports.getMetadata = async function(filter) {
101
+ // Direct API call for better compatibility
102
+ const realm = filter?.realm || kcAdminClientHandler.realmName;
103
+ const baseUrl = kcAdminClientHandler.baseUrl;
104
+ const token = kcAdminClientHandler.accessToken;
105
+
106
+ const url = `${baseUrl}/admin/realms/${realm}/users/profile/metadata`;
107
+
108
+ const response = await fetch(url, {
109
+ method: 'GET',
110
+ headers: {
111
+ 'Authorization': `Bearer ${token}`
112
+ }
113
+ });
114
+
115
+ if (!response.ok) {
116
+ const error = await response.text();
117
+ throw new Error(`Failed to get user profile metadata: ${error}`);
118
+ }
119
+
120
+ return response.json();
121
+ }