@vunexa/lixa 0.0.1-alpha.20 → 0.0.1-alpha.22

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 (40) hide show
  1. package/README.md +562 -79
  2. package/dist/dao/session-cache.d.ts +2 -2
  3. package/dist/dao/session-cache.d.ts.map +1 -1
  4. package/dist/dao/state-cache.d.ts +3 -3
  5. package/dist/dao/state-cache.d.ts.map +1 -1
  6. package/dist/dao/types.d.ts +246 -5
  7. package/dist/dao/types.d.ts.map +1 -1
  8. package/dist/export-types/index.d.ts +852 -49
  9. package/dist/index.cjs +282 -46
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +853 -46
  12. package/dist/index.d.ts +11 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +282 -46
  15. package/dist/index.js.map +1 -1
  16. package/dist/lixa.d.ts +187 -18
  17. package/dist/lixa.d.ts.map +1 -1
  18. package/dist/models/session.d.ts +163 -7
  19. package/dist/models/session.d.ts.map +1 -1
  20. package/dist/providers/IProvider.d.ts +121 -4
  21. package/dist/providers/IProvider.d.ts.map +1 -1
  22. package/dist/providers/index.d.ts +0 -2
  23. package/dist/providers/index.d.ts.map +1 -1
  24. package/dist/types.d.ts +131 -15
  25. package/dist/types.d.ts.map +1 -1
  26. package/package.json +2 -16
  27. package/dist/IProvider-2tDwRAnH.d.cts +0 -18
  28. package/dist/IProvider-2tDwRAnH.d.ts +0 -18
  29. package/dist/export-types/providers.d.ts +0 -61
  30. package/dist/providers/github.d.ts +0 -13
  31. package/dist/providers/github.d.ts.map +0 -1
  32. package/dist/providers/google.d.ts +0 -13
  33. package/dist/providers/google.d.ts.map +0 -1
  34. package/dist/providers-entry.cjs +0 -48
  35. package/dist/providers-entry.cjs.map +0 -1
  36. package/dist/providers-entry.d.cts +0 -25
  37. package/dist/providers-entry.d.ts +0 -22
  38. package/dist/providers-entry.d.ts.map +0 -1
  39. package/dist/providers-entry.js +0 -20
  40. package/dist/providers-entry.js.map +0 -1
@@ -4,13 +4,22 @@
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
 
10
19
  /**
11
20
  * Type representing the keys of configured providers
12
21
  */
13
- declare type ConfiguredProviderKey<T extends LixaConfig<any>> = keyof T['providers'];
22
+ declare type ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];
14
23
 
15
24
  /**
16
25
  * Default session strategy that works with any OAuth provider.
@@ -26,23 +35,140 @@ export declare class DefaultSessionStrategy implements SessionStrategy {
26
35
  * @param tokenData - The token data received from the OAuth provider
27
36
  * @returns A Promise that resolves to a Session object
28
37
  */
29
- createSession(tokenData: any): Promise<Session>;
38
+ createSession(tokenData: OAuthTokenResponse): Promise<Session>;
30
39
  }
31
40
 
32
41
  /**
33
- * Interface for OAuth provider implementations.
42
+ * Interface for OAuth 2.0 and OpenID Connect provider implementations.
34
43
  *
35
44
  * @remarks
36
45
  * Implement this interface to add support for custom OAuth providers.
46
+ * Each provider defines the three core endpoints required for the OAuth 2.0
47
+ * authorization code flow with PKCE.
48
+ *
49
+ * Built-in providers (Google, GitHub) are available in the \@vunexa/lixa-providers package.
50
+ *
51
+ * All implementations must comply with:
52
+ * - RFC 6749 (OAuth 2.0)
53
+ * - RFC 7636 (PKCE)
54
+ * - OpenID Connect Core 1.0 (for OIDC providers)
55
+ *
56
+ * @example
57
+ * Custom provider implementation:
58
+ * ```typescript
59
+ * import { IProvider } from '@vunexa/lixa';
60
+ *
61
+ * class CustomProvider implements IProvider {
62
+ * authorizationEndpoint = 'https://auth.example.com/oauth/authorize';
63
+ * tokenEndpoint = 'https://auth.example.com/oauth/token';
64
+ * userInfoEndpoint = 'https://api.example.com/user';
65
+ * }
66
+ *
67
+ * // Use in configuration
68
+ * const lixa = new Lixa({
69
+ * providers: {
70
+ * custom: {
71
+ * provider: new CustomProvider(),
72
+ * clientId: 'your-client-id',
73
+ * clientSecret: 'your-client-secret',
74
+ * redirectUri: 'https://app.com/callback',
75
+ * scopes: ['read:user']
76
+ * }
77
+ * }
78
+ * });
79
+ * ```
80
+ *
81
+ * @example
82
+ * Object literal provider:
83
+ * ```typescript
84
+ * const customProvider: IProvider = {
85
+ * authorizationEndpoint: 'https://auth.example.com/oauth/authorize',
86
+ * tokenEndpoint: 'https://auth.example.com/oauth/token',
87
+ * userInfoEndpoint: 'https://api.example.com/user'
88
+ * };
89
+ * ```
37
90
  *
38
91
  * @public
39
92
  */
