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