@vunexa/lixa 0.0.1-alpha.8 → 0.1.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 (52) hide show
  1. package/README.md +263 -143
  2. package/dist/dao/session-cache.d.ts +11 -0
  3. package/dist/dao/session-cache.d.ts.map +1 -0
  4. package/dist/dao/state-cache.d.ts +8 -6
  5. package/dist/dao/state-cache.d.ts.map +1 -1
  6. package/dist/dao/types.d.ts +376 -3
  7. package/dist/dao/types.d.ts.map +1 -1
  8. package/dist/export-types/index.d.ts +1397 -0
  9. package/dist/export-types/tsdoc-metadata.json +11 -0
  10. package/dist/index.cjs +1035 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +1361 -0
  13. package/dist/index.d.ts +11 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +992 -9
  16. package/dist/index.js.map +1 -1
  17. package/dist/lixa.d.ts +286 -19
  18. package/dist/lixa.d.ts.map +1 -1
  19. package/dist/models/session.d.ts +316 -0
  20. package/dist/models/session.d.ts.map +1 -0
  21. package/dist/providers/IProvider.d.ts +127 -4
  22. package/dist/providers/IProvider.d.ts.map +1 -1
  23. package/dist/providers/index.d.ts +0 -2
  24. package/dist/providers/index.d.ts.map +1 -1
  25. package/dist/types.d.ts +195 -24
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/utils/user-info.d.ts +82 -0
  28. package/dist/utils/user-info.d.ts.map +1 -0
  29. package/package.json +15 -10
  30. package/dist/dao/state-cache.js +0 -18
  31. package/dist/dao/state-cache.js.map +0 -1
  32. package/dist/dao/types.js +0 -2
  33. package/dist/dao/types.js.map +0 -1
  34. package/dist/lixa.js +0 -248
  35. package/dist/lixa.js.map +0 -1
  36. package/dist/providers/IProvider.js +0 -2
  37. package/dist/providers/IProvider.js.map +0 -1
  38. package/dist/providers/github.d.ts +0 -9
  39. package/dist/providers/github.d.ts.map +0 -1
  40. package/dist/providers/github.js +0 -8
  41. package/dist/providers/github.js.map +0 -1
  42. package/dist/providers/google.d.ts +0 -9
  43. package/dist/providers/google.d.ts.map +0 -1
  44. package/dist/providers/google.js +0 -8
  45. package/dist/providers/google.js.map +0 -1
  46. package/dist/providers/index.js +0 -3
  47. package/dist/providers/index.js.map +0 -1
  48. package/dist/types.js +0 -2
  49. package/dist/types.js.map +0 -1
  50. package/dist/utils/constants.js +0 -4
  51. package/dist/utils/constants.js.map +0 -1
  52. package/index.d.ts +0 -227
