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