40
93
  export declare interface IProvider {
41
- /** The OAuth authorization endpoint URL */
94
+ /**
95
+ * The OAuth 2.0 authorization endpoint URL.
96
+ *
97
+ * @remarks
98
+ * This is the URL where users are redirected to authenticate and authorize your application.
99
+ * The endpoint must support the OAuth 2.0 authorization code flow with PKCE.
100
+ *
101
+ * Standard query parameters sent to this endpoint:
102
+ * - client_id: Your application's client ID
103
+ * - redirect_uri: Where to redirect after authorization
104
+ * - response_type: Always "code" for authorization code flow
105
+ * - scope: Space-separated list of requested scopes
106
+ * - state: Random string for CSRF protection
107
+ * - code_challenge: PKCE code challenge (SHA-256 hash)
108
+ * - code_challenge_method: Always "S256" for SHA-256
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * authorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth'
113
+ * ```
114
+ */
42
115
  authorizationEndpoint: string;
43
- /** The OAuth token exchange endpoint URL */
116
+ /**
117
+ * The OAuth 2.0 token endpoint URL.
118
+ *
119
+ * @remarks
120
+ * This is the URL where authorization codes are exchanged for access tokens.
121
+ * The endpoint must support the OAuth 2.0 token exchange with PKCE.
122
+ *
123
+ * Standard parameters sent to this endpoint (POST request):
124
+ * - grant_type: Always "authorization_code"
125
+ * - code: The authorization code from the callback
126
+ * - redirect_uri: Must match the authorization request
127
+ * - client_id: Your application's client ID
128
+ * - client_secret: Your application's client secret
129
+ * - code_verifier: PKCE code verifier (original random string)
130
+ *
131
+ * Expected response:
132
+ * - access_token: OAuth access token
133
+ * - token_type: Token type (usually "Bearer")
134
+ * - expires_in: Token expiration time in seconds
135
+ * - refresh_token: Refresh token (optional)
136
+ * - id_token: OpenID Connect ID token (for OIDC providers)
137
+ * - scope: Granted scopes
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * tokenEndpoint = 'https://oauth2.googleapis.com/token'
142
+ * ```
143
+ */
44
144
  tokenEndpoint: string;
45
- /** The user information endpoint URL */
145
+ /**
146
+ * The user information endpoint URL.
147
+ *
148
+ * @remarks
149
+ * This is the URL where user profile information can be retrieved using the access token.
150
+ * For OpenID Connect providers, this is the UserInfo endpoint.
151
+ *
152
+ * The endpoint is called with the access token in the Authorization header:
153
+ * ```
154
+ * Authorization: Bearer <access_token>
155
+ * ```
156
+ *
157
+ * Common response fields:
158
+ * - sub: Subject identifier (user ID)
159
+ * - email: User's email address
160
+ * - name: User's full name
161
+ * - picture: User's profile picture URL
162
+ * - email_verified: Whether email is verified
163
+ *
164
+ * Note: This endpoint is not called automatically by Lixa. Your SessionStrategy
165
+ * can call it if needed to fetch user profile information.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * userInfoEndpoint = 'https://www.googleapis.com/oauth2/v2/userinfo'
170
+ * ```
171
+ */
46
172
  userInfoEndpoint: string;
47
173
  }
48
174
 