package/dist/index.cjs ADDED
@@ -0,0 +1,1035 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ AccountLinkingStrategy: () => AccountLinkingStrategy,
34
+ Lixa: () => Lixa,
35
+ decodeIdToken: () => decodeIdToken,
36
+ determineProviderFromIssuer: () => determineProviderFromIssuer,
37
+ extractUserInfo: () => extractUserInfo,
38
+ fetchUserInfo: () => fetchUserInfo
39
+ });
40
+ module.exports = __toCommonJS(src_exports);
41
+
42
+ // src/lixa.ts
43
+ var import_crypto2 = require("crypto");
44
+
45
+ // src/types.ts
46
+ var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
47
+ AccountLinkingStrategy2["AUTO_LINK_BY_VERIFIED_EMAIL"] = "AUTO_LINK_BY_VERIFIED_EMAIL";
48
+ AccountLinkingStrategy2["ISOLATED"] = "ISOLATED";
49
+ return AccountLinkingStrategy2;
50
+ })(AccountLinkingStrategy || {});
51
+
52
+ // src/dao/state-cache.ts
53
+ var import_node_cache = __toESM(require("node-cache"), 1);
54
+ var import_crypto = require("crypto");
55
+ var LocalStateHandler = class {
56
+ cache;
57
+ stateStorage;
58
+ constructor(defaultTtlSeconds = 600) {
59
+ this.cache = new import_node_cache.default({ stdTTL: defaultTtlSeconds });
60
+ this.stateStorage = {
61
+ saveState: async (state, data, expiresInSeconds) => {
62
+ this.cache.set(state, data, expiresInSeconds);
63
+ },
64
+ getState: async (state) => {
65
+ return this.cache.get(state) || null;
66
+ },
67
+ deleteState: async (state) => {
68
+ this.cache.del(state);
69
+ }
70
+ };
71
+ }
72
+ // Default GenerateState implementation
73
+ async generateState(provider) {
74
+ const state = (0, import_crypto.randomBytes)(16).toString("hex");
75
+ const codeVerifier = (0, import_crypto.randomBytes)(32).toString("hex");
76
+ return {
77
+ state,
78
+ data: {
79
+ provider,
80
+ codeVerifier,
81
+ createdAt: Date.now()
82
+ }
83
+ };
84
+ }
85
+ };
86
+
87
+ // src/lixa.ts
88
+ var import_crypto3 = __toESM(require("crypto"), 1);
89
+
90
+ // src/dao/session-cache.ts
91
+ var import_node_cache2 = __toESM(require("node-cache"), 1);
92
+ var LocalSessionHandler = class {
93
+ cache;
94
+ emailToSessionMap = /* @__PURE__ */ new Map();
95
+ sessionStorage;
96
+ constructor(defaultTtlSeconds = 600) {
97
+ this.cache = new import_node_cache2.default({ stdTTL: defaultTtlSeconds });
98
+ this.sessionStorage = {
99
+ saveSession: async (sessionId, session, expiresInSeconds) => {
100
+ this.cache.set(sessionId, session, expiresInSeconds);
101
+ if (session.email) {
102
+ this.emailToSessionMap.set(session.email.toLowerCase(), sessionId);
103
+ }
104
+ },
105
+ getSession: async (sessionId) => {
106
+ return this.cache.get(sessionId) || null;
107
+ },
108
+ deleteSession: async (sessionId) => {
109
+ const session = this.cache.get(sessionId);
110
+ if (session?.email) {
111
+ this.emailToSessionMap.delete(session.email.toLowerCase());
112
+ }
113
+ this.cache.del(sessionId);
114
+ },
115
+ getSessionByEmail: async (email) => {
116
+ const normalizedEmail = email.toLowerCase();
117
+ const sessionId = this.emailToSessionMap.get(normalizedEmail);
118
+ if (!sessionId) return null;
119
+ const session = this.cache.get(sessionId);
120
+ if (!session) {
121
+ this.emailToSessionMap.delete(normalizedEmail);
122
+ return null;
123
+ }
124
+ return { sessionId, session };
125
+ }
126
+ };
127
+ }
128
+ // Default GenerateSession implementation
129
+ async GenerateSession(tokenData, providerMetadata) {
130
+ if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
131
+ throw new Error("No valid access token found in OAuth response");
132
+ }
133
+ const session = {
134
+ token: tokenData.access_token,
135
+ raw: tokenData
136
+ };
137
+ return session;
138
+ }
139
+ };
140
+
141
+ // src/utils/user-info.ts
142
+ function decodeIdToken(idToken) {
143
+ const parts = idToken.split(".");
144
+ if (parts.length !== 3) {
145
+ throw new Error("Invalid ID token format: expected 3 parts separated by dots");
146
+ }
147
+ const base64Payload = parts[1];
148
+ if (!base64Payload) {
149
+ throw new Error("Invalid ID token: missing payload section");
150
+ }
151
+ const payload = Buffer.from(base64Payload, "base64").toString();
152
+ try {
153
+ return JSON.parse(payload);
154
+ } catch (error) {
155
+ throw new Error("Invalid ID token: failed to parse payload JSON");
156
+ }
157
+ }
158
+ function determineProviderFromIssuer(userInfo) {
159
+ if (!userInfo.iss) {
160
+ return null;
161
+ }
162
+ const issuer = userInfo.iss.toLowerCase();
163
+ if (issuer.includes("accounts.google.com")) {
164
+ return "google";
165
+ }
166
+ if (issuer.includes("github")) {
167
+ return "github";
168
+ }
169
+ return null;
170
+ }
171
+ async function fetchUserInfo(accessToken, userInfoEndpoint) {
172
+ const response = await fetch(userInfoEndpoint, {
173
+ headers: {
174
+ Authorization: `Bearer ${accessToken}`,
175
+ Accept: "application/json"
176
+ }
177
+ });
178
+ if (!response.ok) {
179
+ throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);
180
+ }
181
+ const data = await response.json();
182
+ if (!data || typeof data !== "object" || !("email" in data) || typeof data.email !== "string") {
183
+ throw new Error(`Invalid user info response: missing or invalid email`);
184
+ }
185
+ return {
186
+ email: data.email,
187
+ id: "id" in data ? String(data.id) : void 0,
188
+ sub: "sub" in data ? String(data.sub) : void 0,
189
+ given_name: "given_name" in data ? String(data.given_name) : void 0,
190
+ family_name: "family_name" in data ? String(data.family_name) : void 0,
191
+ name: "name" in data ? String(data.name) : void 0,
192
+ picture: "picture" in data ? String(data.picture) : void 0,
193
+ email_verified: "email_verified" in data ? Boolean(data.email_verified) : void 0,
194
+ iss: "iss" in data ? String(data.iss) : void 0
195
+ };
196
+ }
197
+ async function extractUserInfo(tokenData, providerMetadata) {
198
+ let userInfo;
199
+ const userInfoEndpoint = providerMetadata.endpoints.userInfo;
200
+ if (tokenData.id_token) {
201
+ userInfo = decodeIdToken(tokenData.id_token);
202
+ } else if (tokenData.access_token) {
203
+ userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
204
+ } else {
205
+ throw new Error("No ID token or access token available to fetch user info");
206
+ }
207
+ return { userInfo };
208
+ }
209
+
210
+ // src/lixa.ts
211
+ var Lixa = class _Lixa {
212
+ static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
213
+ static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
214
+ // Legacy registry for backward compatibility
215
+ static LOCAL_STATE_HANDLER = new LocalStateHandler();
216
+ static LOCAL_SESSION_HANDLER = new LocalSessionHandler();
217
+ config;
218
+ stateHandler;
219
+ sessionHandler;
220
+ debug;
221
+ /**
222
+ * Creates a new Lixa instance with the provided configuration.
223
+ *
224
+ * @remarks
225
+ * Providers can be passed inline in the configuration using the `provider` field.
226
+ * Provider resolution priority: inline custom provider \> default providers \> legacy registry.
227
+ *
228
+ * @param config - The configuration object containing provider settings and optional session strategy
229
+ *
230
+ * @throws Error when provider configuration is missing required fields
231
+ * @throws Error when provider implementation is missing required properties
232
+ * @throws Error when provider is not available and no inline implementation is provided
233
+ */
234
+ constructor(config) {
235
+ this.config = config;
236
+ this.stateHandler = config.stateHandler || _Lixa.LOCAL_STATE_HANDLER;
237
+ this.sessionHandler = config.sessionHandler || _Lixa.LOCAL_SESSION_HANDLER;
238
+ this.debug = config.debug || false;
239
+ this.log("INFO", "Init", "Initializing Lixa instance", {
240
+ providers: Object.keys(config.providers),
241
+ debug: this.debug
242
+ });
243
+ for (const [providerName, providerConfig] of Object.entries(config.providers)) {
244
+ const name = providerName.toLowerCase();
245
+ const typedConfig = providerConfig;
246
+ this.validateProviderConfig(providerName, typedConfig);
247
+ if (typedConfig.provider) {
248
+ this.validateProviderImplementation(providerName, typedConfig.provider);
249
+ this.log("INFO", "Init", `Registered inline provider: ${providerName}`);
250
+ } else {
251
+ if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
252
+ this.log("ERROR", "Init", `Provider '${providerName}' not available`);
253
+ throw new Error(
254
+ `Provider '${providerName}' is not available. Either import it from '@vunexa/lixa-providers' and include it in the configuration, or provide a custom implementation using the 'provider' field: { provider: new CustomProvider(), clientId: '...', ... }`
255
+ );
256
+ }
257
+ this.log("INFO", "Init", `Using registered provider: ${providerName}`);
258
+ }
259
+ }
260
+ this.log("INFO", "Init", "Lixa instance initialized successfully");
261
+ }
262
+ /**
263
+ * Validates that a provider configuration has all required credentials.
264
+ *
265
+ * @param name - The provider name
266
+ * @param config - The provider configuration
267
+ * @throws Error when required fields are missing or invalid
268
+ */
269
+ validateProviderConfig(name, config) {
270
+ const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
271
+ const missingFields = requiredFields.filter((field) => {
272
+ const value = config[field];
273
+ return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
274
+ });
275
+ if (missingFields.length > 0) {
276
+ throw new Error(
277
+ `Provider '${name}' configuration is missing required fields: ${missingFields.join(", ")}`
278
+ );
279
+ }
280
+ if (!Array.isArray(config.scopes)) {
281
+ throw new Error(
282
+ `Provider '${name}' configuration error: 'scopes' must be an array of strings`
283
+ );
284
+ }
285
+ if (config.scopes.length === 0) {
286
+ throw new Error(
287
+ `Provider '${name}' configuration error: 'scopes' array cannot be empty`
288
+ );
289
+ }
290
+ }
291
+ /**
292
+ * Validates that a provider implementation has all required properties.
293
+ *
294
+ * @param name - The provider name
295
+ * @param provider - The provider implementation
296
+ * @throws Error when required properties are missing
297
+ */
298
+ validateProviderImplementation(name, provider) {
299
+ const requiredProps = ["authorizationEndpoint", "tokenEndpoint", "userInfoEndpoint"];
300
+ const missingProps = requiredProps.filter((prop) => {
301
+ const value = provider[prop];
302
+ return !value || typeof value !== "string" || value.trim() === "";
303
+ });
304
+ if (missingProps.length > 0) {
305
+ throw new Error(
306
+ `Provider '${name}' implementation is missing required properties: ${missingProps.join(", ")}. All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`
307
+ );
308
+ }
309
+ }
310
+ /**
311
+ * Structured debug logging with standardized format.
312
+ *
313
+ * @param level - Log level (INFO, WARN, ERROR)
314
+ * @param context - Context of the log (Init, Auth, Token, Session, State)
315
+ * @param message - Log message
316
+ * @param data - Optional data to log
317
+ *
318
+ * @remarks
319
+ * Format: [Lixa] [timestamp] [level] [context] message
320
+ * Only logs when debug mode is enabled.
321
+ */
322
+ log(level, context, message, data) {
323
+ if (!this.debug) return;
324
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
325
+ const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
326
+ if (data !== void 0) {
327
+ console.log(`${prefix} ${message}`, data);
328
+ } else {
329
+ console.log(`${prefix} ${message}`);
330
+ }
331
+ }
332
+ /**
333
+ * Checks if a provider is configured for this instance.
334
+ * This is a type guard that narrows the provider type for use with getAuthUrl.
335
+ *
336
+ * @param provider - The provider name to check (case-insensitive)
337
+ * @returns True if the provider is configured, false otherwise
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * if (lixa.isProviderConfigured(provider)) {
342
+ * // TypeScript now knows provider is a valid ConfiguredProviderKey
343
+ * const authUrl = lixa.getAuthUrl(provider, state);
344
+ * }
345
+ * ```
346
+ */
347
+ isProviderConfigured(provider) {
348
+ const providerType = provider.toLowerCase();
349
+ return this.config.providers.hasOwnProperty(providerType);
350
+ }
351
+ /**
352
+ * Gets a provider implementation by name.
353
+ * Resolution priority: inline custom provider \> default providers \> legacy registry
354
+ *
355
+ * @param name - The provider name (case-insensitive)
356
+ * @param config - The provider configuration
357
+ * @returns The provider implementation
358
+ * @throws Error when provider is not found
359
+ */
360
+ getProvider(name, config) {
361
+ if (config.provider) {
362
+ return config.provider;
363
+ }
364
+ const lowerName = name.toLowerCase();
365
+ const defaultProvider = _Lixa.DEFAULT_PROVIDERS.get(lowerName);
366
+ if (defaultProvider) {
367
+ return defaultProvider;
368
+ }
369
+ const legacyProvider = _Lixa.CONFIGURED_PROVIDERS.get(lowerName);
370
+ if (legacyProvider) {
371
+ return legacyProvider;
372
+ }
373
+ throw new Error(
374
+ `Provider '${name}' not found. Ensure the provider is included in the configuration with a 'provider' field, or registered using Lixa.registerProvider().`
375
+ );
376
+ }
377
+ /**
378
+ * Registers custom OAuth providers for use with Lixa.
379
+ *
380
+ * @deprecated This method is maintained for backward compatibility.
381
+ * The recommended approach is to pass providers inline in the configuration:
382
+ * ```typescript
383
+ * const lixa = new Lixa({
384
+ * providers: {
385
+ * custom: {
386
+ * provider: new CustomProvider(),
387
+ * clientId: '...',
388
+ * // ...
389
+ * }
390
+ * }
391
+ * });
392
+ * ```
393
+ *
394
+ * @param providerMap - A map of provider names to IProvider implementations
395
+ *
396
+ * @example
397
+ * Legacy usage (still supported):
398
+ * ```typescript
399
+ * class CustomProvider implements IProvider {
400
+ * authorizationEndpoint = 'https://custom.com/oauth/authorize';
401
+ * tokenEndpoint = 'https://custom.com/oauth/token';
402
+ * userInfoEndpoint = 'https://custom.com/api/user';
403
+ * }
404
+ *
405
+ * Lixa.registerProvider({ custom: new CustomProvider() });
406
+ * ```
407
+ */
408
+ static registerProvider(providerMap) {
409
+ Object.entries(providerMap).forEach(([key, providerImpl]) => {
410
+ _Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);
411
+ });
412
+ }
413
+ /**
414
+ * Gets the list of registered provider names.
415
+ *
416
+ * @returns Array of registered provider names
417
+ */
418
+ static getRegisteredProviders() {
419
+ return Array.from(_Lixa.CONFIGURED_PROVIDERS.keys());
420
+ }
421
+ /**
422
+ * Creates a type-safe configuration.
423
+ *
424
+ * @deprecated This method is maintained for backward compatibility.
425
+ * You can now pass configuration directly to the Lixa constructor without this helper.
426
+ *
427
+ * @param config - Configuration object with provider settings
428
+ * @returns The same configuration object with type safety
429
+ *
430
+ * @example
431
+ * New approach (recommended):
432
+ * ```typescript
433
+ * const lixa = new Lixa({
434
+ * providers: {
435
+ * google: {
436
+ * provider: new GoogleProvider(),
437
+ * clientId: '...',
438
+ * // ...
439
+ * }
440
+ * }
441
+ * });
442
+ * ```
443
+ */
444
+ static createConfig(config) {
445
+ return config;
446
+ }
447
+ /**
448
+ * Generates a cryptographically secure random state parameter for OAuth flows.
449
+ *
450
+ * @returns A 32-character hexadecimal string
451
+ *
452
+ * @remarks
453
+ * The state parameter is used to prevent CSRF attacks in OAuth flows.
454
+ */
455
+ static generateRandomState() {
456
+ return (0, import_crypto2.randomBytes)(16).toString("hex");
457
+ }
458
+ /**
459
+ * Generates a cryptographically secure code verifier for PKCE flows.
460
+ *
461
+ * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)
462
+ *
463
+ * @remarks
464
+ * This method implements the code verifier generation as specified in RFC 7636 (PKCE).
465
+ *
466
+ * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that
467
+ * prevents authorization code interception attacks. It's especially important for
468
+ * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.
469
+ *
470
+ * **Generation methodology:**
471
+ * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()
472
+ * 2. Encode the bytes as a hexadecimal string (64 characters)
473
+ * 3. The verifier is stored securely and used later in the token exchange
474
+ *
475
+ * **RFC 7636 Requirements:**
476
+ * - Minimum length: 43 characters
477
+ * - Maximum length: 128 characters
478
+ * - Character set: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
479
+ * - This implementation produces 64 hex characters, meeting the requirements
480
+ *
481
+ * The code verifier is:
482
+ * - Generated when creating the authorization URL
483
+ * - Stored in state cache with the state parameter
484
+ * - Retrieved during callback handling
485
+ * - Sent to the token endpoint to prove the client's identity
486
+ *
487
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
488
+ * @see buildCodeChallenge for the corresponding challenge generation
489
+ *
490
+ * @internal
491
+ */
492
+ static generateCodeVerifier() {
493
+ return (0, import_crypto2.randomBytes)(32).toString("hex");
494
+ }
495
+ /**
496
+ * Generates a code challenge from a code verifier for PKCE flows.
497
+ *
498
+ * @param codeVerifier - The code verifier string (64 hex characters)
499
+ * @returns A base64url-encoded SHA-256 hash of the code verifier
500
+ *
501
+ * @remarks
502
+ * This method implements the code challenge generation as specified in RFC 7636 (PKCE)
503
+ * using the S256 (SHA-256) transformation method.
504
+ *
505
+ * **Challenge generation methodology:**
506
+ * 1. Hash the code verifier using SHA-256
507
+ * 2. Encode the hash as base64
508
+ * 3. Convert to base64url format (RFC 4648):
509
+ * - Replace '+' with '-'
510
+ * - Replace '/' with '_'
511
+ * - Remove trailing '=' padding
512
+ *
513
+ * **PKCE Flow:**
514
+ * 1. Client generates code_verifier (random string)
515
+ * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))
516
+ * 3. Client sends code_challenge to authorization endpoint
517
+ * 4. Authorization server stores the code_challenge
518
+ * 5. Client sends code_verifier to token endpoint
519
+ * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge
520
+ *
521
+ * **Security Benefits:**
522
+ * - Prevents authorization code interception attacks
523
+ * - Even if an attacker intercepts the authorization code, they cannot
524
+ * exchange it for tokens without the original code_verifier
525
+ * - The challenge is sent in the authorization request (public)
526
+ * - The verifier is sent in the token request (should be kept secret)
527
+ *
528
+ * **RFC 7636 Transformation Methods:**
529
+ * - plain: code_challenge = code_verifier (not recommended)
530
+ * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)
531
+ *
532
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
533
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}
534
+ * @see generateCodeVerifier for the verifier generation
535
+ *
536
+ * @internal
537
+ */
538
+ static buildCodeChallenge(codeVerifier) {
539
+ const hash = import_crypto3.default.createHash("sha256").update(codeVerifier).digest("base64");
540
+ return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
541
+ }
542
+ /**
543
+ * Generates the authorization URL for the specified provider.
544
+ *
545
+ * @param provider - The provider name (must be a configured provider key)
546
+ * @param state - The state parameter for CSRF protection
547
+ * @returns The complete authorization URL to redirect users to
548
+ *
549
+ * @throws Error when the provider is not configured
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * const state = Lixa.generateRandomState();
554
+ * const authUrl = lixa.getAuthUrl('google', state);
555
+ * res.redirect(authUrl);
556
+ * ```
557
+ */
558
+ async getAuthUrl(provider, state) {
559
+ const providerType = String(provider).toLowerCase();
560
+ this.log("INFO", "Auth", `Generating authorization URL for provider: ${providerType}`);
561
+ const providerConfig = this.findProviderByType(providerType);
562
+ if (!providerConfig) {
563
+ this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
564
+ throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
565
+ }
566
+ const providerImpl = this.getProvider(providerType, providerConfig);
567
+ let stateValue;
568
+ let codeVerifier;
569
+ if (this.stateHandler.generateState) {
570
+ this.log("INFO", "State", "Calling custom GenerateState");
571
+ const generated = await this.stateHandler.generateState(providerType);
572
+ stateValue = state || generated.state;
573
+ codeVerifier = generated.data.codeVerifier;
574
+ const storage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
575
+ await storage.saveState(stateValue, generated.data, 300);
576
+ } else {
577
+ this.log("INFO", "State", "Using default state generation");
578
+ stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
579
+ codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
580
+ const storage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
581
+ await storage.saveState(
582
+ stateValue,
583
+ {
584
+ createdAt: Date.now(),
585
+ provider: providerType,
586
+ codeVerifier
587
+ },
588
+ 300
589
+ // 5 minutes in seconds
590
+ );
591
+ }
592
+ const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
593
+ this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
594
+ const authNScopes = this.resolveAuthNScopes(providerType, providerConfig.scopes, providerImpl);
595
+ const params = new URLSearchParams({
596
+ client_id: providerConfig.clientId,
597
+ redirect_uri: providerConfig.redirectUri,
598
+ scope: authNScopes.join(" "),
599
+ state: stateValue,
600
+ response_type: "code",
601
+ code_challenge: codeChallenge,
602
+ code_challenge_method: "S256",
603
+ ...providerConfig.extraConfig
604
+ });
605
+ const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;
606
+ this.log("INFO", "Auth", `Authorization URL generated successfully`, {
607
+ provider: providerType,
608
+ endpoint: providerImpl.authorizationEndpoint
609
+ });
610
+ return authUrl;
611
+ }
612
+ /**
613
+ * Restricts primary authentication scopes strictly to AuthN identity scopes.
614
+ */
615
+ resolveAuthNScopes(providerType, configuredScopes, providerImpl) {
616
+ const defaultAuthScopes = {
617
+ google: ["openid", "email", "profile"],
618
+ github: ["read:user", "user:email"],
619
+ microsoft: ["openid", "email", "profile"]
620
+ };
621
+ const standardAuthNScopes = [
622
+ "openid",
623
+ "email",
624
+ "profile",
625
+ "read:user",
626
+ "user:email",
627
+ "read:email",
628
+ "user:profile",
629
+ "user"
630
+ ];
631
+ const allowedAuthNScopes = /* @__PURE__ */ new Set([
632
+ ...standardAuthNScopes,
633
+ ...providerImpl.authScopes || [],
634
+ ...defaultAuthScopes[providerType] || []
635
+ ]);
636
+ const validAuthNScopes = (configuredScopes || []).filter((scope) => allowedAuthNScopes.has(scope));
637
+ const nonAuthNScopes = (configuredScopes || []).filter((scope) => !allowedAuthNScopes.has(scope));
638
+ if (nonAuthNScopes.length > 0) {
639
+ this.log(
640
+ "WARN",
641
+ "Auth",
642
+ `Primary authentication is strictly limited to AuthN scopes. Excluded non-AuthN resource scopes: [${nonAuthNScopes.join(
643
+ ", "
644
+ )}]. Use lixa.getResourceAuthUrl() post-login to connect resource providers.`
645
+ );
646
+ }
647
+ if (validAuthNScopes.length > 0) {
648
+ return validAuthNScopes;
649
+ }
650
+ return providerImpl.authScopes || defaultAuthScopes[providerType] || ["openid", "email", "profile"];
651
+ }
652
+ /**
653
+ * Handles the OAuth callback and creates a user session.
654
+ *
655
+ * @param provider - The provider name (must be a configured provider key)
656
+ * @param code - The authorization code from the provider
657
+ * @param state - The state parameter for validation
658
+ * @returns A Promise that resolves to the session ID
659
+ *
660
+ * @throws Error when code or state is missing/invalid, or provider is not configured
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * const sessionId = await lixa.handleCallback({
665
+ * provider: 'google',
666
+ * code: req.query.code,
667
+ * state: req.query.state
668
+ * });
669
+ * ```
670
+ */
671
+ async handleCallback({
672
+ provider,
673
+ code,
674
+ state
675
+ }) {
676
+ const providerType = String(provider).toLowerCase();
677
+ this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
678
+ if (!code || code.trim() === "") {
679
+ this.log("ERROR", "Auth", "Invalid or missing authorization code in callback");
680
+ throw new Error("Invalid or missing code in callback");
681
+ }
682
+ if (!state || state.trim() === "") {
683
+ this.log("ERROR", "Auth", "Invalid or missing state in callback");
684
+ throw new Error("Invalid or missing state in callback");
685
+ }
686
+ this.log("INFO", "State", "Validating state parameter", { state });
687
+ const stateStorage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
688
+ const cachedState = await stateStorage.getState(state);
689
+ if (!cachedState) {
690
+ this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
691
+ throw new Error("Invalid or expired state");
692
+ }
693
+ this.log("INFO", "State", "State validated successfully, removing from cache");
694
+ await stateStorage.deleteState(state);
695
+ const codeVerifier = cachedState.codeVerifier;
696
+ const providerConfig = this.findProviderByType(providerType);
697
+ if (!providerConfig) {
698
+ this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
699
+ throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
700
+ }
701
+ const providerImpl = this.getProvider(providerType, providerConfig);
702
+ this.log("INFO", "Token", `Exchanging authorization code for tokens`, { provider: providerType });
703
+ const tokens = await this.exchangeCodeForToken(
704
+ code,
705
+ providerConfig,
706
+ providerImpl,
707
+ codeVerifier
708
+ );
709
+ this.log("INFO", "Token", "Token exchange successful");
710
+ this.log("INFO", "Session", "Generating user session");
711
+ const providerMetadata = {
712
+ name: providerType,
713
+ endpoints: {
714
+ authorization: providerImpl.authorizationEndpoint,
715
+ token: providerImpl.tokenEndpoint,
716
+ userInfo: providerImpl.userInfoEndpoint
717
+ }
718
+ };
719
+ let extractedUserInfo;
720
+ try {
721
+ const { userInfo } = await extractUserInfo(tokens, providerMetadata);
722
+ extractedUserInfo = userInfo;
723
+ } catch {
724
+ }
725
+ const generateSession = this.sessionHandler.generateSession || _Lixa.LOCAL_SESSION_HANDLER.GenerateSession.bind(_Lixa.LOCAL_SESSION_HANDLER);
726
+ if (this.sessionHandler.generateSession) {
727
+ this.log("INFO", "Session", "Calling custom GenerateSession");
728
+ } else {
729
+ this.log("INFO", "Session", "Using default session generation");
730
+ }
731
+ const session = await generateSession(tokens, providerMetadata);
732
+ session.provider = session.provider || providerType;
733
+ if (extractedUserInfo?.email && !session.email) {
734
+ session.email = extractedUserInfo.email;
735
+ }
736
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
737
+ const linkingConfig = this.config.accountLinking;
738
+ const mode = String(linkingConfig?.mode || "");
739
+ const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
740
+ const email = extractedUserInfo?.email;
741
+ const isVerified = extractedUserInfo?.email_verified !== false;
742
+ const requireVerified = linkingConfig?.requireVerifiedEmail ?? true;
743
+ const canLink = isLinkByEmail && email && (!requireVerified || isVerified);
744
+ if (canLink && sessionStorage.getSessionByEmail) {
745
+ const existingRecord = await sessionStorage.getSessionByEmail(email);
746
+ if (existingRecord) {
747
+ this.log("INFO", "AccountLinking", `Linking provider '${providerType}' to existing session for email '${email}'`);
748
+ const { sessionId: existingSessionId, session: existingSession } = existingRecord;
749
+ existingSession.accounts = existingSession.accounts || {};
750
+ existingSession.accounts[providerType] = {
751
+ provider: providerType,
752
+ providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
753
+ email,
754
+ accessToken: tokens.access_token,
755
+ raw: tokens,
756
+ linkedAt: Date.now()
757
+ };
758
+ existingSession.provider = providerType;
759
+ existingSession.token = tokens.access_token;
760
+ existingSession.raw = tokens;
761
+ await sessionStorage.saveSession(existingSessionId, existingSession, 86400);
762
+ return existingSessionId;
763
+ }
764
+ }
765
+ session.accounts = session.accounts || {};
766
+ session.accounts[providerType] = {
767
+ provider: providerType,
768
+ providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
769
+ email: extractedUserInfo?.email,
770
+ accessToken: tokens.access_token,
771
+ raw: tokens,
772
+ linkedAt: Date.now()
773
+ };
774
+ const sessionId = (0, import_crypto2.randomBytes)(32).toString("hex");
775
+ session.id = sessionId;
776
+ this.log("INFO", "Session", "Storing session", { sessionId });
777
+ await sessionStorage.saveSession(sessionId, session, 86400);
778
+ this.log("INFO", "Session", "Session created successfully", { sessionId });
779
+ return sessionId;
780
+ }
781
+ /**
782
+ * Explicitly link a new OAuth provider account to an active session.
783
+ *
784
+ * @param params - Object containing sessionId, provider, code, and optional state
785
+ * @returns The active session ID with the newly linked provider
786
+ */
787
+ async linkAccount(params) {
788
+ const { sessionId, provider, code, state } = params;
789
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
790
+ const existingSession = await sessionStorage.getSession(sessionId);
791
+ if (!existingSession) {
792
+ throw new Error("Invalid session ID. User must be authenticated to link an account.");
793
+ }
794
+ const providerType = String(provider).toLowerCase();
795
+ const providerConfig = this.findProviderByType(providerType);
796
+ if (!providerConfig) {
797
+ throw new Error(`Provider '${String(provider)}' is not configured`);
798
+ }
799
+ const providerImpl = this.getProvider(providerType, providerConfig);
800
+ let codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
801
+ if (state) {
802
+ const stateStorage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
803
+ const cachedState = await stateStorage.getState(state);
804
+ if (cachedState) {
805
+ codeVerifier = cachedState.codeVerifier;
806
+ await stateStorage.deleteState(state);
807
+ }
808
+ }
809
+ const tokens = await this.exchangeCodeForToken(providerType, providerConfig, providerImpl, codeVerifier);
810
+ const providerMetadata = {
811
+ name: providerType,
812
+ endpoints: {
813
+ authorization: providerImpl.authorizationEndpoint,
814
+ token: providerImpl.tokenEndpoint,
815
+ userInfo: providerImpl.userInfoEndpoint
816
+ }
817
+ };
818
+ let extractedUserInfo;
819
+ try {
820
+ const { userInfo } = await extractUserInfo(tokens, providerMetadata);
821
+ extractedUserInfo = userInfo;
822
+ } catch {
823
+ }
824
+ existingSession.accounts = existingSession.accounts || {};
825
+ existingSession.accounts[providerType] = {
826
+ provider: providerType,
827
+ providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
828
+ email: extractedUserInfo?.email,
829
+ accessToken: tokens.access_token,
830
+ raw: tokens,
831
+ linkedAt: Date.now()
832
+ };
833
+ if (extractedUserInfo?.email && !existingSession.email) {
834
+ existingSession.email = extractedUserInfo.email;
835
+ }
836
+ await sessionStorage.saveSession(sessionId, existingSession, 86400);
837
+ return sessionId;
838
+ }
839
+ /**
840
+ * Unlinks an OAuth provider account from an active session.
841
+ *
842
+ * @param sessionId - Active session ID
843
+ * @param providerToUnlink - Provider name to unlink (e.g. 'github')
844
+ * @returns Promise resolving to true on successful unlink
845
+ */
846
+ async unlinkAccount(sessionId, providerToUnlink) {
847
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
848
+ const session = await sessionStorage.getSession(sessionId);
849
+ if (!session || !session.accounts) {
850
+ throw new Error("Session not found or has no linked accounts.");
851
+ }
852
+ const linkedProviders = Object.keys(session.accounts);
853
+ if (linkedProviders.length <= 1) {
854
+ throw new Error("Cannot unlink the only authentication provider for this account.");
855
+ }
856
+ delete session.accounts[providerToUnlink.toLowerCase()];
857
+ await sessionStorage.saveSession(sessionId, session, 86400);
858
+ return true;
859
+ }
860
+ /**
861
+ * Generates an authorization URL for connecting a resource provider (AuthZ) post-login.
862
+ *
863
+ * @remarks
864
+ * Resource authorization is kept strictly separate from primary authentication (AuthN).
865
+ * Call this method after a user is authenticated to request permissions for external API access
866
+ * (e.g. GitHub repositories, Google Drive, Slack, etc.).
867
+ *
868
+ * @param params - Object containing sessionId, provider, requested resource scopes, and optional state
869
+ * @returns The authorization URL for resource consent
870
+ */
871
+ async getResourceAuthUrl(params) {
872
+ const { sessionId, provider, scopes, state } = params;
873
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
874
+ const activeSession = await sessionStorage.getSession(sessionId);
875
+ if (!activeSession) {
876
+ throw new Error("Authentication required. Active session must exist to connect resource providers.");
877
+ }
878
+ const providerType = String(provider).toLowerCase();
879
+ const providerConfig = this.findProviderByType(providerType);
880
+ if (!providerConfig) {
881
+ throw new Error(`Provider '${String(provider)}' is not configured`);
882
+ }
883
+ const providerImpl = this.getProvider(providerType, providerConfig);
884
+ const stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
885
+ const codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
886
+ const storage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
887
+ await storage.saveState(
888
+ stateValue,
889
+ {
890
+ createdAt: Date.now(),
891
+ provider: providerType,
892
+ codeVerifier
893
+ },
894
+ 300
895
+ );
896
+ const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
897
+ const searchParams = new URLSearchParams({
898
+ client_id: providerConfig.clientId,
899
+ redirect_uri: providerConfig.redirectUri,
900
+ scope: scopes.join(" "),
901
+ state: stateValue,
902
+ response_type: "code",
903
+ code_challenge: codeChallenge,
904
+ code_challenge_method: "S256",
905
+ ...providerConfig.extraConfig
906
+ });
907
+ return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
908
+ }
909
+ /**
910
+ * Handles the OAuth callback for a connected resource provider and stores resource tokens on the session.
911
+ *
912
+ * @param params - Object containing sessionId, provider, code, state, and requested scopes
913
+ * @returns Updated Session containing stored resource tokens under session.resources[provider]
914
+ */
915
+ async handleResourceCallback(params) {
916
+ const { sessionId, provider, code, state, scopes } = params;
917
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
918
+ const activeSession = await sessionStorage.getSession(sessionId);
919
+ if (!activeSession) {
920
+ throw new Error("Authentication required. Active session not found for resource connection.");
921
+ }
922
+ const providerType = String(provider).toLowerCase();
923
+ const providerConfig = this.findProviderByType(providerType);
924
+ if (!providerConfig) {
925
+ throw new Error(`Provider '${String(provider)}' is not configured`);
926
+ }
927
+ const providerImpl = this.getProvider(providerType, providerConfig);
928
+ let codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
929
+ if (state) {
930
+ const stateStorage = this.stateHandler.stateStorage || _Lixa.LOCAL_STATE_HANDLER.stateStorage;
931
+ const cachedState = await stateStorage.getState(state);
932
+ if (cachedState) {
933
+ codeVerifier = cachedState.codeVerifier;
934
+ await stateStorage.deleteState(state);
935
+ }
936
+ }
937
+ const tokens = await this.exchangeCodeForToken(providerType, providerConfig, providerImpl, codeVerifier);
938
+ activeSession.resources = activeSession.resources || {};
939
+ activeSession.resources[providerType] = {
940
+ provider: providerType,
941
+ accessToken: tokens.access_token,
942
+ refreshToken: tokens.refresh_token,
943
+ scopes: scopes || (tokens.scope ? tokens.scope.split(" ") : []),
944
+ raw: tokens,
945
+ connectedAt: Date.now()
946
+ };
947
+ await sessionStorage.saveSession(sessionId, activeSession, 86400);
948
+ return activeSession;
949
+ }
950
+ /**
951
+ * Retrieves a connected resource provider token for an active session.
952
+ *
953
+ * @param sessionId - Active session ID
954
+ * @param provider - Provider identifier (e.g. 'github')
955
+ */
956
+ async getConnectedResource(sessionId, provider) {
957
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
958
+ const activeSession = await sessionStorage.getSession(sessionId);
959
+ if (!activeSession || !activeSession.resources) return null;
960
+ return activeSession.resources[provider.toLowerCase()] || null;
961
+ }
962
+ /**
963
+ * Disconnects a resource provider from an active session.
964
+ *
965
+ * @param sessionId - Active session ID
966
+ * @param provider - Provider identifier to disconnect
967
+ */
968
+ async disconnectResource(sessionId, provider) {
969
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
970
+ const activeSession = await sessionStorage.getSession(sessionId);
971
+ if (!activeSession || !activeSession.resources) return false;
972
+ delete activeSession.resources[provider.toLowerCase()];
973
+ await sessionStorage.saveSession(sessionId, activeSession, 86400);
974
+ return true;
975
+ }
976
+ async fetchSessionInfo(sessionId) {
977
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
978
+ return await sessionStorage.getSession(sessionId);
979
+ }
980
+ async exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier) {
981
+ const body = {
982
+ client_id: providerConfig.clientId,
983
+ client_secret: providerConfig.clientSecret,
984
+ code,
985
+ redirect_uri: providerConfig.redirectUri,
986
+ grant_type: "authorization_code"
987
+ };
988
+ if (codeVerifier) {
989
+ body.code_verifier = codeVerifier;
990
+ }
991
+ const params = new URLSearchParams(body);
992
+ this.log("INFO", "Token", "Sending token exchange request", {
993
+ endpoint: providerImpl.tokenEndpoint
994
+ });
995
+ const response = await fetch(providerImpl.tokenEndpoint, {
996
+ method: "POST",
997
+ headers: {
998
+ "Content-Type": "application/x-www-form-urlencoded",
999
+ Accept: "application/json"
1000
+ },
1001
+ body: params.toString()
1002
+ });
1003
+ if (!response.ok) {
1004
+ const errorBody = await response.text();
1005
+ this.log("ERROR", "Token", "Token exchange failed", {
1006
+ status: response.status,
1007
+ statusText: response.statusText,
1008
+ error: errorBody
1009
+ });
1010
+ throw new Error(
1011
+ `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`
1012
+ );
1013
+ }
1014
+ this.log("INFO", "Token", "Token exchange response received successfully");
1015
+ return response.json();
1016
+ }
1017
+ findProviderByType(providerType) {
1018
+ for (const [key, value] of Object.entries(this.config.providers)) {
1019
+ if (key.toLowerCase() === providerType.toLowerCase()) {
1020
+ return value;
1021
+ }
1022
+ }
1023
+ return void 0;
1024
+ }
1025
+ };
1026
+ // Annotate the CommonJS export names for ESM import in node:
1027
+ 0 && (module.exports = {
1028
+ AccountLinkingStrategy,
1029
+ Lixa,
1030
+ decodeIdToken,
1031
+ determineProviderFromIssuer,
1032
+ extractUserInfo,
1033
+ fetchUserInfo
1034
+ });
1035
+ //# sourceMappingURL=index.cjs.map