@happyvertical/auth 0.80.0 → 0.80.2

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 (35) hide show
  1. package/dist/chunks/cognito-thQmKf7L.js +124 -0
  2. package/dist/chunks/cognito-thQmKf7L.js.map +1 -0
  3. package/dist/chunks/decode_jwt-BvtACpi_.js +1183 -0
  4. package/dist/chunks/decode_jwt-BvtACpi_.js.map +1 -0
  5. package/dist/chunks/errors-RgVH84_1.js +343 -0
  6. package/dist/chunks/errors-RgVH84_1.js.map +1 -0
  7. package/dist/chunks/github-uNnnVjFZ.js +311 -0
  8. package/dist/chunks/github-uNnnVjFZ.js.map +1 -0
  9. package/dist/chunks/google-C_p8rExJ.js +374 -0
  10. package/dist/chunks/google-C_p8rExJ.js.map +1 -0
  11. package/dist/chunks/kanidm-DTcc6ufi.js +567 -0
  12. package/dist/chunks/kanidm-DTcc6ufi.js.map +1 -0
  13. package/dist/chunks/keycloak-CzPHgI2z.js +632 -0
  14. package/dist/chunks/keycloak-CzPHgI2z.js.map +1 -0
  15. package/dist/chunks/nostr-zrMaYMU-.js +141 -0
  16. package/dist/chunks/nostr-zrMaYMU-.js.map +1 -0
  17. package/dist/cli/claude-context.js +17 -17
  18. package/dist/cli/claude-context.js.map +1 -1
  19. package/dist/index.js +204 -486
  20. package/dist/index.js.map +1 -1
  21. package/package.json +5 -5
  22. package/dist/chunks/cognito-dmypylFX.js +0 -128
  23. package/dist/chunks/cognito-dmypylFX.js.map +0 -1
  24. package/dist/chunks/decode_jwt-D2OK1b8a.js +0 -1395
  25. package/dist/chunks/decode_jwt-D2OK1b8a.js.map +0 -1
  26. package/dist/chunks/github-NSZp5tVm.js +0 -413
  27. package/dist/chunks/github-NSZp5tVm.js.map +0 -1
  28. package/dist/chunks/google-HXk2ctYR.js +0 -483
  29. package/dist/chunks/google-HXk2ctYR.js.map +0 -1
  30. package/dist/chunks/index-BpsMhFXS.js +0 -151
  31. package/dist/chunks/index-BpsMhFXS.js.map +0 -1
  32. package/dist/chunks/kanidm-hkw-YPVF.js +0 -747
  33. package/dist/chunks/kanidm-hkw-YPVF.js.map +0 -1
  34. package/dist/chunks/keycloak-t6JEUeOz.js +0 -871
  35. package/dist/chunks/keycloak-t6JEUeOz.js.map +0 -1
