@vunexa/lixa 0.0.1-alpha.3 → 0.0.1-alpha.33

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