@vunexa/lixa 0.0.1-alpha.19 → 0.0.1-alpha.21

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/dist/index.d.ts CHANGED
@@ -4,10 +4,18 @@
4
4
  * @remarks
5
5
  * This package simplifies multi-provider authentication flows (e.g., Google, GitHub), supports extensible session management, and enables custom provider registration.
6
6
  *
7
+ * Key features:
8
+ * - OAuth 2.0 authorization code flow with PKCE (RFC 6749, RFC 7636)
9
+ * - OpenID Connect support
10
+ * - Built-in providers available in \@vunexa/lixa-providers
11
+ * - Custom provider support via IProvider interface
12
+ * - Extensible session management via SessionStrategy
13
+ * - Pluggable state and session storage via StateDao and SessionDao
14
+ * - TypeScript-first with comprehensive type safety
15
+ *
7
16
  * @packageDocumentation
8
17
  */
9
18
  export { Lixa } from "./lixa";
10
- export { type ProviderConfig, type LixaConfig, type SafeLixaConfig, } from "./types";
11
- export { type Session, type SessionStrategy, DefaultSessionStrategy } from "./models/session";
12
- export { type IProvider } from "./providers";
19
+ export { type ProviderConfig, type LixaConfig, type SafeLixaConfig, type IProvider, type SessionStrategy, type Session, type StateDao, type SessionDao, type StateData, } from "./types";
20
+ export { DefaultSessionStrategy } from "./models/session";
13
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -63,7 +63,9 @@ var DefaultSessionStrategy = class {
63
63
 
64
64
  // src/lixa.ts
65
65
  var Lixa = class _Lixa {
66
+ static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
66
67
  static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
68
+ // Legacy registry for backward compatibility
67
69
  static LOCAL_STATE_CACHE = new LocalStateCache();
68
70
  static LOCAL_SESSION_CACHE = new LocalSessionCache();
69
71
  static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();
@@ -74,39 +76,122 @@ var Lixa = class _Lixa {
74
76
  debug;
75
77
  /**
76
78
  * Creates a new Lixa instance with the provided configuration.
77
- *
79
+ *
80
+ * @remarks
81
+ * Providers can be passed inline in the configuration using the `provider` field.
82
+ * Provider resolution priority: inline custom provider > default providers > legacy registry.
83
+ *
78
84
  * @param config - The configuration object containing provider settings and optional session strategy
85
+ *
86
+ * @throws Error when provider configuration is missing required fields
87
+ * @throws Error when provider implementation is missing required properties
88
+ * @throws Error when provider is not available and no inline implementation is provided
79
89
  */
80
90
  constructor(config) {
81
- const configuredProviders = Object.keys(config.providers);
82
- const registeredProviders = _Lixa.getRegisteredProviders();
83
- for (const provider of configuredProviders) {
84
- if (!registeredProviders.includes(provider.toLowerCase())) {
85
- throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);
86
- }
87
- }
88
91
  this.config = config;
89
92
  this.stateDao = config.stateDao || _Lixa.LOCAL_STATE_CACHE;
90
93
  this.sesionDao = config.sessionDao || _Lixa.LOCAL_SESSION_CACHE;
91
94
  this.sessionStrategy = config.sessionStrategy || _Lixa.DEFAULT_SESSION_STRATEGY;
92
95
  this.debug = config.debug || false;
96
+ this.log("INFO", "Init", "Initializing Lixa instance", {
97
+ providers: Object.keys(config.providers),
98
+ debug: this.debug
99
+ });
100
+ for (const [providerName, providerConfig] of Object.entries(config.providers)) {
101
+ const name = providerName.toLowerCase();
102
+ const typedConfig = providerConfig;
103
+ this.validateProviderConfig(providerName, typedConfig);
104
+ if (typedConfig.provider) {
105
+ this.validateProviderImplementation(providerName, typedConfig.provider);
106
+ this.log("INFO", "Init", `Registered inline provider: ${providerName}`);
107
+ } else {
108
+ if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
109
+ this.log("ERROR", "Init", `Provider '${providerName}' not available`);
110
+ throw new Error(
111
+ `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: '...', ... }`
112
+ );
113
+ }
114
+ this.log("INFO", "Init", `Using registered provider: ${providerName}`);
115
+ }
116
+ }
117
+ this.log("INFO", "Init", "Lixa instance initialized successfully");
93
118
  }
94
- log(...args) {
95
- if (this.debug) {
96
- console.log("[Lixa]", ...args);
119
+ /**
120
+ * Validates that a provider configuration has all required credentials.
121
+ *
122
+ * @param name - The provider name
123
+ * @param config - The provider configuration
124
+ * @throws Error when required fields are missing or invalid
125
+ */
126
+ validateProviderConfig(name, config) {
127
+ const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
128
+ const missingFields = requiredFields.filter((field) => {
129
+ const value = config[field];
130
+ return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
131
+ });
132
+ if (missingFields.length > 0) {
133
+ throw new Error(
134
+ `Provider '${name}' configuration is missing required fields: ${missingFields.join(", ")}`
135
+ );
136
+ }
137
+ if (!Array.isArray(config.scopes)) {
138
+ throw new Error(
139
+ `Provider '${name}' configuration error: 'scopes' must be an array of strings`
140
+ );
141
+ }
142
+ if (config.scopes.length === 0) {
143
+ throw new Error(
144
+ `Provider '${name}' configuration error: 'scopes' array cannot be empty`
145
+ );
97
146
  }
98
147
  }
99
- logError(...args) {
100
- if (this.debug) {
101
- console.error("[Lixa]", ...args);
148
+ /**
149
+ * Validates that a provider implementation has all required properties.
150
+ *
151
+ * @param name - The provider name
152
+ * @param provider - The provider implementation
153
+ * @throws Error when required properties are missing
154
+ */
155
+ validateProviderImplementation(name, provider) {
156
+ const requiredProps = ["authorizationEndpoint", "tokenEndpoint", "userInfoEndpoint"];
157
+ const missingProps = requiredProps.filter((prop) => {
158
+ const value = provider[prop];
159
+ return !value || typeof value !== "string" || value.trim() === "";
160
+ });
161
+ if (missingProps.length > 0) {
162
+ throw new Error(
163
+ `Provider '${name}' implementation is missing required properties: ${missingProps.join(", ")}. All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`
164
+ );
102
165
  }
103
166
  }
104
167
  /**
105
- * Checks if a provider is both registered and configured for this instance.
168
+ * Structured debug logging with standardized format.
169
+ *
170
+ * @param level - Log level (INFO, WARN, ERROR)
171
+ * @param context - Context of the log (Init, Auth, Token, Session, State)
172
+ * @param message - Log message
173
+ * @param data - Optional data to log
174
+ *
175
+ * @remarks
176
+ * Format: [Lixa] [timestamp] [level] [context] message
177
+ * Only logs when debug mode is enabled.
178
+ */
179
+ log(level, context, message, data) {
180
+ if (!this.debug) return;
181
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
182
+ const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
183
+ if (data !== void 0) {
184
+ console.log(`${prefix} ${message}`, data);
185
+ } else {
186
+ console.log(`${prefix} ${message}`);
187
+ }
188
+ }
189
+ /**
190
+ * Checks if a provider is configured for this instance.
106
191
  * This is a type guard that narrows the provider type for use with getAuthUrl.
107
192
  *
108
193
  * @param provider - The provider name to check (case-insensitive)
109
- * @returns True if the provider is registered and configured, false otherwise
194
+ * @returns True if the provider is configured, false otherwise
110
195
  *
111
196
  * @example
112
197
  * ```typescript
@@ -118,14 +203,55 @@ var Lixa = class _Lixa {
118
203
  */
119
204
  isProviderConfigured(provider) {
120
205
  const providerType = provider.toLowerCase();
121
- return _Lixa.CONFIGURED_PROVIDERS.has(providerType) && this.config.providers.hasOwnProperty(providerType);
206
+ return this.config.providers.hasOwnProperty(providerType);
207
+ }
208
+ /**
209
+ * Gets a provider implementation by name.
210
+ * Resolution priority: inline custom provider > default providers > legacy registry
211
+ *
212
+ * @param name - The provider name (case-insensitive)
213
+ * @param config - The provider configuration
214
+ * @returns The provider implementation
215
+ * @throws Error when provider is not found
216
+ */
217
+ getProvider(name, config) {
218
+ if (config.provider) {
219
+ return config.provider;
220
+ }
221
+ const lowerName = name.toLowerCase();
222
+ const defaultProvider = _Lixa.DEFAULT_PROVIDERS.get(lowerName);
223
+ if (defaultProvider) {
224
+ return defaultProvider;
225
+ }
226
+ const legacyProvider = _Lixa.CONFIGURED_PROVIDERS.get(lowerName);
227
+ if (legacyProvider) {
228
+ return legacyProvider;
229
+ }
230
+ throw new Error(
231
+ `Provider '${name}' not found. Ensure the provider is included in the configuration with a 'provider' field, or registered using Lixa.registerProvider().`
232
+ );
122
233
  }
123
234
  /**
124
235
  * Registers custom OAuth providers for use with Lixa.
236
+ *
237
+ * @deprecated This method is maintained for backward compatibility.
238
+ * The recommended approach is to pass providers inline in the configuration:
239
+ * ```typescript
240
+ * const lixa = new Lixa({
241
+ * providers: {
242
+ * custom: {
243
+ * provider: new CustomProvider(),
244
+ * clientId: '...',
245
+ * // ...
246
+ * }
247
+ * }
248
+ * });
249
+ * ```
125
250
  *
126
251
  * @param providerMap - A map of provider names to IProvider implementations
127
252
  *
128
253
  * @example
254
+ * Legacy usage (still supported):
129
255
  * ```typescript
130
256
  * class CustomProvider implements IProvider {
131
257
  * authorizationEndpoint = 'https://custom.com/oauth/authorize';
@@ -150,19 +276,29 @@ var Lixa = class _Lixa {
150
276
  return Array.from(_Lixa.CONFIGURED_PROVIDERS.keys());
151
277
  }
152
278
  /**
153
- * Creates a type-safe configuration that only allows registered providers.
279
+ * Creates a type-safe configuration.
280
+ *
281
+ * @deprecated This method is maintained for backward compatibility.
282
+ * You can now pass configuration directly to the Lixa constructor without this helper.
283
+ *
284
+ * @param config - Configuration object with provider settings
285
+ * @returns The same configuration object with type safety
154
286
  *
155
- * @param config - Configuration object with providers that must be registered
156
- * @returns The same configuration object, but with type safety for registered providers
287
+ * @example
288
+ * New approach (recommended):
289
+ * ```typescript
290
+ * const lixa = new Lixa({
291
+ * providers: {
292
+ * google: {
293
+ * provider: new GoogleProvider(),
294
+ * clientId: '...',
295
+ * // ...
296
+ * }
297
+ * }
298
+ * });
299
+ * ```
157
300
  */
158
301
  static createConfig(config) {
159
- const configuredProviders = Object.keys(config.providers);
160
- const registeredProviders = _Lixa.getRegisteredProviders();
161
- for (const provider of configuredProviders) {
162
- if (!registeredProviders.includes(provider.toLowerCase())) {
163
- throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);
164
- }
165
- }
166
302
  return config;
167
303
  }
168
304
  /**
@@ -179,14 +315,83 @@ var Lixa = class _Lixa {
179
315
  /**
180
316
  * Generates a cryptographically secure code verifier for PKCE flows.
181
317
  *
182
- * @returns A 64-character hexadecimal string
318
+ * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)
183
319
  *
184
320
  * @remarks
185
- * The code verifier is used in PKCE (Proof Key for Code Exchange) to enhance security.
321
+ * This method implements the code verifier generation as specified in RFC 7636 (PKCE).
322
+ *
323
+ * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that
324
+ * prevents authorization code interception attacks. It's especially important for
325
+ * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.
326
+ *
327
+ * **Generation methodology:**
328
+ * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()
329
+ * 2. Encode the bytes as a hexadecimal string (64 characters)
330
+ * 3. The verifier is stored securely and used later in the token exchange
331
+ *
332
+ * **RFC 7636 Requirements:**
333
+ * - Minimum length: 43 characters
334
+ * - Maximum length: 128 characters
335
+ * - Character set: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
336
+ * - This implementation produces 64 hex characters, meeting the requirements
337
+ *
338
+ * The code verifier is:
339
+ * - Generated when creating the authorization URL
340
+ * - Stored in state cache with the state parameter
341
+ * - Retrieved during callback handling
342
+ * - Sent to the token endpoint to prove the client's identity
343
+ *
344
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
345
+ * @see {@link buildCodeChallenge} for the corresponding challenge generation
346
+ *
347
+ * @internal
186
348
  */
187
349
  static generateCodeVerifier() {
188
350
  return randomBytes(32).toString("hex");
189
351
  }
352
+ /**
353
+ * Generates a code challenge from a code verifier for PKCE flows.
354
+ *
355
+ * @param codeVerifier - The code verifier string (64 hex characters)
356
+ * @returns A base64url-encoded SHA-256 hash of the code verifier
357
+ *
358
+ * @remarks
359
+ * This method implements the code challenge generation as specified in RFC 7636 (PKCE)
360
+ * using the S256 (SHA-256) transformation method.
361
+ *
362
+ * **Challenge generation methodology:**
363
+ * 1. Hash the code verifier using SHA-256
364
+ * 2. Encode the hash as base64
365
+ * 3. Convert to base64url format (RFC 4648):
366
+ * - Replace '+' with '-'
367
+ * - Replace '/' with '_'
368
+ * - Remove trailing '=' padding
369
+ *
370
+ * **PKCE Flow:**
371
+ * 1. Client generates code_verifier (random string)
372
+ * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))
373
+ * 3. Client sends code_challenge to authorization endpoint
374
+ * 4. Authorization server stores the code_challenge
375
+ * 5. Client sends code_verifier to token endpoint
376
+ * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge
377
+ *
378
+ * **Security Benefits:**
379
+ * - Prevents authorization code interception attacks
380
+ * - Even if an attacker intercepts the authorization code, they cannot
381
+ * exchange it for tokens without the original code_verifier
382
+ * - The challenge is sent in the authorization request (public)
383
+ * - The verifier is sent in the token request (should be kept secret)
384
+ *
385
+ * **RFC 7636 Transformation Methods:**
386
+ * - plain: code_challenge = code_verifier (not recommended)
387
+ * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)
388
+ *
389
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
390
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}
391
+ * @see {@link generateCodeVerifier} for the verifier generation
392
+ *
393
+ * @internal
394
+ */
190
395
  static buildCodeChallenge(codeVerifier) {
191
396
  const hash = crypto.createHash("sha256").update(codeVerifier).digest("base64");
192
397
  return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -209,13 +414,16 @@ var Lixa = class _Lixa {
209
414
  */
210
415
  getAuthUrl(provider, state) {
211
416
  const providerType = String(provider).toLowerCase();
417
+ this.log("INFO", "Auth", `Generating authorization URL for provider: ${providerType}`);
212
418
  const providerConfig = this.findProviderByType(providerType);
213
- const providerImpl = _Lixa.CONFIGURED_PROVIDERS.get(providerType);
214
- if (!providerConfig || !providerImpl) {
215
- throw new Error(`Provider ${providerType} not configured`);
419
+ if (!providerConfig) {
420
+ this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
421
+ throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
216
422
  }
423
+ const providerImpl = this.getProvider(providerType, providerConfig);
217
424
  const codeVerifier = _Lixa.generateCodeVerifier();
218
425
  const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
426
+ this.log("INFO", "State", `Saving state for provider: ${providerType}`, { state });
219
427
  this.stateDao.saveState(
220
428
  state,
221
429
  {
@@ -236,7 +444,12 @@ var Lixa = class _Lixa {
236
444
  code_challenge_method: "S256",
237
445
  ...providerConfig.extraConfig
238
446
  });
239
- return `${providerImpl.authorizationEndpoint}?${params.toString()}`;
447
+ const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;
448
+ this.log("INFO", "Auth", `Authorization URL generated successfully`, {
449
+ provider: providerType,
450
+ endpoint: providerImpl.authorizationEndpoint
451
+ });
452
+ return authUrl;
240
453
  }
241
454
  /**
242
455
  * Handles the OAuth callback and creates a user session.
@@ -262,33 +475,45 @@ var Lixa = class _Lixa {
262
475
  code,
263
476
  state
264
477
  }) {
478
+ const providerType = String(provider).toLowerCase();
479
+ this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
265
480
  if (!code || code.trim() === "") {
481
+ this.log("ERROR", "Auth", "Invalid or missing authorization code in callback");
266
482
  throw new Error("Invalid or missing code in callback");
267
483
  }
268
484
  if (!state || state.trim() === "") {
485
+ this.log("ERROR", "Auth", "Invalid or missing state in callback");
269
486
  throw new Error("Invalid or missing state in callback");
270
487
  }
488
+ this.log("INFO", "State", "Validating state parameter", { state });
271
489
  const cachedState = await this.stateDao.getState(state);
272
490
  if (!cachedState) {
491
+ this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
273
492
  throw new Error("Invalid or expired state");
274
493
  }
494
+ this.log("INFO", "State", "State validated successfully, removing from cache");
275
495
  await this.stateDao.deleteState(state);
276
496
  const codeVerifier = cachedState.codeVerifier;
277
- const providerType = String(provider).toLowerCase();
278
497
  const providerConfig = this.findProviderByType(providerType);
279
- const providerImpl = _Lixa.CONFIGURED_PROVIDERS.get(providerType);
280
- if (!providerConfig || !providerImpl) {
281
- throw new Error(`Provider ${String(provider)} not configured`);
498
+ if (!providerConfig) {
499
+ this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
500
+ throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
282
501
  }
502
+ const providerImpl = this.getProvider(providerType, providerConfig);
503
+ this.log("INFO", "Token", `Exchanging authorization code for tokens`, { provider: providerType });
283
504
  const tokens = await this.exchangeCodeForToken(
284
505
  code,
285
506
  providerConfig,
286
507
  providerImpl,
287
508
  codeVerifier
288
509
  );
510
+ this.log("INFO", "Token", "Token exchange successful");
511
+ this.log("INFO", "Session", "Creating user session");
289
512
  const session = await this.sessionStrategy.createSession(tokens);
290
513
  const sessionId = randomBytes(32).toString("hex");
514
+ this.log("INFO", "Session", "Storing session", { sessionId });
291
515
  await this.sesionDao.saveSession(sessionId, session, 86400);
516
+ this.log("INFO", "Session", "Session created successfully", { sessionId });
292
517
  return sessionId;
293
518
  }
294
519
  fetchSessionInfo(sessionId) {
@@ -306,8 +531,9 @@ var Lixa = class _Lixa {
306
531
  body.code_verifier = codeVerifier;
307
532
  }
308
533
  const params = new URLSearchParams(body);
309
- this.log("Token Exchange - Request body:", params.toString());
310
- this.log("Token Exchange - Token endpoint:", providerImpl.tokenEndpoint);
534
+ this.log("INFO", "Token", "Sending token exchange request", {
535
+ endpoint: providerImpl.tokenEndpoint
536
+ });
311
537
  const response = await fetch(providerImpl.tokenEndpoint, {
312
538
  method: "POST",
313
539
  headers: {
@@ -318,15 +544,25 @@ var Lixa = class _Lixa {
318
544
  });
319
545
  if (!response.ok) {
320
546
  const errorBody = await response.text();
321
- this.logError("Token Exchange - Error response:", errorBody);
547
+ this.log("ERROR", "Token", "Token exchange failed", {
548
+ status: response.status,
549
+ statusText: response.statusText,
550
+ error: errorBody
551
+ });
322
552
  throw new Error(
323
553
  `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`
324
554
  );
325
555
  }
556
+ this.log("INFO", "Token", "Token exchange response received successfully");
326
557
  return response.json();
327
558
  }
328
559
  findProviderByType(providerType) {
329
- return this.config.providers[providerType];
560
+ for (const [key, value] of Object.entries(this.config.providers)) {
561
+ if (key.toLowerCase() === providerType.toLowerCase()) {
562
+ return value;
563
+ }
564
+ }
565
+ return void 0;
330
566
  }
331
567
  };
332
568
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy } from \"./models/session\";\n\n/**\n * Global registry of registered provider names\n */\ntype RegisteredProviders = string;\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<any>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n *\n * @example\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa/providers';\n * \n * // Register providers before using them\n * Lixa.registerProvider({ google: new GoogleProvider() });\n * \n * const config = Lixa.createConfig({\n * providers: {\n * google: {\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * \n * const lixa = new Lixa(config);\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<any> = LixaConfig> {\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map();\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n *\n * @param config - The configuration object containing provider settings and optional session strategy\n */\n constructor(config: TConfig) {\n // Validate that all providers in config are registered\n const configuredProviders = Object.keys(config.providers);\n const registeredProviders = Lixa.getRegisteredProviders();\n \n for (const provider of configuredProviders) {\n if (!registeredProviders.includes(provider.toLowerCase())) {\n throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);\n }\n }\n \n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n }\n\n private log(...args: any[]) {\n if (this.debug) {\n console.log('[Lixa]', ...args);\n }\n }\n\n private logError(...args: any[]) {\n if (this.debug) {\n console.error('[Lixa]', ...args);\n }\n }\n\n /**\n * Checks if a provider is both registered and configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is registered and configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return Lixa.CONFIGURED_PROVIDERS.has(providerType) &&\n this.config.providers.hasOwnProperty(providerType);\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration that only allows registered providers.\n * \n * @param config - Configuration object with providers that must be registered\n * @returns The same configuration object, but with type safety for registered providers\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<keyof T & string> & { providers: T }\n ): LixaConfig<keyof T & string> {\n // Validate that all providers in config are registered\n const configuredProviders = Object.keys(config.providers);\n const registeredProviders = Lixa.getRegisteredProviders();\n \n for (const provider of configuredProviders) {\n if (!registeredProviders.includes(provider.toLowerCase())) {\n throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);\n }\n }\n \n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string\n *\n * @remarks\n * The code verifier is used in PKCE (Proof Key for Code Exchange) to enhance security.\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n const providerConfig = this.findProviderByType(providerType);\n const providerImpl = Lixa.CONFIGURED_PROVIDERS.get(providerType);\n\n if (!providerConfig || !providerImpl) {\n throw new Error(`Provider ${providerType} not configured`);\n }\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n return `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n if (!code || code.trim() === \"\") {\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n throw new Error(\"Invalid or expired state\");\n }\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerType = String(provider).toLowerCase();\n const providerConfig = this.findProviderByType(providerType);\n const providerImpl = Lixa.CONFIGURED_PROVIDERS.get(providerType);\n\n if (!providerConfig || !providerImpl) {\n throw new Error(`Provider ${String(provider)} not configured`);\n }\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n\n const session = await this.sessionStrategy.createSession(tokens);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n return sessionId;\n }\n\n public fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return this.sesionDao.getSession(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<any> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('Token Exchange - Request body:', params.toString());\n this.log('Token Exchange - Token endpoint:', providerImpl.tokenEndpoint);\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.logError('Token Exchange - Error response:', errorBody);\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n return this.config.providers[providerType];\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: any, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<any | null> {\n return this.cache.get(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession(state: string, data: any, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession(state: string): Promise<any | null> {\n return this.cache.get(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * Represents a user session after successful OAuth authentication.\n *\n * @public\n */\nexport interface Session {\n /** The session token (typically the access token) */\n token: string;\n /** Raw token data from the OAuth provider */\n raw: any;\n}\n\n/**\n * Strategy interface for custom session creation.\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n *\n * @param userInfo - The token data received from the OAuth provider\n * @returns A Promise that resolves to a Session object\n */\n createSession(userInfo: any): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: any): Promise<Session> {\n // Extract access token from various possible formats\n const accessToken = tokenData.access_token || \n tokenData.accessToken || \n tokenData.token ||\n tokenData;\n\n if (!accessToken || typeof accessToken !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: accessToken,\n raw: tokenData,\n };\n }\n}"],"mappings":";AAAA,SAAS,mBAAmB;;;ACA5B,OAAO,eAAe;AAGtB,IAAM,kBAAN,MAA0C;AAAA,EAChC;AAAA,EAER,YAAY,oBAA4B,KAAK;AAC3C,SAAK,QAAQ,IAAI,UAAU,EAAE,QAAQ,kBAAkB,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,UAAU,OAAe,MAAW,kBAAyC;AACjF,SAAK,MAAM,IAAI,OAAO,MAAM,gBAAgB;AAAA,EAC9C;AAAA,EAEA,MAAM,SAAS,OAAoC;AACjD,WAAO,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,YAAY,OAA8B;AAC9C,SAAK,MAAM,IAAI,KAAK;AAAA,EACtB;AACF;;;ADhBA,OAAO,YAAY;;;AELnB,OAAOA,gBAAe;AAGtB,IAAM,oBAAN,MAA8C;AAAA,EACpC;AAAA,EAER,YAAY,oBAA4B,KAAK;AAC3C,SAAK,QAAQ,IAAIA,WAAU,EAAE,QAAQ,kBAAkB,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,YAAY,OAAe,MAAW,kBAAyC;AACnF,SAAK,MAAM,IAAI,OAAO,MAAM,gBAAgB;AAAA,EAC9C;AAAA,EAEA,MAAM,WAAW,OAAoC;AACnD,WAAO,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,cAAc,OAA8B;AAChD,SAAK,MAAM,IAAI,KAAK;AAAA,EACtB;AACF;;;ACYO,IAAM,yBAAN,MAAwD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7D,MAAM,cAAc,WAAkC;AAEpD,UAAM,cAAc,UAAU,gBACX,UAAU,eACV,UAAU,SACV;AAEnB,QAAI,CAAC,eAAe,OAAO,gBAAgB,UAAU;AACnD,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,KAAK;AAAA,IACP;AAAA,EACF;AACF;;;AHRA,IAAM,OAAN,MAAM,MAAmD;AAAA,EACvD,OAAe,uBAA+C,oBAAI,IAAI;AAAA,EACtE,OAAe,oBAAoB,IAAI,gBAAgB;AAAA,EACvD,OAAe,sBAAsB,IAAI,kBAAkB;AAAA,EAC3D,OAAe,2BAA2B,IAAI,uBAAuB;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOR,YAAY,QAAiB;AAE3B,UAAM,sBAAsB,OAAO,KAAK,OAAO,SAAS;AACxD,UAAM,sBAAsB,MAAK,uBAAuB;AAExD,eAAW,YAAY,qBAAqB;AAC1C,UAAI,CAAC,oBAAoB,SAAS,SAAS,YAAY,CAAC,GAAG;AACzD,cAAM,IAAI,MAAM,aAAa,QAAQ,6EAA6E;AAAA,MACpH;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,WAAW,OAAO,YAAY,MAAK;AACxC,SAAK,YAAY,OAAO,cAAc,MAAK;AAC3C,SAAK,kBAAkB,OAAO,mBAAmB,MAAK;AACtD,SAAK,QAAQ,OAAO,SAAS;AAAA,EAC/B;AAAA,EAEQ,OAAO,MAAa;AAC1B,QAAI,KAAK,OAAO;AACd,cAAQ,IAAI,UAAU,GAAG,IAAI;AAAA,IAC/B;AAAA,EACF;AAAA,EAEQ,YAAY,MAAa;AAC/B,QAAI,KAAK,OAAO;AACd,cAAQ,MAAM,UAAU,GAAG,IAAI;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBO,qBAAuC,UAA6D;AACzG,UAAM,eAAe,SAAS,YAAY;AAC1C,WAAO,MAAK,qBAAqB,IAAI,YAAY,KAC/C,KAAK,OAAO,UAAU,eAAe,YAAY;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,OAAc,iBAAsD,aAAsB;AACxF,WAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,YAAY,MAAM;AAC3D,YAAK,qBAAqB,IAAI,IAAI,YAAY,GAAG,YAAY;AAAA,IAC/D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAc,yBAAmC;AAC/C,WAAO,MAAM,KAAK,MAAK,qBAAqB,KAAK,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAc,aACZ,QAC8B;AAE9B,UAAM,sBAAsB,OAAO,KAAK,OAAO,SAAS;AACxD,UAAM,sBAAsB,MAAK,uBAAuB;AAExD,eAAW,YAAY,qBAAqB;AAC1C,UAAI,CAAC,oBAAoB,SAAS,SAAS,YAAY,CAAC,GAAG;AACzD,cAAM,IAAI,MAAM,aAAa,QAAQ,6EAA6E;AAAA,MACpH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAc,sBAA8B;AAC1C,WAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAe,uBAA+B;AAC5C,WAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA,EACvC;AAAA,EAEA,OAAe,mBAAmB,cAA8B;AAC9D,UAAM,OAAO,OACV,WAAW,QAAQ,EACnB,OAAO,YAAY,EACnB,OAAO,QAAQ;AAGlB,WAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,WAAW,UAAmD,OAAuB;AAC1F,UAAM,eAAe,OAAO,QAAQ,EAAE,YAAY;AAClD,UAAM,iBAAiB,KAAK,mBAAmB,YAAY;AAC3D,UAAM,eAAe,MAAK,qBAAqB,IAAI,YAAY;AAE/D,QAAI,CAAC,kBAAkB,CAAC,cAAc;AACpC,YAAM,IAAI,MAAM,YAAY,YAAY,iBAAiB;AAAA,IAC3D;AAEA,UAAM,eAAe,MAAK,qBAAqB;AAC/C,UAAM,gBAAgB,MAAK,mBAAmB,YAAY;AAI1D,SAAK,SAAS;AAAA,MACZ;AAAA,MACA;AAAA,QACE,WAAW,KAAK,IAAI;AAAA,QACpB,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACA;AAAA;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,eAAe;AAAA,MAC1B,cAAc,eAAe;AAAA,MAC7B,OAAO,eAAe,OAAO,KAAK,GAAG;AAAA,MACrC;AAAA,MACA,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,uBAAuB;AAAA,MACvB,GAAG,eAAe;AAAA,IACpB,CAAC;AAED,WAAO,GAAG,aAAa,qBAAqB,IAAI,OAAO,SAAS,CAAC;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAa,eAAe;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIoB;AAClB,QAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAAI;AAC/B,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,QAAI,CAAC,SAAS,MAAM,KAAK,MAAM,IAAI;AACjC,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,cAAc,MAAM,KAAK,SAAS,SAAS,KAAK;AACtD,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,UAAM,KAAK,SAAS,YAAY,KAAK;AAGrC,UAAM,eAAe,YAAY;AAEjC,UAAM,eAAe,OAAO,QAAQ,EAAE,YAAY;AAClD,UAAM,iBAAiB,KAAK,mBAAmB,YAAY;AAC3D,UAAM,eAAe,MAAK,qBAAqB,IAAI,YAAY;AAE/D,QAAI,CAAC,kBAAkB,CAAC,cAAc;AACpC,YAAM,IAAI,MAAM,YAAY,OAAO,QAAQ,CAAC,iBAAiB;AAAA,IAC/D;AAGA,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,UAAM,UAAU,MAAM,KAAK,gBAAgB,cAAc,MAAM;AAG/D,UAAM,YAAY,YAAY,EAAE,EAAE,SAAS,KAAK;AAGhD,UAAM,KAAK,UAAU,YAAY,WAAW,SAAS,KAAK;AAE1D,WAAO;AAAA,EACT;AAAA,EAEO,iBAAiB,WAA4C;AAClE,WAAO,KAAK,UAAU,WAAW,SAAS;AAAA,EAC5C;AAAA,EAEA,MAAc,qBACZ,MACA,gBACA,cACA,cACc;AAEd,UAAM,OAA+B;AAAA,MACnC,WAAW,eAAe;AAAA,MAC1B,eAAe,eAAe;AAAA,MAC9B;AAAA,MACA,cAAc,eAAe;AAAA,MAC7B,YAAY;AAAA,IACd;AAEA,QAAI,cAAc;AAChB,WAAK,gBAAgB;AAAA,IACvB;AACA,UAAM,SAAS,IAAI,gBAAgB,IAAI;AAEvC,SAAK,IAAI,kCAAkC,OAAO,SAAS,CAAC;AAC5D,SAAK,IAAI,oCAAoC,aAAa,aAAa;AAEvE,UAAM,WAAW,MAAM,MAAM,aAAa,eAAe;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,OAAO,SAAS;AAAA,IACxB,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK;AACtC,WAAK,SAAS,oCAAoC,SAAS;AAC3D,YAAM,IAAI;AAAA,QACR,0BAA0B,SAAS,MAAM,IAAI,SAAS,UAAU,MAAM,SAAS;AAAA,MACjF;AAAA,IACF;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEQ,mBAAmB,cAAkD;AAC3E,WAAO,KAAK,OAAO,UAAU,YAAY;AAAA,EAC3C;AACF;","names":["NodeCache"]}
1
+ {"version":3,"sources":["../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy } from \"./models/session\";\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<any>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n * Providers can be passed inline in the configuration, eliminating the need for pre-registration.\n *\n * @example\n * Using built-in providers from @vunexa/lixa-providers:\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa-providers';\n * \n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * ```\n * \n * @example\n * Using custom inline providers:\n * ```typescript\n * import { Lixa, IProvider } from '@vunexa/lixa';\n * \n * const customProvider: IProvider = {\n * authorizationEndpoint: 'https://custom.com/oauth/authorize',\n * tokenEndpoint: 'https://custom.com/oauth/token',\n * userInfoEndpoint: 'https://custom.com/api/user'\n * };\n * \n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: customProvider,\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/custom/callback',\n * scopes: ['read:user']\n * }\n * }\n * });\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<any> = LixaConfig> {\n private static DEFAULT_PROVIDERS: Map<string, IProvider> = new Map();\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map(); // Legacy registry for backward compatibility\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n * \n * @remarks\n * Providers can be passed inline in the configuration using the `provider` field.\n * Provider resolution priority: inline custom provider > default providers > legacy registry.\n * \n * @param config - The configuration object containing provider settings and optional session strategy\n * \n * @throws Error when provider configuration is missing required fields\n * @throws Error when provider implementation is missing required properties\n * @throws Error when provider is not available and no inline implementation is provided\n */\n constructor(config: TConfig) {\n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n \n this.log('INFO', 'Init', 'Initializing Lixa instance', { \n providers: Object.keys(config.providers),\n debug: this.debug \n });\n \n // Validate and extract providers from configuration\n for (const [providerName, providerConfig] of Object.entries(config.providers)) {\n const name = providerName.toLowerCase();\n const typedConfig = providerConfig as ProviderConfig;\n \n // Validate provider configuration has required credentials\n this.validateProviderConfig(providerName, typedConfig);\n \n // If provider config includes a custom provider implementation, validate it\n if (typedConfig.provider) {\n this.validateProviderImplementation(providerName, typedConfig.provider);\n this.log('INFO', 'Init', `Registered inline provider: ${providerName}`);\n } else {\n // Check if it's available in default providers or legacy registry\n if (!Lixa.DEFAULT_PROVIDERS.has(name) && !Lixa.CONFIGURED_PROVIDERS.has(name)) {\n this.log('ERROR', 'Init', `Provider '${providerName}' not available`);\n throw new Error(\n `Provider '${providerName}' is not available. ` +\n `Either import it from '@vunexa/lixa-providers' and include it in the configuration, ` +\n `or provide a custom implementation using the 'provider' field: ` +\n `{ provider: new CustomProvider(), clientId: '...', ... }`\n );\n }\n this.log('INFO', 'Init', `Using registered provider: ${providerName}`);\n }\n }\n \n this.log('INFO', 'Init', 'Lixa instance initialized successfully');\n }\n \n /**\n * Validates that a provider configuration has all required credentials.\n * \n * @param name - The provider name\n * @param config - The provider configuration\n * @throws Error when required fields are missing or invalid\n */\n private validateProviderConfig(name: string, config: ProviderConfig): void {\n const requiredFields: (keyof ProviderConfig)[] = ['clientId', 'clientSecret', 'redirectUri', 'scopes'];\n const missingFields = requiredFields.filter(field => {\n const value = config[field];\n return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');\n });\n \n if (missingFields.length > 0) {\n throw new Error(\n `Provider '${name}' configuration is missing required fields: ${missingFields.join(', ')}`\n );\n }\n \n // Validate scopes is an array\n if (!Array.isArray(config.scopes)) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' must be an array of strings`\n );\n }\n \n if (config.scopes.length === 0) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' array cannot be empty`\n );\n }\n }\n \n /**\n * Validates that a provider implementation has all required properties.\n * \n * @param name - The provider name\n * @param provider - The provider implementation\n * @throws Error when required properties are missing\n */\n private validateProviderImplementation(name: string, provider: IProvider): void {\n const requiredProps: (keyof IProvider)[] = ['authorizationEndpoint', 'tokenEndpoint', 'userInfoEndpoint'];\n const missingProps = requiredProps.filter(prop => {\n const value = provider[prop];\n return !value || typeof value !== 'string' || value.trim() === '';\n });\n \n if (missingProps.length > 0) {\n throw new Error(\n `Provider '${name}' implementation is missing required properties: ${missingProps.join(', ')}. ` +\n `All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`\n );\n }\n }\n\n /**\n * Structured debug logging with standardized format.\n * \n * @param level - Log level (INFO, WARN, ERROR)\n * @param context - Context of the log (Init, Auth, Token, Session, State)\n * @param message - Log message\n * @param data - Optional data to log\n * \n * @remarks\n * Format: [Lixa] [timestamp] [level] [context] message\n * Only logs when debug mode is enabled.\n */\n private log(level: 'INFO' | 'WARN' | 'ERROR', context: 'Init' | 'Auth' | 'Token' | 'Session' | 'State', message: string, data?: any): void {\n if (!this.debug) return;\n \n const timestamp = new Date().toISOString();\n const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;\n \n if (data !== undefined) {\n console.log(`${prefix} ${message}`, data);\n } else {\n console.log(`${prefix} ${message}`);\n }\n }\n\n /**\n * Checks if a provider is configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return this.config.providers.hasOwnProperty(providerType);\n }\n \n /**\n * Gets a provider implementation by name.\n * Resolution priority: inline custom provider > default providers > legacy registry\n * \n * @param name - The provider name (case-insensitive)\n * @param config - The provider configuration\n * @returns The provider implementation\n * @throws Error when provider is not found\n */\n private getProvider(name: string, config: ProviderConfig): IProvider {\n // First check if provider is inline in config\n if (config.provider) {\n return config.provider;\n }\n \n // Then check default providers\n const lowerName = name.toLowerCase();\n const defaultProvider = Lixa.DEFAULT_PROVIDERS.get(lowerName);\n if (defaultProvider) {\n return defaultProvider;\n }\n \n // Finally check legacy registry for backward compatibility\n const legacyProvider = Lixa.CONFIGURED_PROVIDERS.get(lowerName);\n if (legacyProvider) {\n return legacyProvider;\n }\n \n throw new Error(\n `Provider '${name}' not found. ` +\n `Ensure the provider is included in the configuration with a 'provider' field, ` +\n `or registered using Lixa.registerProvider().`\n );\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n * \n * @deprecated This method is maintained for backward compatibility.\n * The recommended approach is to pass providers inline in the configuration:\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: new CustomProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * Legacy usage (still supported):\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration.\n * \n * @deprecated This method is maintained for backward compatibility.\n * You can now pass configuration directly to the Lixa constructor without this helper.\n * \n * @param config - Configuration object with provider settings\n * @returns The same configuration object with type safety\n * \n * @example\n * New approach (recommended):\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<T> & { providers: T }\n ): LixaConfig<T> {\n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)\n *\n * @remarks\n * This method implements the code verifier generation as specified in RFC 7636 (PKCE).\n * \n * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that\n * prevents authorization code interception attacks. It's especially important for\n * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.\n * \n * **Generation methodology:**\n * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()\n * 2. Encode the bytes as a hexadecimal string (64 characters)\n * 3. The verifier is stored securely and used later in the token exchange\n * \n * **RFC 7636 Requirements:**\n * - Minimum length: 43 characters\n * - Maximum length: 128 characters\n * - Character set: [A-Z] / [a-z] / [0-9] / \"-\" / \".\" / \"_\" / \"~\"\n * - This implementation produces 64 hex characters, meeting the requirements\n * \n * The code verifier is:\n * - Generated when creating the authorization URL\n * - Stored in state cache with the state parameter\n * - Retrieved during callback handling\n * - Sent to the token endpoint to prove the client's identity\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link buildCodeChallenge} for the corresponding challenge generation\n * \n * @internal\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n /**\n * Generates a code challenge from a code verifier for PKCE flows.\n *\n * @param codeVerifier - The code verifier string (64 hex characters)\n * @returns A base64url-encoded SHA-256 hash of the code verifier\n *\n * @remarks\n * This method implements the code challenge generation as specified in RFC 7636 (PKCE)\n * using the S256 (SHA-256) transformation method.\n * \n * **Challenge generation methodology:**\n * 1. Hash the code verifier using SHA-256\n * 2. Encode the hash as base64\n * 3. Convert to base64url format (RFC 4648):\n * - Replace '+' with '-'\n * - Replace '/' with '_'\n * - Remove trailing '=' padding\n * \n * **PKCE Flow:**\n * 1. Client generates code_verifier (random string)\n * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))\n * 3. Client sends code_challenge to authorization endpoint\n * 4. Authorization server stores the code_challenge\n * 5. Client sends code_verifier to token endpoint\n * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge\n * \n * **Security Benefits:**\n * - Prevents authorization code interception attacks\n * - Even if an attacker intercepts the authorization code, they cannot\n * exchange it for tokens without the original code_verifier\n * - The challenge is sent in the authorization request (public)\n * - The verifier is sent in the token request (should be kept secret)\n * \n * **RFC 7636 Transformation Methods:**\n * - plain: code_challenge = code_verifier (not recommended)\n * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}\n * @see {@link generateCodeVerifier} for the verifier generation\n * \n * @internal\n */\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url (RFC 4648 Section 5)\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n \n this.log('INFO', 'Auth', `Generating authorization URL for provider: ${providerType}`);\n \n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n this.log('INFO', 'State', `Saving state for provider: ${providerType}`, { state });\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n this.log('INFO', 'Auth', `Authorization URL generated successfully`, { \n provider: providerType,\n endpoint: providerImpl.authorizationEndpoint \n });\n\n return authUrl;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n const providerType = String(provider).toLowerCase();\n this.log('INFO', 'Auth', `Handling OAuth callback for provider: ${providerType}`);\n\n if (!code || code.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing authorization code in callback');\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing state in callback');\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n this.log('INFO', 'State', 'Validating state parameter', { state });\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n this.log('ERROR', 'State', 'State validation failed: state not found or expired', { state });\n throw new Error(\"Invalid or expired state\");\n }\n \n this.log('INFO', 'State', 'State validated successfully, removing from cache');\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n this.log('INFO', 'Token', `Exchanging authorization code for tokens`, { provider: providerType });\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n this.log('INFO', 'Token', 'Token exchange successful');\n\n this.log('INFO', 'Session', 'Creating user session');\n const session = await this.sessionStrategy.createSession(tokens);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n this.log('INFO', 'Session', 'Storing session', { sessionId });\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n this.log('INFO', 'Session', 'Session created successfully', { sessionId });\n\n return sessionId;\n }\n\n public fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return this.sesionDao.getSession(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<any> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('INFO', 'Token', 'Sending token exchange request', { \n endpoint: providerImpl.tokenEndpoint \n });\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.log('ERROR', 'Token', 'Token exchange failed', { \n status: response.status, \n statusText: response.statusText,\n error: errorBody \n });\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n this.log('INFO', 'Token', 'Token exchange response received successfully');\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n // Use Object.entries to safely iterate and find the provider\n for (const [key, value] of Object.entries(this.config.providers)) {\n if (key.toLowerCase() === providerType.toLowerCase()) {\n return value as ProviderConfig;\n }\n }\n return undefined;\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: any, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<any | null> {\n return this.cache.get(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession(state: string, data: any, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession(state: string): Promise<any | null> {\n return this.cache.get(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * Represents a user session after successful OAuth authentication.\n * \n * @remarks\n * The Session object is returned by SessionStrategy.createSession() and contains\n * the session identifier and any additional data needed for your application.\n * \n * The structure is intentionally flexible to support various session management\n * approaches (JWT tokens, session IDs, etc.).\n *\n * @public\n */\nexport interface Session {\n /** \n * The session token or identifier.\n * This could be an access token, a session ID, a JWT, or any other identifier\n * that your application uses to track authenticated users.\n */\n token: string;\n \n /** \n * Raw session data.\n * Contains the complete OAuth token response and any additional data\n * your SessionStrategy adds (user info, database IDs, etc.).\n * \n * Typical OAuth token data includes:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (if requested)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (if using OIDC)\n * - scope: Granted scopes\n */\n raw: any;\n}\n\n/**\n * Strategy interface for custom session creation.\n * \n * @remarks\n * Implement this interface to customize how OAuth tokens are converted into\n * application sessions. This is where you typically:\n * - Decode ID tokens (for OpenID Connect)\n * - Look up or create users in your database\n * - Generate session identifiers\n * - Store session data\n * - Add custom claims or metadata\n * \n * The default implementation (DefaultSessionStrategy) simply extracts the\n * access token and returns it as the session token.\n * \n * @example\n * Custom session strategy with database integration:\n * ```typescript\n * class DatabaseSessionStrategy implements SessionStrategy {\n * constructor(private db: Database) {}\n * \n * async createSession(tokenData: any): Promise<Session> {\n * // Decode ID token for OIDC providers\n * const idToken = tokenData.id_token;\n * const payload = decodeJwt(idToken);\n * \n * // Create or update user in database\n * const user = await this.db.users.upsert({\n * email: payload.email,\n * name: payload.name,\n * picture: payload.picture\n * });\n * \n * // Generate session ID\n * const sessionId = generateSecureId();\n * \n * // Store session with tokens\n * await this.db.sessions.create({\n * id: sessionId,\n * userId: user.id,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: new Date(Date.now() + tokenData.expires_in * 1000)\n * });\n * \n * return {\n * token: sessionId,\n * raw: {\n * userId: user.id,\n * email: user.email,\n * ...tokenData\n * }\n * };\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * \n * @param tokenData - The token data received from the OAuth provider's token endpoint\n * @returns A Promise that resolves to a Session object\n * \n * @remarks\n * This method is called after successfully exchanging the authorization code\n * for tokens. The tokenData parameter contains the raw response from the\n * provider's token endpoint.\n * \n * Common token data fields:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (optional)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (for OIDC providers)\n * - scope: Granted scopes\n * \n * @throws \\{Error\\} If session creation fails (e.g., database error, invalid token)\n * \n * @example\n * Simple implementation:\n * ```typescript\n * async createSession(tokenData: any): Promise<Session> {\n * return {\n * token: tokenData.access_token,\n * raw: tokenData\n * };\n * }\n * ```\n */\n createSession(tokenData: any): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: any): Promise<Session> {\n // Extract access token from various possible formats\n const accessToken = tokenData.access_token || \n tokenData.accessToken || \n tokenData.token ||\n tokenData;\n\n if (!accessToken || typeof accessToken !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: accessToken,\n raw: tokenData,\n };\n }\n}"],"mappings":"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","names":["NodeCache"]}