@@ -0,0 +1,632 @@
1
+ import { E as UserAlreadyExistsError, O as UserNotFoundError, S as ProviderError, T as TokenExpiredError, a as ConfigurationError, b as NetworkError, c as InvalidClientError, l as InvalidCredentialsError, p as InvalidNonceError, t as AccessDeniedError, u as InvalidGrantError, v as InvalidTokenError, x as NotImplementedError, y as MfaRequiredError } from "./errors-RgVH84_1.js";
2
+ import { a as JWTClaimValidationFailed, i as JWSSignatureVerificationFailed, n as createRemoteJWKSet, o as JWTExpired, r as jwtVerify, t as decodeJwt } from "./decode_jwt-BvtACpi_.js";
3
+ //#region src/shared/providers/keycloak.ts
4
+ /**
5
+ * Keycloak Provider - OIDC/OAuth2 Authentication
6
+ *
7
+ * Implements full OIDC/OAuth2 authentication with Keycloak server including:
8
+ * - OIDC Discovery
9
+ * - Authorization Code Flow with PKCE
10
+ * - Token validation using JWKS
11
+ * - Token introspection
12
+ * - User management via Admin API
13
+ * - Session management
14
+ */
15
+ /**
16
+ * Generate a random string for state/nonce/PKCE.
17
+ */
18
+ function generateRandomString(length = 32) {
19
+ const array = new Uint8Array(length);
20
+ crypto.getRandomValues(array);
21
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
22
+ }
23
+ /**
24
+ * Generate PKCE code verifier and challenge.
25
+ */
26
+ async function generatePKCE() {
27
+ const verifier = generateRandomString(32);
28
+ const data = new TextEncoder().encode(verifier);
29
+ const hash = await crypto.subtle.digest("SHA-256", data);
30
+ return {
31
+ verifier,
32
+ challenge: btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
33
+ };
34
+ }
35
+ /**
36
+ * Keycloak authentication provider.
37
+ *
38
+ * Implements full OIDC/OAuth2 authentication with Keycloak server.
39
+ */
40
+ var KeycloakProvider = class {
41
+ options;
42
+ discoveryDocument = null;
43
+ jwks = null;
44
+ constructor(options) {
45
+ if (!options.serverUrl) throw new ConfigurationError("serverUrl is required", "keycloak");
46
+ if (!options.realm) throw new ConfigurationError("realm is required", "keycloak");
47
+ if (!options.clientId) throw new ConfigurationError("clientId is required", "keycloak");
48
+ this.options = {
49
+ usePKCE: true,
50
+ verifySsl: true,
51
+ scopes: [
52
+ "openid",
53
+ "profile",
54
+ "email"
55
+ ],
56
+ timeout: 3e4,
57
+ maxRetries: 3,
58
+ ...options
59
+ };
60
+ }
61
+ /**
62
+ * Get the base URL for the realm.
63
+ */
64
+ getRealmUrl() {
65
+ return `${this.options.serverUrl}/realms/${this.options.realm}`;
66
+ }
67
+ /**
68
+ * Get the admin API base URL.
69
+ */
70
+ getAdminUrl() {
71
+ return `${this.options.serverUrl}/admin/realms/${this.options.realm}`;
72
+ }
73
+ /**
74
+ * Make an HTTP request with error handling.
75
+ */
76
+ async request(url, options = {}, adminToken) {
77
+ const headers = {
78
+ "Content-Type": "application/json",
79
+ ...this.options.headers,
80
+ ...options.headers
81
+ };
82
+ if (adminToken) headers["Authorization"] = `Bearer ${adminToken}`;
83
+ try {
84
+ const response = await fetch(url, {
85
+ ...options,
86
+ headers,
87
+ signal: AbortSignal.timeout(this.options.timeout || 3e4)
88
+ });
89
+ if (!response.ok) {
90
+ const errorBody = await response.text().catch(() => "");
91
+ let errorData = {};
92
+ try {
93
+ errorData = JSON.parse(errorBody);
94
+ } catch {}
95
+ this.handleHttpError(response.status, errorData, errorBody);
96
+ }
97
+ const text = await response.text();
98
+ if (!text) return {};
99
+ return JSON.parse(text);
100
+ } catch (error) {
101
+ if (error instanceof Error && error.name === "TimeoutError") throw new NetworkError("Request timed out", "keycloak", error);
102
+ if (error instanceof InvalidCredentialsError || error instanceof AccessDeniedError || error instanceof InvalidGrantError || error instanceof InvalidClientError || error instanceof UserNotFoundError || error instanceof ProviderError) throw error;
103
+ throw new NetworkError(`Network error: ${error instanceof Error ? error.message : "Unknown error"}`, "keycloak", error instanceof Error ? error : void 0);
104
+ }
105
+ }
106
+ /**
107
+ * Handle HTTP error responses.
108
+ */
109
+ handleHttpError(status, data, rawBody) {
110
+ const error = data.error;
111
+ const errorDescription = data.errorMessage || data.error_description || rawBody;
112
+ switch (status) {
113
+ case 400:
114
+ if (error === "invalid_grant") throw new InvalidGrantError(errorDescription, "keycloak");
115
+ if (error === "invalid_client") throw new InvalidClientError("keycloak");
116
+ throw new ProviderError(`Bad request: ${errorDescription}`, "keycloak");
117
+ case 401: throw new InvalidCredentialsError("keycloak");
118
+ case 403: throw new AccessDeniedError(errorDescription, "keycloak");
119
+ case 404: throw new UserNotFoundError(void 0, "keycloak");
120
+ case 409: throw new UserAlreadyExistsError(void 0, "keycloak");
121
+ default: throw new ProviderError(`Keycloak error (${status}): ${errorDescription}`, "keycloak");
122
+ }
123
+ }
124
+ /**
125
+ * Fetch and cache the OIDC discovery document.
126
+ */
127
+ async fetchDiscoveryDocument() {
128
+ if (this.discoveryDocument) return this.discoveryDocument;
129
+ const url = `${this.getRealmUrl()}/.well-known/openid-configuration`;
130
+ this.discoveryDocument = await this.request(url);
131
+ return this.discoveryDocument;
132
+ }
133
+ /**
134
+ * Get JWKS for token validation.
135
+ */
136
+ async getJWKS() {
137
+ if (this.jwks) return this.jwks;
138
+ const discovery = await this.fetchDiscoveryDocument();
139
+ this.jwks = createRemoteJWKSet(new URL(discovery.jwks_uri));
140
+ return this.jwks;
141
+ }
142
+ async getAuthorizationUrl(options) {
143
+ const discovery = await this.fetchDiscoveryDocument();
144
+ const state = options?.state || generateRandomString();
145
+ const nonce = options?.nonce || generateRandomString();
146
+ const scopes = options?.scopes || this.options.scopes || [
147
+ "openid",
148
+ "profile",
149
+ "email"
150
+ ];
151
+ const redirectUri = options?.redirectUri || this.options.redirectUri;
152
+ if (!redirectUri) throw new ConfigurationError("redirectUri is required", "keycloak");
153
+ const params = new URLSearchParams({
154
+ client_id: this.options.clientId,
155
+ redirect_uri: redirectUri,
156
+ response_type: "code",
157
+ scope: scopes.join(" "),
158
+ state,
159
+ nonce
160
+ });
161
+ if (options?.prompt) params.set("prompt", options.prompt);
162
+ if (options?.loginHint) params.set("login_hint", options.loginHint);
163
+ if (options?.extraParams) for (const [key, value] of Object.entries(options.extraParams)) params.set(key, value);
164
+ let codeVerifier;
165
+ if (this.options.usePKCE) {
166
+ const pkce = await generatePKCE();
167
+ codeVerifier = pkce.verifier;
168
+ params.set("code_challenge", pkce.challenge);
169
+ params.set("code_challenge_method", "S256");
170
+ }
171
+ return {
172
+ url: `${discovery.authorization_endpoint}?${params.toString()}`,
173
+ state,
174
+ nonce,
175
+ codeVerifier
176
+ };
177
+ }
178
+ async exchangeCode(params) {
179
+ const discovery = await this.fetchDiscoveryDocument();
180
+ const redirectUri = params.redirectUri || this.options.redirectUri;
181
+ if (!redirectUri) throw new ConfigurationError("redirectUri is required", "keycloak");
182
+ const body = new URLSearchParams({
183
+ grant_type: "authorization_code",
184
+ client_id: this.options.clientId,
185
+ code: params.code,
186
+ redirect_uri: redirectUri
187
+ });
188
+ if (this.options.clientSecret) body.set("client_secret", this.options.clientSecret);
189
+ if (params.codeVerifier) body.set("code_verifier", params.codeVerifier);
190
+ const response = await this.request(discovery.token_endpoint, {
191
+ method: "POST",
192
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
193
+ body: body.toString()
194
+ });
195
+ let userId = "";
196
+ if (response.id_token) userId = decodeJwt(response.id_token).sub || "";
197
+ return {
198
+ accessToken: response.access_token,
199
+ tokenType: response.token_type || "Bearer",
200
+ expiresIn: response.expires_in,
201
+ refreshToken: response.refresh_token,
202
+ idToken: response.id_token,
203
+ scope: response.scope,
204
+ userId
205
+ };
206
+ }
207
+ async authenticate(credentials) {
208
+ const discovery = await this.fetchDiscoveryDocument();
209
+ const grantType = credentials.grantType || "password";
210
+ const scopes = credentials.scopes || this.options.scopes || [
211
+ "openid",
212
+ "profile",
213
+ "email"
214
+ ];
215
+ const body = new URLSearchParams({
216
+ grant_type: grantType,
217
+ client_id: this.options.clientId,
218
+ scope: scopes.join(" ")
219
+ });
220
+ if (grantType === "client_credentials") {
221
+ if (!this.options.clientSecret) throw new InvalidCredentialsError("keycloak", { reason: "Client secret is required for client_credentials grant" });
222
+ body.set("client_secret", this.options.clientSecret);
223
+ } else {
224
+ if (!credentials.username || !credentials.password) throw new InvalidCredentialsError("keycloak", { reason: "Username and password are required" });
225
+ body.set("username", credentials.username);
226
+ body.set("password", credentials.password);
227
+ if (this.options.clientSecret) body.set("client_secret", this.options.clientSecret);
228
+ if (credentials.mfaCode) body.set("totp", credentials.mfaCode);
229
+ }
230
+ try {
231
+ const response = await this.request(discovery.token_endpoint, {
232
+ method: "POST",
233
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
234
+ body: body.toString()
235
+ });
236
+ let userId = "";
237
+ if (response.id_token) userId = decodeJwt(response.id_token).sub || "";
238
+ else if (response.access_token) userId = decodeJwt(response.access_token).sub || "";
239
+ return {
240
+ accessToken: response.access_token,
241
+ tokenType: response.token_type || "Bearer",
242
+ expiresIn: response.expires_in,
243
+ refreshToken: response.refresh_token,
244
+ idToken: response.id_token,
245
+ scope: response.scope,
246
+ userId
247
+ };
248
+ } catch (error) {
249
+ if (error instanceof ProviderError && error.message.includes("invalid_grant") && error.message.includes("totp")) throw new MfaRequiredError("keycloak", ["totp"]);
250
+ throw error;
251
+ }
252
+ }
253
+ async refresh(refreshToken) {
254
+ const discovery = await this.fetchDiscoveryDocument();
255
+ const body = new URLSearchParams({
256
+ grant_type: "refresh_token",
257
+ client_id: this.options.clientId,
258
+ refresh_token: refreshToken
259
+ });
260
+ if (this.options.clientSecret) body.set("client_secret", this.options.clientSecret);
261
+ const response = await this.request(discovery.token_endpoint, {
262
+ method: "POST",
263
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
264
+ body: body.toString()
265
+ });
266
+ let userId = "";
267
+ if (response.access_token) userId = decodeJwt(response.access_token).sub || "";
268
+ return {
269
+ accessToken: response.access_token,
270
+ tokenType: response.token_type || "Bearer",
271
+ expiresIn: response.expires_in,
272
+ refreshToken: response.refresh_token || refreshToken,
273
+ idToken: response.id_token,
274
+ scope: response.scope,
275
+ userId
276
+ };
277
+ }
278
+ async logout(options) {
279
+ const discovery = await this.fetchDiscoveryDocument();
280
+ if (!discovery.end_session_endpoint) {
281
+ if (options?.refreshToken && discovery.revocation_endpoint) await this.request(discovery.revocation_endpoint, {
282
+ method: "POST",
283
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
284
+ body: new URLSearchParams({
285
+ client_id: this.options.clientId,
286
+ token: options.refreshToken,
287
+ token_type_hint: "refresh_token"
288
+ }).toString()
289
+ });
290
+ return;
291
+ }
292
+ const params = new URLSearchParams({ client_id: this.options.clientId });
293
+ if (options?.token) params.set("id_token_hint", options.token);
294
+ if (options?.postLogoutRedirectUri) params.set("post_logout_redirect_uri", options.postLogoutRedirectUri);
295
+ if (options?.refreshToken && discovery.revocation_endpoint) {
296
+ const revokeParams = new URLSearchParams({
297
+ client_id: this.options.clientId,
298
+ token: options.refreshToken,
299
+ token_type_hint: "refresh_token"
300
+ });
301
+ if (this.options.clientSecret) revokeParams.set("client_secret", this.options.clientSecret);
302
+ await this.request(discovery.revocation_endpoint, {
303
+ method: "POST",
304
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
305
+ body: revokeParams.toString()
306
+ });
307
+ }
308
+ }
309
+ async validateToken(token, options) {
310
+ try {
311
+ const jwks = await this.getJWKS();
312
+ const discovery = await this.fetchDiscoveryDocument();
313
+ const verifyOptions = {
314
+ issuer: options?.issuer || discovery.issuer,
315
+ clockTolerance: options?.clockTolerance || 0
316
+ };
317
+ if (options?.audience) verifyOptions.audience = options.audience;
318
+ const { payload } = await jwtVerify(token, jwks, verifyOptions);
319
+ if (options?.nonce && payload.nonce !== options.nonce) throw new InvalidNonceError("keycloak");
320
+ const roles = [];
321
+ if (payload.realm_access && typeof payload.realm_access === "object") {
322
+ const realmAccess = payload.realm_access;
323
+ if (realmAccess.roles) roles.push(...realmAccess.roles);
324
+ }
325
+ if (payload.resource_access && typeof payload.resource_access === "object") {
326
+ const resourceAccess = payload.resource_access;
327
+ for (const client of Object.values(resourceAccess)) if (client.roles) roles.push(...client.roles);
328
+ }
329
+ return {
330
+ sub: payload.sub || "",
331
+ iss: payload.iss || "",
332
+ aud: payload.aud || "",
333
+ exp: payload.exp || 0,
334
+ iat: payload.iat || 0,
335
+ nbf: payload.nbf,
336
+ azp: payload.azp,
337
+ email: payload.email,
338
+ email_verified: payload.email_verified,
339
+ preferred_username: payload.preferred_username,
340
+ name: payload.name,
341
+ roles,
342
+ ...payload
343
+ };
344
+ } catch (error) {
345
+ if (error instanceof JWTExpired) throw new TokenExpiredError("keycloak");
346
+ if (error instanceof JWTClaimValidationFailed) return null;
347
+ if (error instanceof JWSSignatureVerificationFailed) throw new InvalidTokenError("Invalid token signature", "keycloak");
348
+ if (error instanceof InvalidNonceError) throw error;
349
+ return null;
350
+ }
351
+ }
352
+ decodeToken(token) {
353
+ try {
354
+ const parts = token.split(".");
355
+ if (parts.length !== 3) throw new InvalidTokenError("Invalid JWT format", "keycloak");
356
+ const header = JSON.parse(atob(parts[0]));
357
+ const payload = decodeJwt(token);
358
+ return {
359
+ header: {
360
+ alg: header.alg,
361
+ typ: header.typ,
362
+ kid: header.kid
363
+ },
364
+ payload,
365
+ signature: parts[2]
366
+ };
367
+ } catch {
368
+ throw new InvalidTokenError("Failed to decode token", "keycloak");
369
+ }
370
+ }
371
+ async introspectToken(token) {
372
+ const discovery = await this.fetchDiscoveryDocument();
373
+ if (!discovery.introspection_endpoint) {
374
+ const claims = await this.validateToken(token);
375
+ return {
376
+ active: claims !== null,
377
+ claims: claims || void 0
378
+ };
379
+ }
380
+ const body = new URLSearchParams({
381
+ client_id: this.options.clientId,
382
+ token
383
+ });
384
+ if (this.options.clientSecret) body.set("client_secret", this.options.clientSecret);
385
+ const response = await this.request(discovery.introspection_endpoint, {
386
+ method: "POST",
387
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
388
+ body: body.toString()
389
+ });
390
+ if (!response.active) return { active: false };
391
+ return {
392
+ active: true,
393
+ claims: {
394
+ sub: response.sub || "",
395
+ iss: response.iss || "",
396
+ aud: response.aud || "",
397
+ exp: response.exp || 0,
398
+ iat: response.iat || 0,
399
+ ...response
400
+ },
401
+ tokenType: response.token_type,
402
+ clientId: response.client_id,
403
+ scope: response.scope
404
+ };
405
+ }
406
+ async getProfile(tokenOrSession) {
407
+ const discovery = await this.fetchDiscoveryDocument();
408
+ const response = await this.request(discovery.userinfo_endpoint, {
409
+ method: "GET",
410
+ headers: { Authorization: `Bearer ${tokenOrSession}` }
411
+ });
412
+ return {
413
+ id: response.sub,
414
+ username: response.preferred_username,
415
+ email: response.email,
416
+ emailVerified: response.email_verified,
417
+ firstName: response.given_name,
418
+ lastName: response.family_name,
419
+ displayName: response.name,
420
+ picture: response.picture
421
+ };
422
+ }
423
+ async updateProfile(tokenOrSession, profile) {
424
+ if (!decodeJwt(tokenOrSession).sub) throw new InvalidTokenError("Token does not contain user ID", "keycloak");
425
+ const accountUrl = `${this.getRealmUrl()}/account`;
426
+ const updateData = {};
427
+ if (profile.firstName !== void 0) updateData.firstName = profile.firstName;
428
+ if (profile.lastName !== void 0) updateData.lastName = profile.lastName;
429
+ if (profile.email !== void 0) updateData.email = profile.email;
430
+ await this.request(accountUrl, {
431
+ method: "POST",
432
+ headers: { Authorization: `Bearer ${tokenOrSession}` },
433
+ body: JSON.stringify(updateData)
434
+ });
435
+ return this.getProfile(tokenOrSession);
436
+ }
437
+ async getUser(userId, adminToken) {
438
+ if (!adminToken) throw new AccessDeniedError("Admin token required", "keycloak");
439
+ const response = await this.request(`${this.getAdminUrl()}/users/${userId}`, { method: "GET" }, adminToken);
440
+ return this.mapKeycloakUser(response);
441
+ }
442
+ async createUser(user, adminToken) {
443
+ const keycloakUser = {
444
+ username: user.username,
445
+ email: user.email,
446
+ firstName: user.firstName,
447
+ lastName: user.lastName,
448
+ enabled: user.enabled ?? true,
449
+ emailVerified: user.emailVerified ?? false,
450
+ attributes: user.attributes
451
+ };
452
+ if (user.password) keycloakUser.credentials = [{
453
+ type: "password",
454
+ value: user.password,
455
+ temporary: false
456
+ }];
457
+ const response = await fetch(`${this.getAdminUrl()}/users`, {
458
+ method: "POST",
459
+ headers: {
460
+ "Content-Type": "application/json",
461
+ Authorization: `Bearer ${adminToken}`
462
+ },
463
+ body: JSON.stringify(keycloakUser)
464
+ });
465
+ if (!response.ok) {
466
+ const errorBody = await response.text();
467
+ if (response.status === 409) throw new UserAlreadyExistsError(user.username, "keycloak");
468
+ throw new ProviderError(`Failed to create user: ${errorBody}`, "keycloak");
469
+ }
470
+ const location = response.headers.get("Location");
471
+ if (!location) throw new ProviderError("Failed to get created user ID", "keycloak");
472
+ const userId = location.split("/").pop();
473
+ if (user.roles?.length) await this.assignRoles(userId, user.roles, adminToken);
474
+ if (user.groups?.length) for (const groupName of user.groups) await this.addUserToGroup(userId, groupName, adminToken);
475
+ return this.getUser(userId, adminToken);
476
+ }
477
+ async updateUser(userId, updates, adminToken) {
478
+ const keycloakUser = {};
479
+ if (updates.username !== void 0) keycloakUser.username = updates.username;
480
+ if (updates.email !== void 0) keycloakUser.email = updates.email;
481
+ if (updates.firstName !== void 0) keycloakUser.firstName = updates.firstName;
482
+ if (updates.lastName !== void 0) keycloakUser.lastName = updates.lastName;
483
+ if (updates.enabled !== void 0) keycloakUser.enabled = updates.enabled;
484
+ if (updates.emailVerified !== void 0) keycloakUser.emailVerified = updates.emailVerified;
485
+ if (updates.attributes !== void 0) keycloakUser.attributes = updates.attributes;
486
+ await this.request(`${this.getAdminUrl()}/users/${userId}`, {
487
+ method: "PUT",
488
+ body: JSON.stringify(keycloakUser)
489
+ }, adminToken);
490
+ if (updates.password) await this.request(`${this.getAdminUrl()}/users/${userId}/reset-password`, {
491
+ method: "PUT",
492
+ body: JSON.stringify({
493
+ type: "password",
494
+ value: updates.password,
495
+ temporary: false
496
+ })
497
+ }, adminToken);
498
+ return this.getUser(userId, adminToken);
499
+ }
500
+ async deleteUser(userId, adminToken) {
501
+ await this.request(`${this.getAdminUrl()}/users/${userId}`, { method: "DELETE" }, adminToken);
502
+ }
503
+ async listUsers(query, adminToken) {
504
+ if (!adminToken) throw new AccessDeniedError("Admin token required", "keycloak");
505
+ const params = new URLSearchParams();
506
+ if (query.search) params.set("search", query.search);
507
+ if (query.email) params.set("email", query.email);
508
+ if (query.username) params.set("username", query.username);
509
+ if (query.enabled !== void 0) params.set("enabled", String(query.enabled));
510
+ if (query.limit) params.set("max", String(query.limit));
511
+ if (query.offset) params.set("first", String(query.offset));
512
+ const users = await this.request(`${this.getAdminUrl()}/users?${params.toString()}`, { method: "GET" }, adminToken);
513
+ const countResponse = await this.request(`${this.getAdminUrl()}/users/count?${params.toString()}`, { method: "GET" }, adminToken);
514
+ return {
515
+ users: users.map((u) => this.mapKeycloakUser(u)),
516
+ total: countResponse,
517
+ limit: query.limit || 100,
518
+ offset: query.offset || 0
519
+ };
520
+ }
521
+ async requestPasswordReset(email) {
522
+ throw new NotImplementedError("requestPasswordReset", "keycloak", { reason: "Requires admin token or use Keycloak login page for self-service reset" });
523
+ }
524
+ async resetPassword(token, newPassword) {
525
+ throw new NotImplementedError("resetPassword", "keycloak", { reason: "Password reset is handled by Keycloak login flow" });
526
+ }
527
+ async listSessions(userId, adminToken) {
528
+ if (!adminToken) throw new AccessDeniedError("Admin token required", "keycloak");
529
+ return (await this.request(`${this.getAdminUrl()}/users/${userId}/sessions`, { method: "GET" }, adminToken)).map((s) => ({
530
+ id: s.id,
531
+ userId: s.userId,
532
+ clientId: s.clients ? Object.keys(s.clients).join(", ") : void 0,
533
+ startedAt: new Date(s.start),
534
+ lastAccessedAt: new Date(s.lastAccess),
535
+ ipAddress: s.ipAddress,
536
+ userAgent: s.userAgent
537
+ }));
538
+ }
539
+ async revokeSession(sessionId, adminToken) {
540
+ if (!adminToken) throw new AccessDeniedError("Admin token required", "keycloak");
541
+ await this.request(`${this.getAdminUrl()}/sessions/${sessionId}`, { method: "DELETE" }, adminToken);
542
+ }
543
+ async revokeAllSessions(userId, adminToken) {
544
+ if (!adminToken) throw new AccessDeniedError("Admin token required", "keycloak");
545
+ await this.request(`${this.getAdminUrl()}/users/${userId}/logout`, { method: "POST" }, adminToken);
546
+ }
547
+ async hasRole(tokenOrUserId, role) {
548
+ return (await this.getRoles(tokenOrUserId)).includes(role);
549
+ }
550
+ async hasPermission(tokenOrUserId, permission, resource) {
551
+ const roles = await this.getRoles(tokenOrUserId);
552
+ const permissionRole = resource ? `${resource}:${permission}` : permission;
553
+ return roles.includes(permissionRole) || roles.includes(permission);
554
+ }
555
+ async getRoles(tokenOrUserId, adminToken) {
556
+ try {
557
+ const claims = await this.validateToken(tokenOrUserId);
558
+ if (claims) return claims.roles || [];
559
+ } catch {}
560
+ if (adminToken) try {
561
+ return (await this.request(`${this.getAdminUrl()}/users/${tokenOrUserId}/role-mappings`, { method: "GET" }, adminToken)).realmMappings?.map((r) => r.name) || [];
562
+ } catch {}
563
+ return [];
564
+ }
565
+ async assignRole(userId, role, adminToken) {
566
+ const roleObj = (await this.request(`${this.getAdminUrl()}/roles`, { method: "GET" }, adminToken)).find((r) => r.name === role);
567
+ if (!roleObj) throw new ProviderError(`Role not found: ${role}`, "keycloak");
568
+ await this.request(`${this.getAdminUrl()}/users/${userId}/role-mappings/realm`, {
569
+ method: "POST",
570
+ body: JSON.stringify([roleObj])
571
+ }, adminToken);
572
+ }
573
+ async removeRole(userId, role, adminToken) {
574
+ const roleObj = (await this.request(`${this.getAdminUrl()}/roles`, { method: "GET" }, adminToken)).find((r) => r.name === role);
575
+ if (!roleObj) throw new ProviderError(`Role not found: ${role}`, "keycloak");
576
+ await this.request(`${this.getAdminUrl()}/users/${userId}/role-mappings/realm`, {
577
+ method: "DELETE",
578
+ body: JSON.stringify([roleObj])
579
+ }, adminToken);
580
+ }
581
+ async getCapabilities() {
582
+ return {
583
+ authorizationCode: true,
584
+ passwordGrant: true,
585
+ clientCredentials: true,
586
+ tokenRefresh: true,
587
+ oidc: true,
588
+ userManagement: true,
589
+ sessionManagement: true,
590
+ rbac: true,
591
+ passwordReset: true,
592
+ mfa: true,
593
+ socialLogin: true,
594
+ federation: true,
595
+ decentralized: false
596
+ };
597
+ }
598
+ async getDiscoveryDocument() {
599
+ return this.fetchDiscoveryDocument();
600
+ }
601
+ mapKeycloakUser(user) {
602
+ return {
603
+ id: user.id,
604
+ username: user.username,
605
+ email: user.email,
606
+ emailVerified: user.emailVerified,
607
+ firstName: user.firstName,
608
+ lastName: user.lastName,
609
+ displayName: user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.username,
610
+ enabled: user.enabled,
611
+ createdAt: user.createdTimestamp ? new Date(user.createdTimestamp) : void 0,
612
+ attributes: user.attributes,
613
+ groups: user.groups
614
+ };
615
+ }
616
+ async assignRoles(userId, roleNames, adminToken) {
617
+ const rolesToAssign = (await this.request(`${this.getAdminUrl()}/roles`, { method: "GET" }, adminToken)).filter((r) => roleNames.includes(r.name));
618
+ if (rolesToAssign.length > 0) await this.request(`${this.getAdminUrl()}/users/${userId}/role-mappings/realm`, {
619
+ method: "POST",
620
+ body: JSON.stringify(rolesToAssign)
621
+ }, adminToken);
622
+ }
623
+ async addUserToGroup(userId, groupName, adminToken) {
624
+ const group = (await this.request(`${this.getAdminUrl()}/groups?search=${encodeURIComponent(groupName)}`, { method: "GET" }, adminToken)).find((g) => g.name === groupName);
625
+ if (!group) throw new ProviderError(`Group not found: ${groupName}`, "keycloak");
626
+ await this.request(`${this.getAdminUrl()}/users/${userId}/groups/${group.id}`, { method: "PUT" }, adminToken);
627
+ }
628
+ };
629
+ //#endregion
630
+ export { KeycloakProvider };
631
+
632
+ //# sourceMappingURL=keycloak-CzPHgI2z.js.map