@@ -51,18 +177,18 @@ export declare interface IProvider {
51
177
  *
52
178
  * @remarks
53
179
  * Lixa simplifies multi-provider authentication flows and supports extensible session management.
180
+ * Providers can be passed inline in the configuration, eliminating the need for pre-registration.
54
181
  *
55
182
  * @example
183
+ * Using built-in providers from \@vunexa/lixa-providers:
56
184
  * ```typescript
57
185
  * import { Lixa } from '@vunexa/lixa';
58
- * import { GoogleProvider } from '@vunexa/lixa/providers';
59
- *
60
- * // Register providers before using them
61
- * Lixa.registerProvider({ google: new GoogleProvider() });
186
+ * import { GoogleProvider } from '@vunexa/lixa-providers';
62
187
  *
63
- * const config = Lixa.createConfig({
188
+ * const lixa = new Lixa({
64
189
  * providers: {
65
190
  * google: {
191
+ * provider: new GoogleProvider(),
66
192
  * clientId: 'your-client-id',
67
193
  * clientSecret: 'your-client-secret',
68
194
  * redirectUri: 'https://yourapp.com/auth/google/callback',
@@ -70,13 +196,36 @@ export declare interface IProvider {
70
196
  * }
71
197
  * }
72
198
  * });
199
+ * ```
200
+ *
201
+ * @example
202
+ * Using custom inline providers:
203
+ * ```typescript
204
+ * import { Lixa, IProvider } from '@vunexa/lixa';
205
+ *
206
+ * const customProvider: IProvider = {
207
+ * authorizationEndpoint: 'https://custom.com/oauth/authorize',
208
+ * tokenEndpoint: 'https://custom.com/oauth/token',
209
+ * userInfoEndpoint: 'https://custom.com/api/user'
210
+ * };
73
211
  *
74
- * const lixa = new Lixa(config);
212
+ * const lixa = new Lixa({
213
+ * providers: {
214
+ * custom: {
215
+ * provider: customProvider,
216
+ * clientId: 'your-client-id',
217
+ * clientSecret: 'your-client-secret',
218
+ * redirectUri: 'https://yourapp.com/auth/custom/callback',
219
+ * scopes: ['read:user']
220
+ * }
221
+ * }
222
+ * });
75
223
  * ```
76
224
  *
77
225
  * @public
78
226
  */
79
- export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
227
+ export declare class Lixa<TConfig extends LixaConfig<Record<string, ProviderConfig>> = LixaConfig> {
228
+ private static DEFAULT_PROVIDERS;
80
229
  private static CONFIGURED_PROVIDERS;
81
230
  private static LOCAL_STATE_CACHE;
82
231
  private static LOCAL_SESSION_CACHE;
@@ -89,17 +238,52 @@ export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
89
238
  /**
90
239
  * Creates a new Lixa instance with the provided configuration.
91
240
  *
241
+ * @remarks
242
+ * Providers can be passed inline in the configuration using the `provider` field.
243
+ * Provider resolution priority: inline custom provider \> default providers \> legacy registry.
244
+ *
92
245
  * @param config - The configuration object containing provider settings and optional session strategy
246
+ *
247
+ * @throws Error when provider configuration is missing required fields
248
+ * @throws Error when provider implementation is missing required properties
249
+ * @throws Error when provider is not available and no inline implementation is provided
93
250
  */
94
251
  constructor(config: TConfig);
252
+ /**
253
+ * Validates that a provider configuration has all required credentials.
254
+ *
255
+ * @param name - The provider name
256
+ * @param config - The provider configuration
257
+ * @throws Error when required fields are missing or invalid
258
+ */
259
+ private validateProviderConfig;
260
+ /**
261
+ * Validates that a provider implementation has all required properties.
262
+ *
263
+ * @param name - The provider name
264
+ * @param provider - The provider implementation
265
+ * @throws Error when required properties are missing
266
+ */
267
+ private validateProviderImplementation;
268
+ /**
269
+ * Structured debug logging with standardized format.
270
+ *
271
+ * @param level - Log level (INFO, WARN, ERROR)
272
+ * @param context - Context of the log (Init, Auth, Token, Session, State)
273
+ * @param message - Log message
274
+ * @param data - Optional data to log
275
+ *
276
+ * @remarks
277
+ * Format: [Lixa] [timestamp] [level] [context] message
278
+ * Only logs when debug mode is enabled.
279
+ */
95
280
  private log;
96
- private logError;
97
281
  /**
98
- * Checks if a provider is both registered and configured for this instance.
282
+ * Checks if a provider is configured for this instance.
99
283
  * This is a type guard that narrows the provider type for use with getAuthUrl.
100
284
  *
101
285
  * @param provider - The provider name to check (case-insensitive)
102
- * @returns True if the provider is registered and configured, false otherwise
286
+ * @returns True if the provider is configured, false otherwise
103
287
  *
104
288
  * @example
105
289
  * ```typescript
@@ -110,12 +294,37 @@ export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
110
294
  * ```
111
295
  */
112
296
  isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig>;
297
+ /**
298
+ * Gets a provider implementation by name.
299
+ * Resolution priority: inline custom provider \> default providers \> legacy registry
300
+ *
301
+ * @param name - The provider name (case-insensitive)
302
+ * @param config - The provider configuration
303
+ * @returns The provider implementation
304
+ * @throws Error when provider is not found
305
+ */
306
+ private getProvider;
113
307
  /**
114
308
  * Registers custom OAuth providers for use with Lixa.
115
309
  *
310
+ * @deprecated This method is maintained for backward compatibility.
311
+ * The recommended approach is to pass providers inline in the configuration:
312
+ * ```typescript
313
+ * const lixa = new Lixa({
314
+ * providers: {
315
+ * custom: {
316
+ * provider: new CustomProvider(),
317
+ * clientId: '...',
318
+ * // ...
319
+ * }
320
+ * }
321
+ * });
322
+ * ```
323
+ *
116
324
  * @param providerMap - A map of provider names to IProvider implementations
117
325
  *
118
326
  * @example
327
+ * Legacy usage (still supported):
119
328
  * ```typescript
120
329
  * class CustomProvider implements IProvider {
121
330
  * authorizationEndpoint = 'https://custom.com/oauth/authorize';
@@ -134,14 +343,31 @@ export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
134
343
  */
135
344
  static getRegisteredProviders(): string[];
136
345
  /**
137
- * Creates a type-safe configuration that only allows registered providers.
346
+ * Creates a type-safe configuration.
347
+ *
348
+ * @deprecated This method is maintained for backward compatibility.
349
+ * You can now pass configuration directly to the Lixa constructor without this helper.
138
350
  *
139
- * @param config - Configuration object with providers that must be registered
140
- * @returns The same configuration object, but with type safety for registered providers
351
+ * @param config - Configuration object with provider settings
352
+ * @returns The same configuration object with type safety
353
+ *
354
+ * @example
355
+ * New approach (recommended):
356
+ * ```typescript
357
+ * const lixa = new Lixa({
358
+ * providers: {
359
+ * google: {
360
+ * provider: new GoogleProvider(),
361
+ * clientId: '...',
362
+ * // ...
363
+ * }
364
+ * }
365
+ * });
366
+ * ```
141
367
  */
142
- static createConfig<T extends Record<string, ProviderConfig>>(config: LixaConfig<keyof T & string> & {
368
+ static createConfig<T extends Record<string, ProviderConfig>>(config: LixaConfig<T> & {
143
369
  providers: T;
144
- }): LixaConfig<keyof T & string>;
370
+ }): LixaConfig<T>;
145
371
  /**
146
372
  * Generates a cryptographically secure random state parameter for OAuth flows.
147
373
  *
@@ -154,12 +380,81 @@ export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
154
380
  /**
155
381
  * Generates a cryptographically secure code verifier for PKCE flows.
156
382
  *
157
- * @returns A 64-character hexadecimal string
383
+ * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)
158
384
  *
159
385
  * @remarks
160
- * The code verifier is used in PKCE (Proof Key for Code Exchange) to enhance security.
386
+ * This method implements the code verifier generation as specified in RFC 7636 (PKCE).
387
+ *
388
+ * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that
389
+ * prevents authorization code interception attacks. It's especially important for
390
+ * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.
391
+ *
392
+ * **Generation methodology:**
393
+ * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()
394
+ * 2. Encode the bytes as a hexadecimal string (64 characters)
395
+ * 3. The verifier is stored securely and used later in the token exchange
396
+ *
397
+ * **RFC 7636 Requirements:**
398
+ * - Minimum length: 43 characters
399
+ * - Maximum length: 128 characters
400
+ * - Character set: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
401
+ * - This implementation produces 64 hex characters, meeting the requirements
402
+ *
403
+ * The code verifier is:
404
+ * - Generated when creating the authorization URL
405
+ * - Stored in state cache with the state parameter
406
+ * - Retrieved during callback handling
407
+ * - Sent to the token endpoint to prove the client's identity
408
+ *
409
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
410
+ * @see buildCodeChallenge for the corresponding challenge generation
411
+ *
412
+ * @internal
161
413
  */
162
414
  private static generateCodeVerifier;
415
+ /**
416
+ * Generates a code challenge from a code verifier for PKCE flows.
417
+ *
418
+ * @param codeVerifier - The code verifier string (64 hex characters)
419
+ * @returns A base64url-encoded SHA-256 hash of the code verifier
420
+ *
421
+ * @remarks
422
+ * This method implements the code challenge generation as specified in RFC 7636 (PKCE)
423
+ * using the S256 (SHA-256) transformation method.
424
+ *
425
+ * **Challenge generation methodology:**
426
+ * 1. Hash the code verifier using SHA-256
427
+ * 2. Encode the hash as base64
428
+ * 3. Convert to base64url format (RFC 4648):
429
+ * - Replace '+' with '-'
430
+ * - Replace '/' with '_'
431
+ * - Remove trailing '=' padding
432
+ *
433
+ * **PKCE Flow:**
434
+ * 1. Client generates code_verifier (random string)
435
+ * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))
436
+ * 3. Client sends code_challenge to authorization endpoint
437
+ * 4. Authorization server stores the code_challenge
438
+ * 5. Client sends code_verifier to token endpoint
439
+ * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge
440
+ *
441
+ * **Security Benefits:**
442
+ * - Prevents authorization code interception attacks
443
+ * - Even if an attacker intercepts the authorization code, they cannot
444
+ * exchange it for tokens without the original code_verifier
445
+ * - The challenge is sent in the authorization request (public)
446
+ * - The verifier is sent in the token request (should be kept secret)
447
+ *
448
+ * **RFC 7636 Transformation Methods:**
449
+ * - plain: code_challenge = code_verifier (not recommended)
450
+ * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)
451
+ *
452
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
453
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}
454
+ * @see generateCodeVerifier for the verifier generation
455
+ *
456
+ * @internal
457
+ */
163
458
  private static buildCodeChallenge;
164
459
  /**
165
460
  * Generates the authorization URL for the specified provider.
@@ -209,29 +504,180 @@ export declare class Lixa<TConfig extends LixaConfig<any> = LixaConfig> {
209
504
 
210
505
  /**
211
506
  * Main configuration object for Lixa.
212
- * Only allows providers that have been registered through registerProvider.
507
+ * Provides type-safe provider name inference.
508
+ *
509
+ * @remarks
510
+ * The generic type parameter TProviders enables TypeScript to infer provider names
511
+ * from the configuration object, providing autocomplete and type checking for
512
+ * provider names in methods like getAuthUrl() and handleCallback().
513
+ *
514
+ * @typeParam TProviders - The provider configuration map type, defaults to a generic record
515
+ *
516
+ * @example
517
+ * Basic configuration with built-in providers:
518
+ * ```typescript
519
+ * import { Lixa } from '@vunexa/lixa';
520
+ * import { GoogleProvider } from '@vunexa/lixa-providers';
521
+ *
522
+ * const lixa = new Lixa({
523
+ * providers: {
524
+ * google: {
525
+ * provider: new GoogleProvider(),
526
+ * clientId: process.env.GOOGLE_CLIENT_ID!,
527
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
528
+ * redirectUri: 'https://app.com/auth/google/callback',
529
+ * scopes: ['openid', 'email', 'profile']
530
+ * }
531
+ * }
532
+ * });
533
+ * ```
534
+ *
535
+ * @example
536
+ * Configuration with custom session and state storage:
537
+ * ```typescript
538
+ * const lixa = new Lixa({
539
+ * providers: {
540
+ * google: {
541
+ * provider: new GoogleProvider(),
542
+ * clientId: process.env.GOOGLE_CLIENT_ID!,
543
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
544
+ * redirectUri: 'https://app.com/auth/google/callback',
545
+ * scopes: ['openid', 'email', 'profile']
546
+ * }
547
+ * },
548
+ * sessionStrategy: new CustomSessionStrategy(),
549
+ * stateDao: new RedisStateDao(),
550
+ * sessionDao: new DatabaseSessionDao(),
551
+ * debug: true
552
+ * });
553
+ * ```
213
554
  *
214
555
  * @public
215
556
  */
216
- export declare interface LixaConfig<TRegisteredProviders extends string = string> {
217
- /** Map of registered provider names to their configurations */
218
- providers: Record<TRegisteredProviders, ProviderConfig>;
219
- /** Optional custom session creation strategy */
557
+ export declare interface LixaConfig<TProviders extends Record<string, ProviderConfig> = Record<string, ProviderConfig>> {
558
+ /**
559
+ * Map of provider names to their configurations.
560
+ * Provider names will be available for autocomplete in getAuthUrl() and handleCallback().
561
+ */
562
+ providers: TProviders;
563
+ /**
564
+ * Optional custom session creation strategy.
565
+ * Defines how OAuth tokens are converted into application sessions.
566
+ * Defaults to DefaultSessionStrategy if not provided.
567
+ *
568
+ * @see {@link SessionStrategy}
569
+ */
220
570
  sessionStrategy?: SessionStrategy;
221
- /** Optional custom state storage implementation */
571
+ /**
572
+ * Optional custom state storage implementation.
573
+ * Used for CSRF protection and PKCE code verifier storage during OAuth flow.
574
+ * Defaults to in-memory cache if not provided (not suitable for production).
575
+ *
576
+ * @see {@link StateDao}
577
+ */
222
578
  stateDao?: StateDao;
223
- /** Optiona: custom session storage implementation */
579
+ /**
580
+ * Optional custom session storage implementation.
581
+ * Used for persistent user session storage after authentication.
582
+ * Defaults to in-memory cache if not provided (not suitable for production).
583
+ *
584
+ * @see {@link SessionDao}
585
+ */
224
586
  sessionDao?: SessionDao;
225
- /** Enable debug logging */
587
+ /**
588
+ * Enable debug logging.
589
+ * When enabled, outputs structured logs for initialization, auth flow, and errors.
590
+ * Format: [Lixa] [timestamp] [level] [context] message
591
+ */
226
592
  debug?: boolean;
227
593
  }
228
594
 
229
595
  /**
230
- * Configuration for an OAuth provider.
596
+ * OAuth 2.0 token response structure.
597
+ * Based on RFC 6749 Section 5.1 and OpenID Connect Core 1.0 Section 3.1.3.3
598
+ *
599
+ * @remarks
600
+ * This interface represents the standard OAuth 2.0 token response with
601
+ * optional OpenID Connect extensions. All OAuth providers should return
602
+ * at minimum the required fields (access_token, token_type).
231
603
  *
232
604
  * @public
233
605
  */
234
- export declare interface ProviderConfig {
606
+ declare interface OAuthTokenResponse {
607
+ /**
608
+ * OAuth 2.0 access token (required).
609
+ * Used to access protected resources on behalf of the user.
610
+ */
611
+ access_token: string;
612
+ /**
613
+ * Token type (required).
614
+ * Typically "Bearer" for OAuth 2.0.
615
+ */
616
+ token_type: string;
617
+ /**
618
+ * Token expiration time in seconds (optional).
619
+ * Time until the access token expires.
620
+ */
621
+ expires_in?: number;
622
+ /**
623
+ * OAuth 2.0 refresh token (optional).
624
+ * Used to obtain new access tokens without re-authentication.
625
+ */
626
+ refresh_token?: string;
627
+ /**
628
+ * Granted OAuth scopes (optional).
629
+ * Space-separated list of scopes that were granted.
630
+ */
631
+ scope?: string;
632
+ /**
633
+ * OpenID Connect ID token (optional).
634
+ * JWT containing user identity claims (only present for OIDC providers).
635
+ */
636
+ id_token?: string;
637
+ /**
638
+ * Additional provider-specific fields.
639
+ * Some providers may include extra fields like user_id, account_id, etc.
640
+ */
641
+ [key: string]: unknown;
642
+ }
643
+
644
+ /**
645
+ * Configuration for an OAuth provider instance.
646
+ *
647
+ * @remarks
648
+ * For built-in providers (google, github), just provide credentials.
649
+ * For custom providers, include the provider implementation.
650
+ *
651
+ * The provider field uses a discriminated union to ensure type safety:
652
+ * - When omitted or undefined: assumes a built-in provider
653
+ * - When provided: must be a valid IProvider implementation
654
+ *
655
+ * @example
656
+ * Built-in provider configuration:
657
+ * ```typescript
658
+ * {
659
+ * clientId: 'your-client-id',
660
+ * clientSecret: 'your-client-secret',
661
+ * redirectUri: 'https://app.com/callback',
662
+ * scopes: ['openid', 'email']
663
+ * }
664
+ * ```
665
+ *
666
+ * @example
667
+ * Custom provider configuration:
668
+ * ```typescript
669
+ * {
670
+ * provider: new CustomProvider(),
671
+ * clientId: 'your-client-id',
672
+ * clientSecret: 'your-client-secret',
673
+ * redirectUri: 'https://app.com/callback',
674
+ * scopes: ['read:user']
675
+ * }
676
+ * ```
677
+ *
678
+ * @public
679
+ */
680
+ export declare type ProviderConfig = {
235
681
  /** The OAuth client ID provided by the provider */
236
682
  clientId: string;
237
683
  /** The OAuth client secret provided by the provider */
@@ -241,56 +687,413 @@ export declare interface ProviderConfig {
241
687
  /** Array of OAuth scopes to request */
242
688
  scopes: string[];
243
689
  /** Additional provider-specific configuration parameters */
244
- extraConfig?: Record<string, any>;
245
- }
690
+ extraConfig?: Record<string, string>;
691
+ } & ({
692
+ provider?: never;
693
+ } | {
694
+ provider: IProvider;
695
+ });
246
696
 
247
697
  /**
248
698
  * Helper type to create a configuration with only registered providers.
249
699
  * Use this with Lixa.createConfig() for type safety.
250
700
  *
701
+ * @deprecated This type is maintained for backward compatibility.
702
+ * The new inline provider configuration pattern makes this unnecessary.
703
+ *
251
704
  * @public
252
705
  */
253
- export declare type SafeLixaConfig<TProviders extends Record<string, ProviderConfig>> = LixaConfig<keyof TProviders & string> & {
706
+ export declare type SafeLixaConfig<TProviders extends Record<string, ProviderConfig>> = LixaConfig<TProviders> & {
254
707
  providers: TProviders;
255
708
  };
256
709
 
257
710
  /**
258
711
  * Represents a user session after successful OAuth authentication.
259
712
  *
713
+ * @remarks
714
+ * The Session object is returned by SessionStrategy.createSession() and contains
715
+ * the session identifier and any additional data needed for your application.
716
+ *
717
+ * The structure is intentionally flexible to support various session management
718
+ * approaches (JWT tokens, session IDs, etc.).
719
+ *
260
720
  * @public
261
721
  */
262
- export declare interface Session {
263
- /** The session token (typically the access token) */
722
+ export declare interface Session<TRaw = OAuthTokenResponse> {
723
+ /**
724
+ * The session token or identifier.
725
+ * This could be an access token, a session ID, a JWT, or any other identifier
726
+ * that your application uses to track authenticated users.
727
+ */
264
728
  token: string;
265
- /** Raw token data from the OAuth provider */
266
- raw: any;
729
+ /**
730
+ * Raw session data.
731
+ * Contains the complete OAuth token response and any additional data
732
+ * your SessionStrategy adds (user info, database IDs, etc.).
733
+ *
734
+ * Typical OAuth token data includes:
735
+ * - access_token: OAuth access token
736
+ * - refresh_token: OAuth refresh token (if requested)
737
+ * - expires_in: Token expiration time in seconds
738
+ * - token_type: Token type (usually "Bearer")
739
+ * - id_token: OpenID Connect ID token (if using OIDC)
740
+ * - scope: Granted scopes
741
+ */
742
+ raw: TRaw;
267
743
  }
268
744
 
269
- declare interface SessionDao {
270
- saveSession(session: string, data: any, expiresInSeconds: number): Promise<void>;
271
- getSession(session: string): Promise<any | null>;
272
- deleteSession(session: string): Promise<void>;
745
+ /**
746
+ * Data access object for session storage.
747
+ *
748
+ * @remarks
749
+ * Session storage is used to persist user sessions after successful OAuth authentication.
750
+ * Sessions must persist across HTTP requests and support TTL (time-to-live).
751
+ *
752
+ * The default implementation uses in-memory cache, which is not suitable for production
753
+ * environments with multiple server instances or server restarts.
754
+ *
755
+ * For production, implement this interface with a distributed cache like Redis,
756
+ * a database, or a session store like express-session.
757
+ *
758
+ * @example
759
+ * Database implementation:
760
+ * ```typescript
761
+ * import { Session } from '../models/session';
762
+ *
763
+ * class DatabaseSessionDao implements SessionDao {
764
+ * constructor(private db: Database) {}
765
+ *
766
+ * async saveSession(sessionId: string, session: Session, expiresInSeconds: number): Promise<void> {
767
+ * const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
768
+ * await this.db.sessions.create({
769
+ * id: sessionId,
770
+ * token: session.token,
771
+ * data: session.raw,
772
+ * expiresAt
773
+ * });
774
+ * }
775
+ *
776
+ * async getSession(sessionId: string): Promise<Session | null> {
777
+ * const record = await this.db.sessions.findOne({
778
+ * id: sessionId,
779
+ * expiresAt: { $gt: new Date() }
780
+ * });
781
+ * return record ? { token: record.token, raw: record.data } : null;
782
+ * }
783
+ *
784
+ * async deleteSession(sessionId: string): Promise<void> {
785
+ * await this.db.sessions.delete({ id: sessionId });
786
+ * }
787
+ * }
788
+ * ```
789
+ *
790
+ * @public
791
+ */
792
+ export declare interface SessionDao {
793
+ /**
794
+ * Saves a session with expiration.
795
+ *
796
+ * @param sessionId - Unique session identifier
797
+ * @param data - Session data from SessionStrategy.createSession()
798
+ * @param expiresInSeconds - TTL in seconds (typically 86400 for 24 hours)
799
+ *
800
+ * @remarks
801
+ * The session data structure is defined by your SessionStrategy implementation.
802
+ * The default strategy returns a Session object with token and raw OAuth token response.
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * await sessionDao.saveSession('session-123', \{
807
+ * token: 'access-token',
808
+ * raw: \{
809
+ * access_token: 'token',
810
+ * token_type: 'Bearer',
811
+ * expires_in: 3600
812
+ * \}
813
+ * \}, 86400);
814
+ * ```
815
+ */
816
+ saveSession<T = unknown>(sessionId: string, data: T, expiresInSeconds: number): Promise<void>;
817
+ /**
818
+ * Retrieves a session by ID.
819
+ *
820
+ * @param sessionId - Unique session identifier
821
+ * @returns Session data or null if not found or expired
822
+ *
823
+ * @remarks
824
+ * This method is called to retrieve user session data for authenticated requests.
825
+ * The returned data should match the structure saved by saveSession.
826
+ *
827
+ * @example
828
+ * ```typescript
829
+ * const session = await sessionDao.getSession('session-123');
830
+ * if (!session) {
831
+ * throw new Error('Session not found or expired');
832
+ * }
833
+ * ```
834
+ */
835
+ getSession<T = unknown>(sessionId: string): Promise<T | null>;
836
+ /**
837
+ * Deletes a session (e.g., on logout).
838
+ *
839
+ * @param sessionId - Unique session identifier
840
+ *
841
+ * @remarks
842
+ * This method should be called when a user logs out to invalidate their session.
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * await sessionDao.deleteSession('session-123');
847
+ * ```
848
+ */
849
+ deleteSession(sessionId: string): Promise<void>;
273
850
  }
274
851
 
275
852
  /**
276
853
  * Strategy interface for custom session creation.
277
854
  *
855
+ * @remarks
856
+ * Implement this interface to customize how OAuth tokens are converted into
857
+ * application sessions. This is where you typically:
858
+ * - Decode ID tokens (for OpenID Connect)
859
+ * - Look up or create users in your database
860
+ * - Generate session identifiers
861
+ * - Store session data
862
+ * - Add custom claims or metadata
863
+ *
864
+ * The default implementation (DefaultSessionStrategy) simply extracts the
865
+ * access token and returns it as the session token.
866
+ *
867
+ * @example
868
+ * Custom session strategy with database integration:
869
+ * ```typescript
870
+ * interface CustomSessionData extends OAuthTokenResponse {
871
+ * userId: string;
872
+ * email: string;
873
+ * }
874
+ *
875
+ * class DatabaseSessionStrategy implements SessionStrategy {
876
+ * constructor(private db: Database) {}
877
+ *
878
+ * async createSession(tokenData: OAuthTokenResponse): Promise<Session<CustomSessionData>> {
879
+ * // Decode ID token for OIDC providers
880
+ * const idToken = tokenData.id_token;
881
+ * const payload = decodeJwt(idToken);
882
+ *
883
+ * // Create or update user in database
884
+ * const user = await this.db.users.upsert({
885
+ * email: payload.email,
886
+ * name: payload.name,
887
+ * picture: payload.picture
888
+ * });
889
+ *
890
+ * // Generate session ID
891
+ * const sessionId = generateSecureId();
892
+ *
893
+ * // Store session with tokens
894
+ * await this.db.sessions.create({
895
+ * id: sessionId,
896
+ * userId: user.id,
897
+ * accessToken: tokenData.access_token,
898
+ * refreshToken: tokenData.refresh_token,
899
+ * expiresAt: new Date(Date.now() + (tokenData.expires_in || 3600) * 1000)
900
+ * });
901
+ *
902
+ * return {
903
+ * token: sessionId,
904
+ * raw: {
905
+ * userId: user.id,
906
+ * email: user.email,
907
+ * ...tokenData
908
+ * }
909
+ * };
910
+ * }
911
+ * }
912
+ * ```
913
+ *
278
914
  * @public
279
915
  */
280
916
  export declare interface SessionStrategy {
281
917
  /**
282
918
  * Creates a session from OAuth token data.
283
919
  *
284
- * @param userInfo - The token data received from the OAuth provider
920
+ * @param tokenData - The token data received from the OAuth provider's token endpoint
285
921
  * @returns A Promise that resolves to a Session object
922
+ *
923
+ * @remarks
924
+ * This method is called after successfully exchanging the authorization code
925
+ * for tokens. The tokenData parameter contains the raw response from the
926
+ * provider's token endpoint.
927
+ *
928
+ * Common token data fields:
929
+ * - access_token: OAuth access token
930
+ * - refresh_token: OAuth refresh token (optional)
931
+ * - expires_in: Token expiration time in seconds
932
+ * - token_type: Token type (usually "Bearer")
933
+ * - id_token: OpenID Connect ID token (for OIDC providers)
934
+ * - scope: Granted scopes
935
+ *
936
+ * @throws \{Error\} If session creation fails (e.g., database error, invalid token)
937
+ *
938
+ * @example
939
+ * Simple implementation:
940
+ * ```typescript
941
+ * async createSession(tokenData: OAuthTokenResponse): Promise<Session> {
942
+ * return {
943
+ * token: tokenData.access_token,
944
+ * raw: tokenData
945
+ * };
946
+ * }
947
+ * ```
286
948
  */
287
- createSession(userInfo: any): Promise<Session>;
949
+ createSession(tokenData: OAuthTokenResponse): Promise<Session>;
288
950
  }
289
951
 
290
- declare interface StateDao {
291
- saveState(state: string, data: any, expiresInSeconds: number): Promise<void>;
292
- getState(state: string): Promise<any | null>;
952
+ /**
953
+ * Data access object for OAuth state storage.
954
+ *
955
+ * @remarks
956
+ * State storage is used during the OAuth authorization flow to:
957
+ * - Prevent CSRF attacks by validating the state parameter
958
+ * - Store PKCE code verifiers for secure token exchange
959
+ * - Maintain OAuth flow context across HTTP requests
960
+ *
961
+ * State data must persist across HTTP requests and support TTL (time-to-live).
962
+ * The default implementation uses in-memory cache, which is not suitable for production
963
+ * environments with multiple server instances or server restarts.
964
+ *
965
+ * For production, implement this interface with a distributed cache like Redis,
966
+ * or a database with TTL support.
967
+ *
968
+ * @example
969
+ * Redis implementation:
970
+ * ```typescript
971
+ * class RedisStateDao implements StateDao {
972
+ * constructor(private redis: RedisClient) {}
973
+ *
974
+ * async saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void> {
975
+ * await this.redis.setex(`oauth:state:${state}`, expiresInSeconds, JSON.stringify(data));
976
+ * }
977
+ *
978
+ * async getState(state: string): Promise<StateData | null> {
979
+ * const data = await this.redis.get(`oauth:state:${state}`);
980
+ * return data ? JSON.parse(data) : null;
981
+ * }
982
+ *
983
+ * async deleteState(state: string): Promise<void> {
984
+ * await this.redis.del(`oauth:state:${state}`);
985
+ * }
986
+ * }
987
+ * ```
988
+ *
989
+ * @public
990
+ */
991
+ export declare interface StateDao {
992
+ /**
993
+ * Saves OAuth state with expiration.
994
+ *
995
+ * @param state - The state parameter value (random string for CSRF protection)
996
+ * @param data - State data including provider name and PKCE code verifier
997
+ * @param expiresInSeconds - TTL in seconds (typically 300 for 5 minutes)
998
+ *
999
+ * @remarks
1000
+ * The data object MUST include the following required fields:
1001
+ * - **provider** (string): Provider name for callback routing (e.g., 'google', 'github')
1002
+ * - **codeVerifier** (string): PKCE code verifier for secure token exchange (64 hex characters)
1003
+ * - **createdAt** (number): Unix timestamp in milliseconds for debugging and validation
1004
+ *
1005
+ * These fields are automatically populated by Lixa during the authorization flow.
1006
+ * The state is stored when generating the authorization URL and retrieved during
1007
+ * the OAuth callback to complete the PKCE flow.
1008
+ *
1009
+ * @see {@link StateData} for the complete state data structure
1010
+ *
1011
+ * @example
1012
+ * ```typescript
1013
+ * await stateDao.saveState('random-state-123', {
1014
+ * provider: 'google',
1015
+ * codeVerifier: 'abc123...',
1016
+ * createdAt: Date.now()
1017
+ * }, 300);
1018
+ * ```
1019
+ */
1020
+ saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void>;
1021
+ /**
1022
+ * Retrieves OAuth state data.
1023
+ *
1024
+ * @param state - The state parameter value
1025
+ * @returns State data or null if not found or expired
1026
+ *
1027
+ * @remarks
1028
+ * This method is called during the OAuth callback to validate the state
1029
+ * parameter and retrieve the PKCE code verifier for token exchange.
1030
+ *
1031
+ * The returned data will include:
1032
+ * - provider: Provider name for routing
1033
+ * - codeVerifier: PKCE code verifier for token exchange
1034
+ * - createdAt: Timestamp when state was created
1035
+ *
1036
+ * @see {@link StateData} for the complete state data structure
1037
+ *
1038
+ * @example
1039
+ * ```typescript
1040
+ * const stateData = await stateDao.getState('random-state-123');
1041
+ * if (!stateData) {
1042
+ * throw new Error('Invalid or expired state');
1043
+ * }
1044
+ * console.log(stateData.provider); // 'google'
1045
+ * console.log(stateData.codeVerifier); // 'abc123...'
1046
+ * ```
1047
+ */
1048
+ getState(state: string): Promise<StateData | null>;
1049
+ /**
1050
+ * Deletes OAuth state (called after successful validation).
1051
+ *
1052
+ * @param state - The state parameter value
1053
+ *
1054
+ * @remarks
1055
+ * This method should be called after successfully validating the state
1056
+ * to prevent replay attacks. State should only be usable once.
1057
+ *
1058
+ * @example
1059
+ * ```typescript
1060
+ * await stateDao.deleteState('random-state-123');
1061
+ * ```
1062
+ */
293
1063
  deleteState(state: string): Promise<void>;
294
1064
  }
295
1065
 
1066
+ /**
1067
+ * OAuth state data structure.
1068
+ *
1069
+ * @remarks
1070
+ * This structure is used internally by Lixa to store OAuth flow state
1071
+ * during the authorization process. It contains the information needed
1072
+ * to complete the PKCE flow and route callbacks to the correct provider.
1073
+ *
1074
+ * @public
1075
+ */
1076
+ export declare interface StateData {
1077
+ /**
1078
+ * Provider name for callback routing.
1079
+ * Used to identify which provider configuration to use when handling the callback.
1080
+ *
1081
+ * @example 'google', 'github', 'custom'
1082
+ */
1083
+ provider: string;
1084
+ /**
1085
+ * PKCE code verifier for secure token exchange.
1086
+ * A cryptographically random string (64 hex characters) used in the PKCE flow
1087
+ * to prevent authorization code interception attacks.
1088
+ *
1089
+ * @see RFC 7636 - Proof Key for Code Exchange
1090
+ */
1091
+ codeVerifier: string;
1092
+ /**
1093
+ * Unix timestamp in milliseconds when the state was created.
1094
+ * Used for debugging and validation purposes.
1095
+ */
1096
+ createdAt: number;
1097
+ }
1098
+
296
1099
  export { }