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,758 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Keycloak Admin REST API client service.
|
|
3
|
+
* Handles all direct communication with Keycloak servers including authentication,
|
|
4
|
+
* user management, role management, and email actions.
|
|
5
|
+
*
|
|
6
|
+
* @module services/keycloak-client
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
PLUGIN_ID,
|
|
11
|
+
ERROR_MESSAGES,
|
|
12
|
+
TOKEN_EXPIRY_BUFFER_MS,
|
|
13
|
+
EMAIL_ACTION_LIFESPAN_SECONDS,
|
|
14
|
+
HTTP_STATUS,
|
|
15
|
+
HTTP_HEADERS,
|
|
16
|
+
KEYCLOAK_EMAIL_ACTIONS,
|
|
17
|
+
PAGINATION,
|
|
18
|
+
} from '../constants.js';
|
|
19
|
+
import { createSanitizedError } from '../utils/errors.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* In-memory cache for Keycloak access tokens.
|
|
23
|
+
* Keys are formatted as `${serverUrl}:${realmName}:${clientId}`.
|
|
24
|
+
* @type {Map<string, {accessToken: string, expiresAt: number}>}
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
const TOKEN_CACHE = new Map();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a cache key for token storage.
|
|
31
|
+
*
|
|
32
|
+
* @param {RealmConfig} realmConfig - The realm configuration
|
|
33
|
+
* @returns {string} Cache key in format "serverUrl:realmName:clientId"
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
const getCacheKey = (realmConfig) =>
|
|
37
|
+
`${realmConfig.serverUrl}:${realmConfig.realmName}:${realmConfig.clientId}`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds the Keycloak token endpoint URL.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} serverUrl - Keycloak server base URL
|
|
43
|
+
* @param {string} realmName - Target realm name
|
|
44
|
+
* @returns {string} Full token endpoint URL
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
const buildTokenUrl = (serverUrl, realmName) =>
|
|
48
|
+
`${serverUrl}/realms/${realmName}/protocol/openid-connect/token`;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Builds a Keycloak Admin API URL.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} serverUrl - Keycloak server base URL
|
|
54
|
+
* @param {string} realmName - Target realm name
|
|
55
|
+
* @param {string} [path=''] - Additional path segments
|
|
56
|
+
* @returns {string} Full Admin API URL
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
const buildAdminUrl = (serverUrl, realmName, path = '') =>
|
|
60
|
+
`${serverUrl}/admin/realms/${realmName}${path}`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {Object} RealmConfig
|
|
64
|
+
* @property {string} serverUrl - Keycloak server base URL (e.g., "https://keycloak.example.com")
|
|
65
|
+
* @property {string} realmName - Name of the Keycloak realm
|
|
66
|
+
* @property {string} clientId - OAuth client ID for service account
|
|
67
|
+
* @property {string} clientSecret - OAuth client secret
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {Object} KeycloakUser
|
|
72
|
+
* @property {string} id - Unique user identifier
|
|
73
|
+
* @property {string} username - User's username
|
|
74
|
+
* @property {string} [email] - User's email address
|
|
75
|
+
* @property {string} [firstName] - User's first name
|
|
76
|
+
* @property {string} [lastName] - User's last name
|
|
77
|
+
* @property {boolean} enabled - Whether the account is enabled
|
|
78
|
+
* @property {boolean} emailVerified - Whether email has been verified
|
|
79
|
+
* @property {number} createdTimestamp - Account creation timestamp
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} KeycloakRole
|
|
84
|
+
* @property {string} id - Unique role identifier
|
|
85
|
+
* @property {string} name - Role name
|
|
86
|
+
* @property {string} [description] - Role description
|
|
87
|
+
* @property {boolean} [composite] - Whether this is a composite role
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @typedef {Object} ConnectionTestResult
|
|
92
|
+
* @property {boolean} success - Whether connection was successful
|
|
93
|
+
* @property {string} [message] - Error message if failed
|
|
94
|
+
* @property {string} [realmDisplayName] - Realm display name if successful
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates the Keycloak client service.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} params - Service parameters
|
|
101
|
+
* @param {Object} params.strapi - Strapi instance for logging
|
|
102
|
+
* @returns {Object} Keycloak client service methods
|
|
103
|
+
*/
|
|
104
|
+
const keycloakClientService = ({ strapi }) => ({
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves or refreshes an access token for the specified realm.
|
|
107
|
+
* Tokens are cached and automatically refreshed before expiration.
|
|
108
|
+
*
|
|
109
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
110
|
+
* @returns {Promise<string>} Valid access token
|
|
111
|
+
* @throws {Error} If authentication fails
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* const token = await keycloakClient.getAccessToken(realmConfig);
|
|
115
|
+
* // Use token for API requests
|
|
116
|
+
*/
|
|
117
|
+
async getAccessToken(realmConfig) {
|
|
118
|
+
const cacheKey = getCacheKey(realmConfig);
|
|
119
|
+
const cached = TOKEN_CACHE.get(cacheKey);
|
|
120
|
+
|
|
121
|
+
// Return cached token if still valid
|
|
122
|
+
if (cached && Date.now() < cached.expiresAt - TOKEN_EXPIRY_BUFFER_MS) {
|
|
123
|
+
return cached.accessToken;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const tokenUrl = buildTokenUrl(realmConfig.serverUrl, realmConfig.realmName);
|
|
128
|
+
|
|
129
|
+
const response = await fetch(tokenUrl, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_FORM,
|
|
133
|
+
},
|
|
134
|
+
body: new URLSearchParams({
|
|
135
|
+
grant_type: 'client_credentials',
|
|
136
|
+
client_id: realmConfig.clientId,
|
|
137
|
+
client_secret: realmConfig.clientSecret,
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
const errorText = await response.text();
|
|
143
|
+
strapi.log.error(`[${PLUGIN_ID}] Token fetch failed: ${errorText}`);
|
|
144
|
+
throw createSanitizedError(
|
|
145
|
+
`Token fetch failed: ${errorText}`,
|
|
146
|
+
ERROR_MESSAGES.KEYCLOAK_AUTH_FAILED
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
|
|
152
|
+
// Cache the token with expiration timestamp
|
|
153
|
+
TOKEN_CACHE.set(cacheKey, {
|
|
154
|
+
accessToken: data.access_token,
|
|
155
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return data.access_token;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// Re-throw sanitized errors as-is
|
|
161
|
+
if (err.sanitizedMessage) throw err;
|
|
162
|
+
strapi.log.error(`[${PLUGIN_ID}] Token error:`, err);
|
|
163
|
+
throw createSanitizedError(err.message, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Tests connection to a Keycloak realm by attempting authentication
|
|
169
|
+
* and fetching realm metadata.
|
|
170
|
+
*
|
|
171
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
172
|
+
* @returns {Promise<ConnectionTestResult>} Test result with success status
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* const result = await keycloakClient.testConnection(config);
|
|
176
|
+
* if (result.success) {
|
|
177
|
+
* console.log(`Connected to ${result.realmDisplayName}`);
|
|
178
|
+
* }
|
|
179
|
+
*/
|
|
180
|
+
async testConnection(realmConfig) {
|
|
181
|
+
try {
|
|
182
|
+
const token = await this.getAccessToken(realmConfig);
|
|
183
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName);
|
|
184
|
+
|
|
185
|
+
const response = await fetch(url, {
|
|
186
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
return { success: false, message: `HTTP ${response.status}: ${response.statusText}` };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const realm = await response.json();
|
|
194
|
+
return { success: true, realmDisplayName: realm.displayName || realm.realm };
|
|
195
|
+
} catch (err) {
|
|
196
|
+
return { success: false, message: err.sanitizedMessage || err.message };
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Fetches a paginated list of users from Keycloak.
|
|
202
|
+
*
|
|
203
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
204
|
+
* @param {Object} [options={}] - Query options
|
|
205
|
+
* @param {string} [options.search=''] - Search query (matches username, email, name)
|
|
206
|
+
* @param {number} [options.first=0] - Starting index for pagination
|
|
207
|
+
* @param {number} [options.max=25] - Maximum results to return
|
|
208
|
+
* @param {boolean} [options.briefRepresentation=true] - Return minimal user data
|
|
209
|
+
* @returns {Promise<KeycloakUser[]>} Array of user objects
|
|
210
|
+
* @throws {Error} If the request fails
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* const users = await keycloakClient.getUsers(realm, {
|
|
214
|
+
* search: 'john',
|
|
215
|
+
* first: 0,
|
|
216
|
+
* max: 10
|
|
217
|
+
* });
|
|
218
|
+
*/
|
|
219
|
+
async getUsers(realmConfig, { search = '', first = 0, max = PAGINATION.PAGE_SIZE, briefRepresentation = true } = {}) {
|
|
220
|
+
const token = await this.getAccessToken(realmConfig);
|
|
221
|
+
const params = new URLSearchParams({
|
|
222
|
+
first: String(first),
|
|
223
|
+
max: String(max),
|
|
224
|
+
briefRepresentation: String(briefRepresentation),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (search) {
|
|
228
|
+
params.set('search', search);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const url = `${buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, '/users')}?${params}`;
|
|
232
|
+
|
|
233
|
+
const response = await fetch(url, {
|
|
234
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const errorText = await response.text();
|
|
239
|
+
strapi.log.error(`[${PLUGIN_ID}] Failed to fetch users: ${errorText}`);
|
|
240
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return response.json();
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Counts total users in a realm, optionally filtered by search query.
|
|
248
|
+
*
|
|
249
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
250
|
+
* @param {Object} [options={}] - Query options
|
|
251
|
+
* @param {string} [options.search=''] - Search query to filter count
|
|
252
|
+
* @returns {Promise<number>} Total user count
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* const total = await keycloakClient.countUsers(realm, { search: 'admin' });
|
|
256
|
+
*/
|
|
257
|
+
async countUsers(realmConfig, { search = '' } = {}) {
|
|
258
|
+
const token = await this.getAccessToken(realmConfig);
|
|
259
|
+
const params = new URLSearchParams();
|
|
260
|
+
if (search) params.set('search', search);
|
|
261
|
+
|
|
262
|
+
const url = `${buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, '/users/count')}?${params}`;
|
|
263
|
+
|
|
264
|
+
const response = await fetch(url, {
|
|
265
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return response.json();
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Retrieves a single user by their Keycloak ID.
|
|
277
|
+
*
|
|
278
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
279
|
+
* @param {string} userId - Keycloak user ID
|
|
280
|
+
* @returns {Promise<KeycloakUser>} User object with full details
|
|
281
|
+
* @throws {Error} If user not found or request fails
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* const user = await keycloakClient.getUserById(realm, 'abc-123');
|
|
285
|
+
*/
|
|
286
|
+
async getUserById(realmConfig, userId) {
|
|
287
|
+
const token = await this.getAccessToken(realmConfig);
|
|
288
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}`);
|
|
289
|
+
|
|
290
|
+
const response = await fetch(url, {
|
|
291
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
296
|
+
throw createSanitizedError(`User ${userId} not found`, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
297
|
+
}
|
|
298
|
+
const errorText = await response.text();
|
|
299
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return response.json();
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Creates a new user in Keycloak.
|
|
307
|
+
*
|
|
308
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
309
|
+
* @param {Object} userData - User data to create
|
|
310
|
+
* @param {string} userData.username - Required username
|
|
311
|
+
* @param {string} [userData.email] - Email address
|
|
312
|
+
* @param {string} [userData.firstName] - First name
|
|
313
|
+
* @param {string} [userData.lastName] - Last name
|
|
314
|
+
* @param {boolean} [userData.enabled=true] - Account enabled status
|
|
315
|
+
* @param {boolean} [userData.emailVerified=false] - Email verification status
|
|
316
|
+
* @returns {Promise<{userId: string|null, location: string|null}>} Created user info
|
|
317
|
+
* @throws {Error} If user creation fails (e.g., duplicate username/email)
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* const result = await keycloakClient.createUser(realm, {
|
|
321
|
+
* username: 'newuser',
|
|
322
|
+
* email: 'new@example.com',
|
|
323
|
+
* enabled: true
|
|
324
|
+
* });
|
|
325
|
+
*/
|
|
326
|
+
async createUser(realmConfig, userData) {
|
|
327
|
+
const token = await this.getAccessToken(realmConfig);
|
|
328
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, '/users');
|
|
329
|
+
|
|
330
|
+
const response = await fetch(url, {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: {
|
|
333
|
+
Authorization: `Bearer ${token}`,
|
|
334
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
335
|
+
},
|
|
336
|
+
body: JSON.stringify(userData),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (!response.ok) {
|
|
340
|
+
const errorText = await response.text();
|
|
341
|
+
strapi.log.error(`[${PLUGIN_ID}] Failed to create user: ${errorText}`);
|
|
342
|
+
|
|
343
|
+
if (response.status === HTTP_STATUS.CONFLICT) {
|
|
344
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.USER_ALREADY_EXISTS);
|
|
345
|
+
}
|
|
346
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.INVALID_USER_DATA);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Extract user ID from Location header
|
|
350
|
+
const location = response.headers.get('Location');
|
|
351
|
+
const userId = location ? location.split('/').pop() : null;
|
|
352
|
+
|
|
353
|
+
return { userId, location };
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Updates an existing user's attributes.
|
|
358
|
+
*
|
|
359
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
360
|
+
* @param {string} userId - Keycloak user ID
|
|
361
|
+
* @param {Object} userData - Updated user attributes
|
|
362
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
363
|
+
* @throws {Error} If user not found or update fails
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* await keycloakClient.updateUser(realm, userId, {
|
|
367
|
+
* firstName: 'Updated',
|
|
368
|
+
* enabled: false
|
|
369
|
+
* });
|
|
370
|
+
*/
|
|
371
|
+
async updateUser(realmConfig, userId, userData) {
|
|
372
|
+
const token = await this.getAccessToken(realmConfig);
|
|
373
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}`);
|
|
374
|
+
|
|
375
|
+
const response = await fetch(url, {
|
|
376
|
+
method: 'PUT',
|
|
377
|
+
headers: {
|
|
378
|
+
Authorization: `Bearer ${token}`,
|
|
379
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
380
|
+
},
|
|
381
|
+
body: JSON.stringify(userData),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
const errorText = await response.text();
|
|
386
|
+
strapi.log.error(`[${PLUGIN_ID}] Failed to update user: ${errorText}`);
|
|
387
|
+
|
|
388
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
389
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
390
|
+
}
|
|
391
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.INVALID_USER_DATA);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return { success: true };
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Permanently deletes a user from Keycloak.
|
|
399
|
+
*
|
|
400
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
401
|
+
* @param {string} userId - Keycloak user ID
|
|
402
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
403
|
+
* @throws {Error} If user not found or deletion fails
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* await keycloakClient.deleteUser(realm, 'user-id-123');
|
|
407
|
+
*/
|
|
408
|
+
async deleteUser(realmConfig, userId) {
|
|
409
|
+
const token = await this.getAccessToken(realmConfig);
|
|
410
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}`);
|
|
411
|
+
|
|
412
|
+
const response = await fetch(url, {
|
|
413
|
+
method: 'DELETE',
|
|
414
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
const errorText = await response.text();
|
|
419
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
420
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
421
|
+
}
|
|
422
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return { success: true };
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Sets a new password for a user.
|
|
430
|
+
*
|
|
431
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
432
|
+
* @param {string} userId - Keycloak user ID
|
|
433
|
+
* @param {string} password - New password value
|
|
434
|
+
* @param {boolean} [temporary=true] - If true, user must change password on next login
|
|
435
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
436
|
+
* @throws {Error} If user not found or password reset fails
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* // Set temporary password (user must change on login)
|
|
440
|
+
* await keycloakClient.resetPassword(realm, userId, 'TempPass123!', true);
|
|
441
|
+
*
|
|
442
|
+
* // Set permanent password
|
|
443
|
+
* await keycloakClient.resetPassword(realm, userId, 'NewPass123!', false);
|
|
444
|
+
*/
|
|
445
|
+
async resetPassword(realmConfig, userId, password, temporary = true) {
|
|
446
|
+
const token = await this.getAccessToken(realmConfig);
|
|
447
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/reset-password`);
|
|
448
|
+
|
|
449
|
+
const response = await fetch(url, {
|
|
450
|
+
method: 'PUT',
|
|
451
|
+
headers: {
|
|
452
|
+
Authorization: `Bearer ${token}`,
|
|
453
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
454
|
+
},
|
|
455
|
+
body: JSON.stringify({
|
|
456
|
+
type: 'password',
|
|
457
|
+
value: password,
|
|
458
|
+
temporary,
|
|
459
|
+
}),
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
if (!response.ok) {
|
|
463
|
+
const errorText = await response.text();
|
|
464
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
465
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
466
|
+
}
|
|
467
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.PASSWORD_RESET_FAILED);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { success: true };
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Enables a user account.
|
|
475
|
+
*
|
|
476
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
477
|
+
* @param {string} userId - Keycloak user ID
|
|
478
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
479
|
+
*/
|
|
480
|
+
async enableUser(realmConfig, userId) {
|
|
481
|
+
return this.updateUser(realmConfig, userId, { enabled: true });
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Disables a user account.
|
|
486
|
+
*
|
|
487
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
488
|
+
* @param {string} userId - Keycloak user ID
|
|
489
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
490
|
+
*/
|
|
491
|
+
async disableUser(realmConfig, userId) {
|
|
492
|
+
return this.updateUser(realmConfig, userId, { enabled: false });
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Triggers email actions for a user (e.g., verify email, update password).
|
|
497
|
+
*
|
|
498
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
499
|
+
* @param {string} userId - Keycloak user ID
|
|
500
|
+
* @param {string[]} actions - Array of action types (e.g., ['VERIFY_EMAIL'])
|
|
501
|
+
* @param {number} [lifespan] - Link validity in seconds (default: 12 hours)
|
|
502
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
503
|
+
* @throws {Error} If user not found or email fails
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* // Send verification email
|
|
507
|
+
* await keycloakClient.executeEmailActions(realm, userId, ['VERIFY_EMAIL']);
|
|
508
|
+
*
|
|
509
|
+
* // Send password reset with custom lifespan (1 hour)
|
|
510
|
+
* await keycloakClient.executeEmailActions(realm, userId, ['UPDATE_PASSWORD'], 3600);
|
|
511
|
+
*/
|
|
512
|
+
async executeEmailActions(realmConfig, userId, actions, lifespan = EMAIL_ACTION_LIFESPAN_SECONDS) {
|
|
513
|
+
const token = await this.getAccessToken(realmConfig);
|
|
514
|
+
const params = new URLSearchParams({ lifespan: String(lifespan) });
|
|
515
|
+
const url = `${buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/execute-actions-email`)}?${params}`;
|
|
516
|
+
|
|
517
|
+
const response = await fetch(url, {
|
|
518
|
+
method: 'PUT',
|
|
519
|
+
headers: {
|
|
520
|
+
Authorization: `Bearer ${token}`,
|
|
521
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
522
|
+
},
|
|
523
|
+
body: JSON.stringify(actions),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (!response.ok) {
|
|
527
|
+
const errorText = await response.text();
|
|
528
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
529
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
530
|
+
}
|
|
531
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.EMAIL_SEND_FAILED);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return { success: true };
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Sends an email verification link to the user.
|
|
539
|
+
*
|
|
540
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
541
|
+
* @param {string} userId - Keycloak user ID
|
|
542
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
543
|
+
*/
|
|
544
|
+
async sendVerificationEmail(realmConfig, userId) {
|
|
545
|
+
return this.executeEmailActions(realmConfig, userId, [KEYCLOAK_EMAIL_ACTIONS.VERIFY_EMAIL]);
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Sends a password reset email to the user.
|
|
550
|
+
*
|
|
551
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
552
|
+
* @param {string} userId - Keycloak user ID
|
|
553
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
554
|
+
*/
|
|
555
|
+
async sendResetPasswordEmail(realmConfig, userId) {
|
|
556
|
+
return this.executeEmailActions(realmConfig, userId, [KEYCLOAK_EMAIL_ACTIONS.UPDATE_PASSWORD]);
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Retrieves all realm-level roles.
|
|
561
|
+
*
|
|
562
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
563
|
+
* @returns {Promise<KeycloakRole[]>} Array of realm roles
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* const roles = await keycloakClient.getRealmRoles(realm);
|
|
567
|
+
* // [{ id: '...', name: 'admin', ... }, ...]
|
|
568
|
+
*/
|
|
569
|
+
async getRealmRoles(realmConfig) {
|
|
570
|
+
const token = await this.getAccessToken(realmConfig);
|
|
571
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, '/roles');
|
|
572
|
+
|
|
573
|
+
const response = await fetch(url, {
|
|
574
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
if (!response.ok) {
|
|
578
|
+
const errorText = await response.text();
|
|
579
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return response.json();
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Gets realm roles assigned to a specific user.
|
|
587
|
+
*
|
|
588
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
589
|
+
* @param {string} userId - Keycloak user ID
|
|
590
|
+
* @returns {Promise<KeycloakRole[]>} Array of assigned roles
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* const userRoles = await keycloakClient.getUserRoles(realm, userId);
|
|
594
|
+
*/
|
|
595
|
+
async getUserRoles(realmConfig, userId) {
|
|
596
|
+
const token = await this.getAccessToken(realmConfig);
|
|
597
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/role-mappings/realm`);
|
|
598
|
+
|
|
599
|
+
const response = await fetch(url, {
|
|
600
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
if (!response.ok) {
|
|
604
|
+
if (response.status === HTTP_STATUS.NOT_FOUND) {
|
|
605
|
+
throw createSanitizedError(`User ${userId} not found`, ERROR_MESSAGES.USER_NOT_FOUND);
|
|
606
|
+
}
|
|
607
|
+
const errorText = await response.text();
|
|
608
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.KEYCLOAK_CONNECTION_FAILED);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return response.json();
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Assigns realm roles to a user.
|
|
616
|
+
*
|
|
617
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
618
|
+
* @param {string} userId - Keycloak user ID
|
|
619
|
+
* @param {KeycloakRole[]} roles - Array of role objects with id and name
|
|
620
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* await keycloakClient.assignRoles(realm, userId, [
|
|
624
|
+
* { id: 'role-id-1', name: 'admin' },
|
|
625
|
+
* { id: 'role-id-2', name: 'editor' }
|
|
626
|
+
* ]);
|
|
627
|
+
*/
|
|
628
|
+
async assignRoles(realmConfig, userId, roles) {
|
|
629
|
+
const token = await this.getAccessToken(realmConfig);
|
|
630
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/role-mappings/realm`);
|
|
631
|
+
|
|
632
|
+
const response = await fetch(url, {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: {
|
|
635
|
+
Authorization: `Bearer ${token}`,
|
|
636
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
637
|
+
},
|
|
638
|
+
body: JSON.stringify(roles),
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
if (!response.ok) {
|
|
642
|
+
const errorText = await response.text();
|
|
643
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.ROLE_ASSIGNMENT_FAILED);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return { success: true };
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Removes realm roles from a user.
|
|
651
|
+
*
|
|
652
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
653
|
+
* @param {string} userId - Keycloak user ID
|
|
654
|
+
* @param {KeycloakRole[]} roles - Array of role objects to remove
|
|
655
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
656
|
+
*
|
|
657
|
+
* @example
|
|
658
|
+
* await keycloakClient.removeRoles(realm, userId, [
|
|
659
|
+
* { id: 'role-id-1', name: 'admin' }
|
|
660
|
+
* ]);
|
|
661
|
+
*/
|
|
662
|
+
async removeRoles(realmConfig, userId, roles) {
|
|
663
|
+
const token = await this.getAccessToken(realmConfig);
|
|
664
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/role-mappings/realm`);
|
|
665
|
+
|
|
666
|
+
const response = await fetch(url, {
|
|
667
|
+
method: 'DELETE',
|
|
668
|
+
headers: {
|
|
669
|
+
Authorization: `Bearer ${token}`,
|
|
670
|
+
'Content-Type': HTTP_HEADERS.CONTENT_TYPE_JSON,
|
|
671
|
+
},
|
|
672
|
+
body: JSON.stringify(roles),
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
if (!response.ok) {
|
|
676
|
+
const errorText = await response.text();
|
|
677
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.ROLE_REMOVAL_FAILED);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return { success: true };
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Gets active sessions for a user.
|
|
685
|
+
*
|
|
686
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
687
|
+
* @param {string} userId - Keycloak user ID
|
|
688
|
+
* @returns {Promise<Object[]>} Array of session objects
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* const sessions = await keycloakClient.getUserSessions(realm, userId);
|
|
692
|
+
*/
|
|
693
|
+
async getUserSessions(realmConfig, userId) {
|
|
694
|
+
const token = await this.getAccessToken(realmConfig);
|
|
695
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/sessions`);
|
|
696
|
+
|
|
697
|
+
const response = await fetch(url, {
|
|
698
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
if (!response.ok) {
|
|
702
|
+
return [];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return response.json();
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Terminates all active sessions for a user.
|
|
710
|
+
*
|
|
711
|
+
* @param {RealmConfig} realmConfig - Realm connection configuration
|
|
712
|
+
* @param {string} userId - Keycloak user ID
|
|
713
|
+
* @returns {Promise<{success: boolean}>} Success indicator
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* await keycloakClient.logoutUser(realm, userId);
|
|
717
|
+
*/
|
|
718
|
+
async logoutUser(realmConfig, userId) {
|
|
719
|
+
const token = await this.getAccessToken(realmConfig);
|
|
720
|
+
const url = buildAdminUrl(realmConfig.serverUrl, realmConfig.realmName, `/users/${userId}/logout`);
|
|
721
|
+
|
|
722
|
+
const response = await fetch(url, {
|
|
723
|
+
method: 'POST',
|
|
724
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
if (!response.ok && response.status !== HTTP_STATUS.NO_CONTENT) {
|
|
728
|
+
const errorText = await response.text();
|
|
729
|
+
throw createSanitizedError(errorText, ERROR_MESSAGES.LOGOUT_FAILED);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return { success: true };
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Clears cached access tokens.
|
|
737
|
+
* Call this when realm configuration changes to force re-authentication.
|
|
738
|
+
*
|
|
739
|
+
* @param {RealmConfig} [realmConfig] - Specific realm to clear, or all if omitted
|
|
740
|
+
*
|
|
741
|
+
* @example
|
|
742
|
+
* // Clear specific realm's token
|
|
743
|
+
* keycloakClient.clearTokenCache(realmConfig);
|
|
744
|
+
*
|
|
745
|
+
* // Clear all cached tokens
|
|
746
|
+
* keycloakClient.clearTokenCache();
|
|
747
|
+
*/
|
|
748
|
+
clearTokenCache(realmConfig) {
|
|
749
|
+
if (realmConfig) {
|
|
750
|
+
const cacheKey = getCacheKey(realmConfig);
|
|
751
|
+
TOKEN_CACHE.delete(cacheKey);
|
|
752
|
+
} else {
|
|
753
|
+
TOKEN_CACHE.clear();
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
export default keycloakClientService;
|