@vunexa/lixa 0.0.1-alpha.28 → 0.0.1-alpha.29
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/README.md +18 -23
- package/dist/export-types/index.d.ts +17 -24
- package/dist/index.cjs +7 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -24
- package/dist/index.js +7 -33
- package/dist/index.js.map +1 -1
- package/dist/utils/user-info.d.ts +18 -25
- package/dist/utils/user-info.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -558,15 +558,11 @@ import { extractUserInfo, type UserInfo } from "@vunexa/lixa";
|
|
|
558
558
|
|
|
559
559
|
// In your session strategy
|
|
560
560
|
const sessionStrategy = {
|
|
561
|
-
createSession: async (tokenData) => {
|
|
561
|
+
createSession: async (tokenData, providerMetadata) => {
|
|
562
562
|
// Automatically extracts user info from ID token or fetches from API
|
|
563
|
-
const { userInfo
|
|
564
|
-
tokenData,
|
|
565
|
-
providerConfig.userInfoEndpoint, // Optional: from IProvider
|
|
566
|
-
'google' // Optional: provider name for error messages
|
|
567
|
-
);
|
|
563
|
+
const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
568
564
|
|
|
569
|
-
console.log(`User ${userInfo.email} authenticated
|
|
565
|
+
console.log(`User ${userInfo.email} authenticated`);
|
|
570
566
|
|
|
571
567
|
// Create user in database
|
|
572
568
|
const user = await db.users.upsert({
|
|
@@ -587,17 +583,16 @@ const sessionStrategy = {
|
|
|
587
583
|
|
|
588
584
|
**Parameters:**
|
|
589
585
|
- `tokenData` (OAuthTokenResponse) - OAuth token response from provider
|
|
590
|
-
- `
|
|
591
|
-
- `providerName` (string, optional) - Provider name for error messages
|
|
586
|
+
- `providerMetadata` (ProviderMetadata) - Provider metadata containing endpoints configuration
|
|
592
587
|
|
|
593
588
|
**Returns:**
|
|
594
|
-
- `{ userInfo: UserInfo
|
|
589
|
+
- `{ userInfo: UserInfo }` - User information extracted from token or fetched from provider
|
|
595
590
|
|
|
596
591
|
**Behavior:**
|
|
597
|
-
1. If ID token present: Decodes JWT and extracts user info
|
|
598
|
-
2. If only access token: Fetches from userinfo endpoint
|
|
599
|
-
3.
|
|
600
|
-
4. Throws clear errors if
|
|
592
|
+
1. If ID token present: Decodes JWT and extracts user info (OIDC providers like Google)
|
|
593
|
+
2. If only access token: Fetches from userinfo endpoint using `providerMetadata.endpoints.userInfo` (OAuth providers like GitHub)
|
|
594
|
+
3. Automatically determines the best method based on available token data
|
|
595
|
+
4. Throws clear errors if neither ID token nor access token is available
|
|
601
596
|
|
|
602
597
|
### `decodeIdToken()`
|
|
603
598
|
|
|
@@ -645,6 +640,8 @@ const provider = determineProviderFromIssuer(userInfo);
|
|
|
645
640
|
- GitHub: Detects `github` in issuer
|
|
646
641
|
- Returns `null` for unknown issuers
|
|
647
642
|
|
|
643
|
+
**Note:** This utility is primarily for informational purposes. The `extractUserInfo()` function no longer returns provider information as it now relies on `providerMetadata` parameter.
|
|
644
|
+
|
|
648
645
|
### `UserInfo` Type
|
|
649
646
|
|
|
650
647
|
Standard user information structure across all providers:
|
|
@@ -688,11 +685,11 @@ const lixa = new Lixa({
|
|
|
688
685
|
},
|
|
689
686
|
|
|
690
687
|
sessionStrategy: {
|
|
691
|
-
createSession: async (tokenData) => {
|
|
688
|
+
createSession: async (tokenData, providerMetadata) => {
|
|
692
689
|
// Extract user info - works for both Google (ID token) and GitHub (API fetch)
|
|
693
|
-
const { userInfo
|
|
690
|
+
const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
694
691
|
|
|
695
|
-
console.log(`User ${userInfo.email} authenticated
|
|
692
|
+
console.log(`User ${userInfo.email} authenticated`);
|
|
696
693
|
|
|
697
694
|
// Create or update user in database
|
|
698
695
|
const user = await db.users.upsert({
|
|
@@ -709,7 +706,6 @@ const lixa = new Lixa({
|
|
|
709
706
|
await db.sessions.create({
|
|
710
707
|
id: sessionId,
|
|
711
708
|
userId: user.id,
|
|
712
|
-
provider,
|
|
713
709
|
accessToken: tokenData.access_token,
|
|
714
710
|
refreshToken: tokenData.refresh_token,
|
|
715
711
|
expiresAt: tokenData.expires_in
|
|
@@ -722,7 +718,6 @@ const lixa = new Lixa({
|
|
|
722
718
|
raw: {
|
|
723
719
|
...tokenData,
|
|
724
720
|
userId: user.id,
|
|
725
|
-
provider,
|
|
726
721
|
}
|
|
727
722
|
};
|
|
728
723
|
}
|
|
@@ -733,11 +728,11 @@ const lixa = new Lixa({
|
|
|
733
728
|
### Benefits of User Info Utilities
|
|
734
729
|
|
|
735
730
|
✅ **Provider-agnostic** - Works with any OAuth provider
|
|
736
|
-
✅ **Automatic
|
|
731
|
+
✅ **Automatic method selection** - Uses ID token or fetches from API based on what's available
|
|
737
732
|
✅ **Type-safe** - Full TypeScript support with UserInfo interface
|
|
738
|
-
✅ **Flexible** - Supports both ID token and API
|
|
739
|
-
✅ **Error handling** - Clear error messages when
|
|
740
|
-
✅ **Uses
|
|
733
|
+
✅ **Flexible** - Supports both OIDC (ID token) and OAuth-only (API fetch) providers
|
|
734
|
+
✅ **Error handling** - Clear error messages when token data is invalid
|
|
735
|
+
✅ **Uses provider metadata** - Leverages userInfoEndpoint from provider configuration
|
|
741
736
|
|
|
742
737
|
## API Reference
|
|
743
738
|
|
|
@@ -57,47 +57,40 @@ export declare function determineProviderFromIssuer(userInfo: UserInfo): string
|
|
|
57
57
|
* Extract user info from OAuth token data
|
|
58
58
|
*
|
|
59
59
|
* @param tokenData - OAuth token response from provider
|
|
60
|
-
* @param
|
|
61
|
-
* @
|
|
62
|
-
* @returns User info and detected provider name
|
|
60
|
+
* @param providerMetadata - Provider metadata containing endpoints configuration
|
|
61
|
+
* @returns User info extracted from token or fetched from provider
|
|
63
62
|
*
|
|
64
63
|
* @remarks
|
|
65
64
|
* This function attempts to extract user information in the following order:
|
|
66
|
-
* 1. Decode ID token if present (preferred method)
|
|
67
|
-
* 2. Fetch from userinfo endpoint using access token (
|
|
65
|
+
* 1. Decode ID token if present (preferred method for OIDC providers)
|
|
66
|
+
* 2. Fetch from userinfo endpoint using access token (uses providerMetadata.endpoints.userInfo)
|
|
68
67
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* -
|
|
72
|
-
* - Fallback: Use providerName parameter
|
|
73
|
-
* - Throws error if provider cannot be determined
|
|
68
|
+
* The function automatically determines the best method based on available token data.
|
|
69
|
+
* For OIDC providers (like Google), it decodes the JWT ID token.
|
|
70
|
+
* For OAuth-only providers (like GitHub), it fetches from the userinfo endpoint.
|
|
74
71
|
*
|
|
75
72
|
* @throws Error if no ID token or access token is available
|
|
76
|
-
* @throws Error if
|
|
77
|
-
* @throws Error if userInfoEndpoint is required but not provided
|
|
73
|
+
* @throws Error if userinfo endpoint is required but not provided in providerMetadata
|
|
78
74
|
*
|
|
79
75
|
* @example
|
|
80
|
-
* With ID token (provider
|
|
76
|
+
* With ID token (OIDC provider like Google):
|
|
81
77
|
* ```typescript
|
|
82
|
-
* const { userInfo
|
|
83
|
-
* console.log(`User ${userInfo.email} authenticated
|
|
78
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
79
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
84
80
|
* ```
|
|
85
81
|
*
|
|
86
82
|
* @example
|
|
87
|
-
* Without ID token (
|
|
83
|
+
* Without ID token (OAuth provider like GitHub):
|
|
88
84
|
* ```typescript
|
|
89
|
-
* const { userInfo
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* 'custom'
|
|
93
|
-
* );
|
|
85
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
86
|
+
* // Automatically fetches from providerMetadata.endpoints.userInfo
|
|
87
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
94
88
|
* ```
|
|
95
89
|
*
|
|
96
90
|
* @public
|
|
97
91
|
*/
|
|
98
|
-
export declare function extractUserInfo(tokenData: OAuthTokenResponse,
|
|
92
|
+
export declare function extractUserInfo(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<{
|
|
99
93
|
userInfo: UserInfo;
|
|
100
|
-
provider: string;
|
|
101
94
|
}>;
|
|
102
95
|
|
|
103
96
|
/**
|
|
@@ -112,7 +105,7 @@ export declare function extractUserInfo(tokenData: OAuthTokenResponse, userInfoE
|
|
|
112
105
|
*
|
|
113
106
|
* @public
|
|
114
107
|
*/
|
|
115
|
-
export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string
|
|
108
|
+
export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string): Promise<UserInfo>;
|
|
116
109
|
|
|
117
110
|
/**
|
|
118
111
|
* Interface for OAuth 2.0 and OpenID Connect provider implementations.
|
package/dist/index.cjs
CHANGED
|
@@ -645,7 +645,7 @@ function determineProviderFromIssuer(userInfo) {
|
|
|
645
645
|
}
|
|
646
646
|
return null;
|
|
647
647
|
}
|
|
648
|
-
async function fetchUserInfo(accessToken, userInfoEndpoint
|
|
648
|
+
async function fetchUserInfo(accessToken, userInfoEndpoint) {
|
|
649
649
|
const response = await fetch(userInfoEndpoint, {
|
|
650
650
|
headers: {
|
|
651
651
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -653,13 +653,11 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
|
|
|
653
653
|
}
|
|
654
654
|
});
|
|
655
655
|
if (!response.ok) {
|
|
656
|
-
|
|
657
|
-
throw new Error(`Failed to fetch user info${providerLabel}: ${response.status} ${response.statusText}`);
|
|
656
|
+
throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);
|
|
658
657
|
}
|
|
659
658
|
const data = await response.json();
|
|
660
659
|
if (!data || typeof data !== "object" || !("email" in data) || typeof data.email !== "string") {
|
|
661
|
-
|
|
662
|
-
throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);
|
|
660
|
+
throw new Error(`Invalid user info response: missing or invalid email`);
|
|
663
661
|
}
|
|
664
662
|
return {
|
|
665
663
|
email: data.email,
|
|
@@ -673,41 +671,17 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
|
|
|
673
671
|
iss: "iss" in data ? String(data.iss) : void 0
|
|
674
672
|
};
|
|
675
673
|
}
|
|
676
|
-
async function extractUserInfo(tokenData,
|
|
674
|
+
async function extractUserInfo(tokenData, providerMetadata) {
|
|
677
675
|
let userInfo;
|
|
678
|
-
|
|
676
|
+
const userInfoEndpoint = providerMetadata.endpoints.userInfo;
|
|
679
677
|
if (tokenData.id_token) {
|
|
680
678
|
userInfo = decodeIdToken(tokenData.id_token);
|
|
681
|
-
provider = determineProviderFromIssuer(userInfo);
|
|
682
|
-
if (!provider && providerName) {
|
|
683
|
-
provider = providerName;
|
|
684
|
-
}
|
|
685
679
|
} else if (tokenData.access_token) {
|
|
686
|
-
|
|
687
|
-
provider = tokenData.provider;
|
|
688
|
-
} else if (providerName) {
|
|
689
|
-
provider = providerName;
|
|
690
|
-
}
|
|
691
|
-
if (!provider) {
|
|
692
|
-
throw new Error(
|
|
693
|
-
"Cannot determine OAuth provider: No ID token with issuer information, no provider field in token data, and no providerName provided. Unable to fetch user info."
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
if (!userInfoEndpoint) {
|
|
697
|
-
throw new Error(
|
|
698
|
-
`Cannot fetch user info for provider '${provider}': No ID token available and no userInfoEndpoint provided. Either ensure the provider returns an ID token or provide the userInfoEndpoint parameter.`
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint, provider);
|
|
680
|
+
userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
|
|
702
681
|
} else {
|
|
703
682
|
throw new Error("No ID token or access token available to fetch user info");
|
|
704
683
|
}
|
|
705
|
-
|
|
706
|
-
throw new Error(
|
|
707
|
-
"Cannot determine OAuth provider: ID token issuer not recognized and no provider name provided."
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
return { userInfo, provider };
|
|
684
|
+
return { userInfo };
|
|
711
685
|
}
|
|
712
686
|
// Annotate the CommonJS export names for ESM import in node:
|
|
713
687
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts","../src/utils/user-info.ts"],"sourcesContent":["/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library for backend applications.\n * \n * @remarks\n * This package simplifies multi-provider authentication flows (e.g., Google, GitHub), supports extensible session management, and enables custom provider registration.\n * \n * Key features:\n * - OAuth 2.0 authorization code flow with PKCE (RFC 6749, RFC 7636)\n * - OpenID Connect support\n * - Built-in providers available in \\@vunexa/lixa-providers\n * - Custom provider support via IProvider interface\n * - Extensible session management via SessionStrategy\n * - Pluggable state and session storage via StateDao and SessionDao\n * - TypeScript-first with comprehensive type safety\n * \n * @packageDocumentation\n */\n\nexport { Lixa } from \"./lixa\";\nexport {\n type ProviderConfig,\n type LixaConfig,\n type SafeLixaConfig,\n type IProvider,\n type SessionStrategy,\n type Session,\n type ProviderMetadata,\n type OAuthTokenResponse,\n type StateDao,\n type SessionDao,\n type StateData,\n} from \"./types\";\nexport { DefaultSessionStrategy } from \"./models/session\";\nexport {\n type UserInfo,\n extractUserInfo,\n decodeIdToken,\n fetchUserInfo,\n determineProviderFromIssuer,\n} from \"./utils/user-info\";\n","import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy, type OAuthTokenResponse, type ProviderMetadata } from \"./models/session\";\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n * Providers can be passed inline in the configuration, eliminating the need for pre-registration.\n *\n * @example\n * Using built-in providers from \\@vunexa/lixa-providers:\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa-providers';\n * \n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * ```\n * \n * @example\n * Using custom inline providers:\n * ```typescript\n * import { Lixa, IProvider } from '@vunexa/lixa';\n * \n * const customProvider: IProvider = {\n * authorizationEndpoint: 'https://custom.com/oauth/authorize',\n * tokenEndpoint: 'https://custom.com/oauth/token',\n * userInfoEndpoint: 'https://custom.com/api/user'\n * };\n * \n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: customProvider,\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/custom/callback',\n * scopes: ['read:user']\n * }\n * }\n * });\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<Record<string, ProviderConfig>> = LixaConfig> {\n private static DEFAULT_PROVIDERS: Map<string, IProvider> = new Map();\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map(); // Legacy registry for backward compatibility\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n * \n * @remarks\n * Providers can be passed inline in the configuration using the `provider` field.\n * Provider resolution priority: inline custom provider \\> default providers \\> legacy registry.\n * \n * @param config - The configuration object containing provider settings and optional session strategy\n * \n * @throws Error when provider configuration is missing required fields\n * @throws Error when provider implementation is missing required properties\n * @throws Error when provider is not available and no inline implementation is provided\n */\n constructor(config: TConfig) {\n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n \n this.log('INFO', 'Init', 'Initializing Lixa instance', { \n providers: Object.keys(config.providers),\n debug: this.debug \n });\n \n // Validate and extract providers from configuration\n for (const [providerName, providerConfig] of Object.entries(config.providers)) {\n const name = providerName.toLowerCase();\n const typedConfig: ProviderConfig = providerConfig;\n \n // Validate provider configuration has required credentials\n this.validateProviderConfig(providerName, typedConfig);\n \n // If provider config includes a custom provider implementation, validate it\n if (typedConfig.provider) {\n this.validateProviderImplementation(providerName, typedConfig.provider);\n this.log('INFO', 'Init', `Registered inline provider: ${providerName}`);\n } else {\n // Check if it's available in default providers or legacy registry\n if (!Lixa.DEFAULT_PROVIDERS.has(name) && !Lixa.CONFIGURED_PROVIDERS.has(name)) {\n this.log('ERROR', 'Init', `Provider '${providerName}' not available`);\n throw new Error(\n `Provider '${providerName}' is not available. ` +\n `Either import it from '@vunexa/lixa-providers' and include it in the configuration, ` +\n `or provide a custom implementation using the 'provider' field: ` +\n `{ provider: new CustomProvider(), clientId: '...', ... }`\n );\n }\n this.log('INFO', 'Init', `Using registered provider: ${providerName}`);\n }\n }\n \n this.log('INFO', 'Init', 'Lixa instance initialized successfully');\n }\n \n /**\n * Validates that a provider configuration has all required credentials.\n * \n * @param name - The provider name\n * @param config - The provider configuration\n * @throws Error when required fields are missing or invalid\n */\n private validateProviderConfig(name: string, config: ProviderConfig): void {\n const requiredFields: (keyof ProviderConfig)[] = ['clientId', 'clientSecret', 'redirectUri', 'scopes'];\n const missingFields = requiredFields.filter(field => {\n const value = config[field];\n return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');\n });\n \n if (missingFields.length > 0) {\n throw new Error(\n `Provider '${name}' configuration is missing required fields: ${missingFields.join(', ')}`\n );\n }\n \n // Validate scopes is an array\n if (!Array.isArray(config.scopes)) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' must be an array of strings`\n );\n }\n \n if (config.scopes.length === 0) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' array cannot be empty`\n );\n }\n }\n \n /**\n * Validates that a provider implementation has all required properties.\n * \n * @param name - The provider name\n * @param provider - The provider implementation\n * @throws Error when required properties are missing\n */\n private validateProviderImplementation(name: string, provider: IProvider): void {\n const requiredProps: (keyof IProvider)[] = ['authorizationEndpoint', 'tokenEndpoint', 'userInfoEndpoint'];\n const missingProps = requiredProps.filter(prop => {\n const value = provider[prop];\n return !value || typeof value !== 'string' || value.trim() === '';\n });\n \n if (missingProps.length > 0) {\n throw new Error(\n `Provider '${name}' implementation is missing required properties: ${missingProps.join(', ')}. ` +\n `All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`\n );\n }\n }\n\n /**\n * Structured debug logging with standardized format.\n * \n * @param level - Log level (INFO, WARN, ERROR)\n * @param context - Context of the log (Init, Auth, Token, Session, State)\n * @param message - Log message\n * @param data - Optional data to log\n * \n * @remarks\n * Format: [Lixa] [timestamp] [level] [context] message\n * Only logs when debug mode is enabled.\n */\n private log(level: 'INFO' | 'WARN' | 'ERROR', context: 'Init' | 'Auth' | 'Token' | 'Session' | 'State', message: string, data?: Record<string, string | number | boolean | string[]>): void {\n if (!this.debug) return;\n \n const timestamp = new Date().toISOString();\n const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;\n \n if (data !== undefined) {\n console.log(`${prefix} ${message}`, data);\n } else {\n console.log(`${prefix} ${message}`);\n }\n }\n\n /**\n * Checks if a provider is configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return this.config.providers.hasOwnProperty(providerType);\n }\n \n /**\n * Gets a provider implementation by name.\n * Resolution priority: inline custom provider \\> default providers \\> legacy registry\n * \n * @param name - The provider name (case-insensitive)\n * @param config - The provider configuration\n * @returns The provider implementation\n * @throws Error when provider is not found\n */\n private getProvider(name: string, config: ProviderConfig): IProvider {\n // First check if provider is inline in config\n if (config.provider) {\n return config.provider;\n }\n \n // Then check default providers\n const lowerName = name.toLowerCase();\n const defaultProvider = Lixa.DEFAULT_PROVIDERS.get(lowerName);\n if (defaultProvider) {\n return defaultProvider;\n }\n \n // Finally check legacy registry for backward compatibility\n const legacyProvider = Lixa.CONFIGURED_PROVIDERS.get(lowerName);\n if (legacyProvider) {\n return legacyProvider;\n }\n \n throw new Error(\n `Provider '${name}' not found. ` +\n `Ensure the provider is included in the configuration with a 'provider' field, ` +\n `or registered using Lixa.registerProvider().`\n );\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n * \n * @deprecated This method is maintained for backward compatibility.\n * The recommended approach is to pass providers inline in the configuration:\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: new CustomProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * Legacy usage (still supported):\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration.\n * \n * @deprecated This method is maintained for backward compatibility.\n * You can now pass configuration directly to the Lixa constructor without this helper.\n * \n * @param config - Configuration object with provider settings\n * @returns The same configuration object with type safety\n * \n * @example\n * New approach (recommended):\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<T> & { providers: T }\n ): LixaConfig<T> {\n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)\n *\n * @remarks\n * This method implements the code verifier generation as specified in RFC 7636 (PKCE).\n * \n * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that\n * prevents authorization code interception attacks. It's especially important for\n * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.\n * \n * **Generation methodology:**\n * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()\n * 2. Encode the bytes as a hexadecimal string (64 characters)\n * 3. The verifier is stored securely and used later in the token exchange\n * \n * **RFC 7636 Requirements:**\n * - Minimum length: 43 characters\n * - Maximum length: 128 characters\n * - Character set: [A-Z] / [a-z] / [0-9] / \"-\" / \".\" / \"_\" / \"~\"\n * - This implementation produces 64 hex characters, meeting the requirements\n * \n * The code verifier is:\n * - Generated when creating the authorization URL\n * - Stored in state cache with the state parameter\n * - Retrieved during callback handling\n * - Sent to the token endpoint to prove the client's identity\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see buildCodeChallenge for the corresponding challenge generation\n * \n * @internal\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n /**\n * Generates a code challenge from a code verifier for PKCE flows.\n *\n * @param codeVerifier - The code verifier string (64 hex characters)\n * @returns A base64url-encoded SHA-256 hash of the code verifier\n *\n * @remarks\n * This method implements the code challenge generation as specified in RFC 7636 (PKCE)\n * using the S256 (SHA-256) transformation method.\n * \n * **Challenge generation methodology:**\n * 1. Hash the code verifier using SHA-256\n * 2. Encode the hash as base64\n * 3. Convert to base64url format (RFC 4648):\n * - Replace '+' with '-'\n * - Replace '/' with '_'\n * - Remove trailing '=' padding\n * \n * **PKCE Flow:**\n * 1. Client generates code_verifier (random string)\n * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))\n * 3. Client sends code_challenge to authorization endpoint\n * 4. Authorization server stores the code_challenge\n * 5. Client sends code_verifier to token endpoint\n * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge\n * \n * **Security Benefits:**\n * - Prevents authorization code interception attacks\n * - Even if an attacker intercepts the authorization code, they cannot\n * exchange it for tokens without the original code_verifier\n * - The challenge is sent in the authorization request (public)\n * - The verifier is sent in the token request (should be kept secret)\n * \n * **RFC 7636 Transformation Methods:**\n * - plain: code_challenge = code_verifier (not recommended)\n * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}\n * @see generateCodeVerifier for the verifier generation\n * \n * @internal\n */\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url (RFC 4648 Section 5)\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n \n this.log('INFO', 'Auth', `Generating authorization URL for provider: ${providerType}`);\n \n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n this.log('INFO', 'State', `Saving state for provider: ${providerType}`, { state });\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n this.log('INFO', 'Auth', `Authorization URL generated successfully`, { \n provider: providerType,\n endpoint: providerImpl.authorizationEndpoint \n });\n\n return authUrl;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n const providerType = String(provider).toLowerCase();\n this.log('INFO', 'Auth', `Handling OAuth callback for provider: ${providerType}`);\n\n if (!code || code.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing authorization code in callback');\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing state in callback');\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n this.log('INFO', 'State', 'Validating state parameter', { state });\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n this.log('ERROR', 'State', 'State validation failed: state not found or expired', { state });\n throw new Error(\"Invalid or expired state\");\n }\n \n this.log('INFO', 'State', 'State validated successfully, removing from cache');\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n this.log('INFO', 'Token', `Exchanging authorization code for tokens`, { provider: providerType });\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n this.log('INFO', 'Token', 'Token exchange successful');\n this.log('INFO', 'Session', 'Creating user session');\n \n // Create provider metadata for session strategy\n const providerMetadata: ProviderMetadata = {\n name: providerType,\n endpoints: {\n authorization: providerImpl.authorizationEndpoint,\n token: providerImpl.tokenEndpoint,\n userInfo: providerImpl.userInfoEndpoint\n }\n };\n \n const session = await this.sessionStrategy.createSession(tokens, providerMetadata);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n this.log('INFO', 'Session', 'Storing session', { sessionId });\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n this.log('INFO', 'Session', 'Session created successfully', { sessionId });\n\n return sessionId;\n }\n\n public async fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return await this.sesionDao.getSession<Session>(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<OAuthTokenResponse> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('INFO', 'Token', 'Sending token exchange request', { \n endpoint: providerImpl.tokenEndpoint \n });\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.log('ERROR', 'Token', 'Token exchange failed', { \n status: response.status, \n statusText: response.statusText,\n error: errorBody \n });\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n this.log('INFO', 'Token', 'Token exchange response received successfully');\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n // Use Object.entries to safely iterate and find the provider\n for (const [key, value] of Object.entries(this.config.providers)) {\n if (key.toLowerCase() === providerType.toLowerCase()) {\n return value;\n }\n }\n return undefined;\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao, StateData } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<StateData | null> {\n return this.cache.get<StateData>(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\nimport type { Session } from \"../models/session\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession<T extends Session>(state: string, data: T, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession<T extends Session>(state: string): Promise<T | null> {\n return this.cache.get<T>(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * OAuth 2.0 token response structure.\n * Based on RFC 6749 Section 5.1 and OpenID Connect Core 1.0 Section 3.1.3.3\n * \n * @remarks\n * This interface represents the standard OAuth 2.0 token response with\n * optional OpenID Connect extensions. All OAuth providers should return\n * at minimum the required fields (access_token, token_type).\n * \n * @public\n */\nexport interface OAuthTokenResponse {\n /** \n * OAuth 2.0 access token (required).\n * Used to access protected resources on behalf of the user.\n */\n access_token: string;\n \n /** \n * Token type (required).\n * Typically \"Bearer\" for OAuth 2.0.\n */\n token_type: string;\n \n /** \n * Token expiration time in seconds (optional).\n * Time until the access token expires.\n */\n expires_in?: number;\n \n /** \n * OAuth 2.0 refresh token (optional).\n * Used to obtain new access tokens without re-authentication.\n */\n refresh_token?: string;\n \n /** \n * Granted OAuth scopes (optional).\n * Space-separated list of scopes that were granted.\n */\n scope?: string;\n \n /** \n * OpenID Connect ID token (optional).\n * JWT containing user identity claims (only present for OIDC providers).\n */\n id_token?: string;\n \n /**\n * Additional provider-specific fields.\n * Some providers may include extra fields like user_id, account_id, etc.\n */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Represents a user session after successful OAuth authentication.\n * \n * @remarks\n * The Session object is returned by SessionStrategy.createSession() and contains\n * the session identifier and any additional data needed for your application.\n * \n * The structure is intentionally flexible to support various session management\n * approaches (JWT tokens, session IDs, etc.).\n *\n * @public\n */\nexport interface Session<TRaw = OAuthTokenResponse> {\n /** \n * The session token or identifier.\n * This could be an access token, a session ID, a JWT, or any other identifier\n * that your application uses to track authenticated users.\n */\n token: string;\n \n /** \n * Raw session data.\n * Contains the complete OAuth token response and any additional data\n * your SessionStrategy adds (user info, database IDs, etc.).\n * \n * Typical OAuth token data includes:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (if requested)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (if using OIDC)\n * - scope: Granted scopes\n */\n raw: TRaw;\n}\n\n/**\n * Provider metadata passed to session strategy.\n * Contains provider name and endpoints for user info extraction.\n * \n * @public\n */\nexport interface ProviderMetadata {\n /** The provider name (e.g., 'google', 'github') */\n name: string;\n \n /** Provider endpoints */\n endpoints: {\n /** Authorization endpoint URL */\n authorization: string;\n /** Token endpoint URL */\n token: string;\n /** UserInfo endpoint URL */\n userInfo: string;\n };\n}\n\n/**\n * Strategy interface for custom session creation.\n * \n * @remarks\n * Implement this interface to customize how OAuth tokens are converted into\n * application sessions. This is where you typically:\n * - Decode ID tokens (for OpenID Connect)\n * - Look up or create users in your database\n * - Generate session identifiers\n * - Store session data\n * - Add custom claims or metadata\n * \n * The default implementation (DefaultSessionStrategy) simply extracts the\n * access token and returns it as the session token.\n * \n * @example\n * Custom session strategy with database integration:\n * ```typescript\n * interface CustomSessionData {\n * userId: string;\n * email: string;\n * provider: string;\n * accessToken: string;\n * refreshToken?: string;\n * expiresAt: number;\n * }\n * \n * class DatabaseSessionStrategy implements SessionStrategy {\n * constructor(private db: Database) {}\n * \n * async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {\n * // User info is already extracted by Lixa!\n * const { userInfo, provider, tokenData } = oauthContext;\n * \n * // Create or update user in database\n * const user = await this.db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name,\n * picture: userInfo.picture\n * });\n * \n * // Generate session ID\n * const sessionId = generateSecureId();\n * \n * // Store session with tokens\n * await this.db.sessions.create({\n * id: sessionId,\n * userId: user.id,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: new Date(Date.now() + (tokenData.expires_in || 3600) * 1000)\n * });\n * \n * return {\n * token: sessionId,\n * raw: {\n * userId: user.id,\n * email: user.email,\n * provider,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000\n * }\n * };\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * \n * @param tokenData - The token data received from the OAuth provider's token endpoint\n * @param providerMetadata - Provider metadata including name and endpoints\n * @returns A Promise that resolves to a Session object\n * \n * @remarks\n * This method is called after successfully exchanging the authorization code\n * for tokens. You receive:\n * \n * Token Data:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (optional)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (for OIDC providers)\n * - scope: Granted scopes\n * \n * Provider Metadata:\n * - name: The provider name (e.g., 'google', 'github')\n * - endpoints: Provider endpoints (authorization, token, userInfo)\n * \n * The providerMetadata.endpoints.userInfo can be used with extractUserInfo():\n * ```typescript\n * import { extractUserInfo } from '@vunexa/lixa';\n * \n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * ```\n * \n * Your session strategy should:\n * 1. Extract user info (using extractUserInfo or decode ID token)\n * 2. Create or lookup users in your database\n * 3. Generate session identifiers\n * 4. Store session data as needed\n * 5. Return a Session object with token and raw data\n * \n * @throws \\{Error\\} If session creation fails (e.g., database error, invalid token)\n * \n * @example\n * Simple implementation:\n * ```typescript\n * async createSession(tokenData: OAuthTokenResponse): Promise<Session> {\n * return {\n * token: tokenData.access_token,\n * raw: tokenData\n * };\n * }\n * ```\n * \n * @example\n * Database integration with user info extraction:\n * ```typescript\n * async createSession(\n * tokenData: OAuthTokenResponse,\n * providerMetadata: ProviderMetadata\n * ): Promise<Session> {\n * // Extract user info from token or userinfo endpoint\n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * \n * // Create or update user in database\n * const user = await db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name\n * });\n * \n * return {\n * token: generateSessionId(),\n * raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }\n * };\n * }\n * ```\n */\n createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @param providerMetadata - Provider metadata (not used in default implementation)\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session> {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: tokenData.access_token,\n raw: tokenData,\n };\n }\n}","import type { OAuthTokenResponse } from \"../models/session\";\n\n/**\n * User information extracted from OAuth provider\n * \n * @public\n */\nexport interface UserInfo {\n email: string;\n id?: string | undefined;\n sub?: string | undefined;\n given_name?: string | undefined;\n family_name?: string | undefined;\n name?: string | undefined;\n picture?: string | undefined;\n email_verified?: boolean | undefined;\n iss?: string | undefined;\n}\n\n/**\n * Decode JWT ID token to extract user information\n * \n * @public\n */\nexport function decodeIdToken(idToken: string): UserInfo {\n const parts = idToken.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid ID token format: expected 3 parts separated by dots');\n }\n \n const base64Payload = parts[1];\n if (!base64Payload) {\n throw new Error('Invalid ID token: missing payload section');\n }\n const payload = Buffer.from(base64Payload, 'base64').toString();\n \n try {\n return JSON.parse(payload);\n } catch (error) {\n throw new Error('Invalid ID token: failed to parse payload JSON');\n }\n}\n\n/**\n * Determine OAuth provider from ID token issuer\n * \n * @public\n */\nexport function determineProviderFromIssuer(userInfo: UserInfo): string | null {\n if (!userInfo.iss) {\n return null;\n }\n \n const issuer = userInfo.iss.toLowerCase();\n \n if (issuer.includes('accounts.google.com')) {\n return 'google';\n }\n \n if (issuer.includes('github')) {\n return 'github';\n }\n \n // Unknown issuer\n return null;\n}\n\n/**\n * Fetch user info from OAuth provider's userinfo endpoint\n * \n * @param accessToken - OAuth access token\n * @param userInfoEndpoint - The provider's userinfo endpoint URL\n * @param providerName - Provider name for error messages (optional)\n * @returns User information from the provider\n * \n * @throws Error if the request fails or response is invalid\n * \n * @public\n */\nexport async function fetchUserInfo(\n accessToken: string, \n userInfoEndpoint: string,\n providerName?: string\n): Promise<UserInfo> {\n const response = await fetch(userInfoEndpoint, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n \n if (!response.ok) {\n const providerLabel = providerName ? ` from ${providerName}` : '';\n throw new Error(`Failed to fetch user info${providerLabel}: ${response.status} ${response.statusText}`);\n }\n \n const data = await response.json();\n if (!data || typeof data !== 'object' || !('email' in data) || typeof data.email !== 'string') {\n const providerLabel = providerName ? ` from ${providerName}` : '';\n throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);\n }\n \n return {\n email: data.email,\n id: 'id' in data ? String(data.id) : undefined,\n sub: 'sub' in data ? String(data.sub) : undefined,\n given_name: 'given_name' in data ? String(data.given_name) : undefined,\n family_name: 'family_name' in data ? String(data.family_name) : undefined,\n name: 'name' in data ? String(data.name) : undefined,\n picture: 'picture' in data ? String(data.picture) : undefined,\n email_verified: 'email_verified' in data ? Boolean(data.email_verified) : undefined,\n iss: 'iss' in data ? String(data.iss) : undefined,\n };\n}\n\n/**\n * Extract user info from OAuth token data\n * \n * @param tokenData - OAuth token response from provider\n * @param userInfoEndpoint - Optional userinfo endpoint URL (required if no ID token)\n * @param providerName - Optional provider name for error messages\n * @returns User info and detected provider name\n * \n * @remarks\n * This function attempts to extract user information in the following order:\n * 1. Decode ID token if present (preferred method)\n * 2. Fetch from userinfo endpoint using access token (requires userInfoEndpoint parameter)\n * \n * Provider detection:\n * - Primary: Extract from ID token issuer field\n * - Fallback: Use provider field in token data (if present)\n * - Fallback: Use providerName parameter\n * - Throws error if provider cannot be determined\n * \n * @throws Error if no ID token or access token is available\n * @throws Error if provider cannot be determined\n * @throws Error if userInfoEndpoint is required but not provided\n * \n * @example\n * With ID token (provider auto-detected):\n * ```typescript\n * const { userInfo, provider } = await extractUserInfo(tokenData);\n * console.log(`User ${userInfo.email} authenticated via ${provider}`);\n * ```\n * \n * @example\n * Without ID token (requires userInfoEndpoint):\n * ```typescript\n * const { userInfo, provider } = await extractUserInfo(\n * tokenData,\n * 'https://api.example.com/user',\n * 'custom'\n * );\n * ```\n * \n * @public\n */\nexport async function extractUserInfo(\n tokenData: OAuthTokenResponse,\n userInfoEndpoint?: string,\n providerName?: string\n): Promise<{ userInfo: UserInfo; provider: string }> {\n let userInfo: UserInfo;\n let provider: string | null = null;\n \n if (tokenData.id_token) {\n // Decode ID token to get user info\n userInfo = decodeIdToken(tokenData.id_token);\n \n // Try to determine provider from issuer\n provider = determineProviderFromIssuer(userInfo);\n \n // Fallback to provided provider name\n if (!provider && providerName) {\n provider = providerName;\n }\n } else if (tokenData.access_token) {\n // Without ID token, we need to know which provider to fetch from\n // Check if provider info is in the token data (custom field)\n if ('provider' in tokenData && typeof tokenData.provider === 'string') {\n provider = tokenData.provider;\n } else if (providerName) {\n provider = providerName;\n }\n \n if (!provider) {\n throw new Error(\n 'Cannot determine OAuth provider: No ID token with issuer information, ' +\n 'no provider field in token data, and no providerName provided. Unable to fetch user info.'\n );\n }\n \n if (!userInfoEndpoint) {\n throw new Error(\n `Cannot fetch user info for provider '${provider}': No ID token available and no userInfoEndpoint provided. ` +\n 'Either ensure the provider returns an ID token or provide the userInfoEndpoint parameter.'\n );\n }\n \n // Fetch user info from provider's API\n userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint, provider);\n } else {\n throw new Error('No ID token or access token available to fetch user info');\n }\n \n // Final provider validation\n if (!provider) {\n throw new Error(\n 'Cannot determine OAuth provider: ID token issuer not recognized and no provider name provided.'\n );\n }\n \n return { userInfo, provider };\n}\n"],"mappings":"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","names":["NodeCache","import_crypto","import_node_cache","NodeCache","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts","../src/utils/user-info.ts"],"sourcesContent":["/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library for backend applications.\n * \n * @remarks\n * This package simplifies multi-provider authentication flows (e.g., Google, GitHub), supports extensible session management, and enables custom provider registration.\n * \n * Key features:\n * - OAuth 2.0 authorization code flow with PKCE (RFC 6749, RFC 7636)\n * - OpenID Connect support\n * - Built-in providers available in \\@vunexa/lixa-providers\n * - Custom provider support via IProvider interface\n * - Extensible session management via SessionStrategy\n * - Pluggable state and session storage via StateDao and SessionDao\n * - TypeScript-first with comprehensive type safety\n * \n * @packageDocumentation\n */\n\nexport { Lixa } from \"./lixa\";\nexport {\n type ProviderConfig,\n type LixaConfig,\n type SafeLixaConfig,\n type IProvider,\n type SessionStrategy,\n type Session,\n type ProviderMetadata,\n type OAuthTokenResponse,\n type StateDao,\n type SessionDao,\n type StateData,\n} from \"./types\";\nexport { DefaultSessionStrategy } from \"./models/session\";\nexport {\n type UserInfo,\n extractUserInfo,\n decodeIdToken,\n fetchUserInfo,\n determineProviderFromIssuer,\n} from \"./utils/user-info\";\n","import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy, type OAuthTokenResponse, type ProviderMetadata } from \"./models/session\";\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n * Providers can be passed inline in the configuration, eliminating the need for pre-registration.\n *\n * @example\n * Using built-in providers from \\@vunexa/lixa-providers:\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa-providers';\n * \n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * ```\n * \n * @example\n * Using custom inline providers:\n * ```typescript\n * import { Lixa, IProvider } from '@vunexa/lixa';\n * \n * const customProvider: IProvider = {\n * authorizationEndpoint: 'https://custom.com/oauth/authorize',\n * tokenEndpoint: 'https://custom.com/oauth/token',\n * userInfoEndpoint: 'https://custom.com/api/user'\n * };\n * \n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: customProvider,\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/custom/callback',\n * scopes: ['read:user']\n * }\n * }\n * });\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<Record<string, ProviderConfig>> = LixaConfig> {\n private static DEFAULT_PROVIDERS: Map<string, IProvider> = new Map();\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map(); // Legacy registry for backward compatibility\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n * \n * @remarks\n * Providers can be passed inline in the configuration using the `provider` field.\n * Provider resolution priority: inline custom provider \\> default providers \\> legacy registry.\n * \n * @param config - The configuration object containing provider settings and optional session strategy\n * \n * @throws Error when provider configuration is missing required fields\n * @throws Error when provider implementation is missing required properties\n * @throws Error when provider is not available and no inline implementation is provided\n */\n constructor(config: TConfig) {\n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n \n this.log('INFO', 'Init', 'Initializing Lixa instance', { \n providers: Object.keys(config.providers),\n debug: this.debug \n });\n \n // Validate and extract providers from configuration\n for (const [providerName, providerConfig] of Object.entries(config.providers)) {\n const name = providerName.toLowerCase();\n const typedConfig: ProviderConfig = providerConfig;\n \n // Validate provider configuration has required credentials\n this.validateProviderConfig(providerName, typedConfig);\n \n // If provider config includes a custom provider implementation, validate it\n if (typedConfig.provider) {\n this.validateProviderImplementation(providerName, typedConfig.provider);\n this.log('INFO', 'Init', `Registered inline provider: ${providerName}`);\n } else {\n // Check if it's available in default providers or legacy registry\n if (!Lixa.DEFAULT_PROVIDERS.has(name) && !Lixa.CONFIGURED_PROVIDERS.has(name)) {\n this.log('ERROR', 'Init', `Provider '${providerName}' not available`);\n throw new Error(\n `Provider '${providerName}' is not available. ` +\n `Either import it from '@vunexa/lixa-providers' and include it in the configuration, ` +\n `or provide a custom implementation using the 'provider' field: ` +\n `{ provider: new CustomProvider(), clientId: '...', ... }`\n );\n }\n this.log('INFO', 'Init', `Using registered provider: ${providerName}`);\n }\n }\n \n this.log('INFO', 'Init', 'Lixa instance initialized successfully');\n }\n \n /**\n * Validates that a provider configuration has all required credentials.\n * \n * @param name - The provider name\n * @param config - The provider configuration\n * @throws Error when required fields are missing or invalid\n */\n private validateProviderConfig(name: string, config: ProviderConfig): void {\n const requiredFields: (keyof ProviderConfig)[] = ['clientId', 'clientSecret', 'redirectUri', 'scopes'];\n const missingFields = requiredFields.filter(field => {\n const value = config[field];\n return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');\n });\n \n if (missingFields.length > 0) {\n throw new Error(\n `Provider '${name}' configuration is missing required fields: ${missingFields.join(', ')}`\n );\n }\n \n // Validate scopes is an array\n if (!Array.isArray(config.scopes)) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' must be an array of strings`\n );\n }\n \n if (config.scopes.length === 0) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' array cannot be empty`\n );\n }\n }\n \n /**\n * Validates that a provider implementation has all required properties.\n * \n * @param name - The provider name\n * @param provider - The provider implementation\n * @throws Error when required properties are missing\n */\n private validateProviderImplementation(name: string, provider: IProvider): void {\n const requiredProps: (keyof IProvider)[] = ['authorizationEndpoint', 'tokenEndpoint', 'userInfoEndpoint'];\n const missingProps = requiredProps.filter(prop => {\n const value = provider[prop];\n return !value || typeof value !== 'string' || value.trim() === '';\n });\n \n if (missingProps.length > 0) {\n throw new Error(\n `Provider '${name}' implementation is missing required properties: ${missingProps.join(', ')}. ` +\n `All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`\n );\n }\n }\n\n /**\n * Structured debug logging with standardized format.\n * \n * @param level - Log level (INFO, WARN, ERROR)\n * @param context - Context of the log (Init, Auth, Token, Session, State)\n * @param message - Log message\n * @param data - Optional data to log\n * \n * @remarks\n * Format: [Lixa] [timestamp] [level] [context] message\n * Only logs when debug mode is enabled.\n */\n private log(level: 'INFO' | 'WARN' | 'ERROR', context: 'Init' | 'Auth' | 'Token' | 'Session' | 'State', message: string, data?: Record<string, string | number | boolean | string[]>): void {\n if (!this.debug) return;\n \n const timestamp = new Date().toISOString();\n const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;\n \n if (data !== undefined) {\n console.log(`${prefix} ${message}`, data);\n } else {\n console.log(`${prefix} ${message}`);\n }\n }\n\n /**\n * Checks if a provider is configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return this.config.providers.hasOwnProperty(providerType);\n }\n \n /**\n * Gets a provider implementation by name.\n * Resolution priority: inline custom provider \\> default providers \\> legacy registry\n * \n * @param name - The provider name (case-insensitive)\n * @param config - The provider configuration\n * @returns The provider implementation\n * @throws Error when provider is not found\n */\n private getProvider(name: string, config: ProviderConfig): IProvider {\n // First check if provider is inline in config\n if (config.provider) {\n return config.provider;\n }\n \n // Then check default providers\n const lowerName = name.toLowerCase();\n const defaultProvider = Lixa.DEFAULT_PROVIDERS.get(lowerName);\n if (defaultProvider) {\n return defaultProvider;\n }\n \n // Finally check legacy registry for backward compatibility\n const legacyProvider = Lixa.CONFIGURED_PROVIDERS.get(lowerName);\n if (legacyProvider) {\n return legacyProvider;\n }\n \n throw new Error(\n `Provider '${name}' not found. ` +\n `Ensure the provider is included in the configuration with a 'provider' field, ` +\n `or registered using Lixa.registerProvider().`\n );\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n * \n * @deprecated This method is maintained for backward compatibility.\n * The recommended approach is to pass providers inline in the configuration:\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: new CustomProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * Legacy usage (still supported):\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration.\n * \n * @deprecated This method is maintained for backward compatibility.\n * You can now pass configuration directly to the Lixa constructor without this helper.\n * \n * @param config - Configuration object with provider settings\n * @returns The same configuration object with type safety\n * \n * @example\n * New approach (recommended):\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<T> & { providers: T }\n ): LixaConfig<T> {\n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)\n *\n * @remarks\n * This method implements the code verifier generation as specified in RFC 7636 (PKCE).\n * \n * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that\n * prevents authorization code interception attacks. It's especially important for\n * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.\n * \n * **Generation methodology:**\n * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()\n * 2. Encode the bytes as a hexadecimal string (64 characters)\n * 3. The verifier is stored securely and used later in the token exchange\n * \n * **RFC 7636 Requirements:**\n * - Minimum length: 43 characters\n * - Maximum length: 128 characters\n * - Character set: [A-Z] / [a-z] / [0-9] / \"-\" / \".\" / \"_\" / \"~\"\n * - This implementation produces 64 hex characters, meeting the requirements\n * \n * The code verifier is:\n * - Generated when creating the authorization URL\n * - Stored in state cache with the state parameter\n * - Retrieved during callback handling\n * - Sent to the token endpoint to prove the client's identity\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see buildCodeChallenge for the corresponding challenge generation\n * \n * @internal\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n /**\n * Generates a code challenge from a code verifier for PKCE flows.\n *\n * @param codeVerifier - The code verifier string (64 hex characters)\n * @returns A base64url-encoded SHA-256 hash of the code verifier\n *\n * @remarks\n * This method implements the code challenge generation as specified in RFC 7636 (PKCE)\n * using the S256 (SHA-256) transformation method.\n * \n * **Challenge generation methodology:**\n * 1. Hash the code verifier using SHA-256\n * 2. Encode the hash as base64\n * 3. Convert to base64url format (RFC 4648):\n * - Replace '+' with '-'\n * - Replace '/' with '_'\n * - Remove trailing '=' padding\n * \n * **PKCE Flow:**\n * 1. Client generates code_verifier (random string)\n * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))\n * 3. Client sends code_challenge to authorization endpoint\n * 4. Authorization server stores the code_challenge\n * 5. Client sends code_verifier to token endpoint\n * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge\n * \n * **Security Benefits:**\n * - Prevents authorization code interception attacks\n * - Even if an attacker intercepts the authorization code, they cannot\n * exchange it for tokens without the original code_verifier\n * - The challenge is sent in the authorization request (public)\n * - The verifier is sent in the token request (should be kept secret)\n * \n * **RFC 7636 Transformation Methods:**\n * - plain: code_challenge = code_verifier (not recommended)\n * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}\n * @see generateCodeVerifier for the verifier generation\n * \n * @internal\n */\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url (RFC 4648 Section 5)\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n \n this.log('INFO', 'Auth', `Generating authorization URL for provider: ${providerType}`);\n \n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n this.log('INFO', 'State', `Saving state for provider: ${providerType}`, { state });\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n this.log('INFO', 'Auth', `Authorization URL generated successfully`, { \n provider: providerType,\n endpoint: providerImpl.authorizationEndpoint \n });\n\n return authUrl;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n const providerType = String(provider).toLowerCase();\n this.log('INFO', 'Auth', `Handling OAuth callback for provider: ${providerType}`);\n\n if (!code || code.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing authorization code in callback');\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing state in callback');\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n this.log('INFO', 'State', 'Validating state parameter', { state });\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n this.log('ERROR', 'State', 'State validation failed: state not found or expired', { state });\n throw new Error(\"Invalid or expired state\");\n }\n \n this.log('INFO', 'State', 'State validated successfully, removing from cache');\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n this.log('INFO', 'Token', `Exchanging authorization code for tokens`, { provider: providerType });\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n this.log('INFO', 'Token', 'Token exchange successful');\n this.log('INFO', 'Session', 'Creating user session');\n \n // Create provider metadata for session strategy\n const providerMetadata: ProviderMetadata = {\n name: providerType,\n endpoints: {\n authorization: providerImpl.authorizationEndpoint,\n token: providerImpl.tokenEndpoint,\n userInfo: providerImpl.userInfoEndpoint\n }\n };\n \n const session = await this.sessionStrategy.createSession(tokens, providerMetadata);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n this.log('INFO', 'Session', 'Storing session', { sessionId });\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n this.log('INFO', 'Session', 'Session created successfully', { sessionId });\n\n return sessionId;\n }\n\n public async fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return await this.sesionDao.getSession<Session>(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<OAuthTokenResponse> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('INFO', 'Token', 'Sending token exchange request', { \n endpoint: providerImpl.tokenEndpoint \n });\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.log('ERROR', 'Token', 'Token exchange failed', { \n status: response.status, \n statusText: response.statusText,\n error: errorBody \n });\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n this.log('INFO', 'Token', 'Token exchange response received successfully');\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n // Use Object.entries to safely iterate and find the provider\n for (const [key, value] of Object.entries(this.config.providers)) {\n if (key.toLowerCase() === providerType.toLowerCase()) {\n return value;\n }\n }\n return undefined;\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao, StateData } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<StateData | null> {\n return this.cache.get<StateData>(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\nimport type { Session } from \"../models/session\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession<T extends Session>(state: string, data: T, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession<T extends Session>(state: string): Promise<T | null> {\n return this.cache.get<T>(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * OAuth 2.0 token response structure.\n * Based on RFC 6749 Section 5.1 and OpenID Connect Core 1.0 Section 3.1.3.3\n * \n * @remarks\n * This interface represents the standard OAuth 2.0 token response with\n * optional OpenID Connect extensions. All OAuth providers should return\n * at minimum the required fields (access_token, token_type).\n * \n * @public\n */\nexport interface OAuthTokenResponse {\n /** \n * OAuth 2.0 access token (required).\n * Used to access protected resources on behalf of the user.\n */\n access_token: string;\n \n /** \n * Token type (required).\n * Typically \"Bearer\" for OAuth 2.0.\n */\n token_type: string;\n \n /** \n * Token expiration time in seconds (optional).\n * Time until the access token expires.\n */\n expires_in?: number;\n \n /** \n * OAuth 2.0 refresh token (optional).\n * Used to obtain new access tokens without re-authentication.\n */\n refresh_token?: string;\n \n /** \n * Granted OAuth scopes (optional).\n * Space-separated list of scopes that were granted.\n */\n scope?: string;\n \n /** \n * OpenID Connect ID token (optional).\n * JWT containing user identity claims (only present for OIDC providers).\n */\n id_token?: string;\n \n /**\n * Additional provider-specific fields.\n * Some providers may include extra fields like user_id, account_id, etc.\n */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Represents a user session after successful OAuth authentication.\n * \n * @remarks\n * The Session object is returned by SessionStrategy.createSession() and contains\n * the session identifier and any additional data needed for your application.\n * \n * The structure is intentionally flexible to support various session management\n * approaches (JWT tokens, session IDs, etc.).\n *\n * @public\n */\nexport interface Session<TRaw = OAuthTokenResponse> {\n /** \n * The session token or identifier.\n * This could be an access token, a session ID, a JWT, or any other identifier\n * that your application uses to track authenticated users.\n */\n token: string;\n \n /** \n * Raw session data.\n * Contains the complete OAuth token response and any additional data\n * your SessionStrategy adds (user info, database IDs, etc.).\n * \n * Typical OAuth token data includes:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (if requested)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (if using OIDC)\n * - scope: Granted scopes\n */\n raw: TRaw;\n}\n\n/**\n * Provider metadata passed to session strategy.\n * Contains provider name and endpoints for user info extraction.\n * \n * @public\n */\nexport interface ProviderMetadata {\n /** The provider name (e.g., 'google', 'github') */\n name: string;\n \n /** Provider endpoints */\n endpoints: {\n /** Authorization endpoint URL */\n authorization: string;\n /** Token endpoint URL */\n token: string;\n /** UserInfo endpoint URL */\n userInfo: string;\n };\n}\n\n/**\n * Strategy interface for custom session creation.\n * \n * @remarks\n * Implement this interface to customize how OAuth tokens are converted into\n * application sessions. This is where you typically:\n * - Decode ID tokens (for OpenID Connect)\n * - Look up or create users in your database\n * - Generate session identifiers\n * - Store session data\n * - Add custom claims or metadata\n * \n * The default implementation (DefaultSessionStrategy) simply extracts the\n * access token and returns it as the session token.\n * \n * @example\n * Custom session strategy with database integration:\n * ```typescript\n * interface CustomSessionData {\n * userId: string;\n * email: string;\n * provider: string;\n * accessToken: string;\n * refreshToken?: string;\n * expiresAt: number;\n * }\n * \n * class DatabaseSessionStrategy implements SessionStrategy {\n * constructor(private db: Database) {}\n * \n * async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {\n * // User info is already extracted by Lixa!\n * const { userInfo, provider, tokenData } = oauthContext;\n * \n * // Create or update user in database\n * const user = await this.db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name,\n * picture: userInfo.picture\n * });\n * \n * // Generate session ID\n * const sessionId = generateSecureId();\n * \n * // Store session with tokens\n * await this.db.sessions.create({\n * id: sessionId,\n * userId: user.id,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: new Date(Date.now() + (tokenData.expires_in || 3600) * 1000)\n * });\n * \n * return {\n * token: sessionId,\n * raw: {\n * userId: user.id,\n * email: user.email,\n * provider,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000\n * }\n * };\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * \n * @param tokenData - The token data received from the OAuth provider's token endpoint\n * @param providerMetadata - Provider metadata including name and endpoints\n * @returns A Promise that resolves to a Session object\n * \n * @remarks\n * This method is called after successfully exchanging the authorization code\n * for tokens. You receive:\n * \n * Token Data:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (optional)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (for OIDC providers)\n * - scope: Granted scopes\n * \n * Provider Metadata:\n * - name: The provider name (e.g., 'google', 'github')\n * - endpoints: Provider endpoints (authorization, token, userInfo)\n * \n * The providerMetadata.endpoints.userInfo can be used with extractUserInfo():\n * ```typescript\n * import { extractUserInfo } from '@vunexa/lixa';\n * \n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * ```\n * \n * Your session strategy should:\n * 1. Extract user info (using extractUserInfo or decode ID token)\n * 2. Create or lookup users in your database\n * 3. Generate session identifiers\n * 4. Store session data as needed\n * 5. Return a Session object with token and raw data\n * \n * @throws \\{Error\\} If session creation fails (e.g., database error, invalid token)\n * \n * @example\n * Simple implementation:\n * ```typescript\n * async createSession(tokenData: OAuthTokenResponse): Promise<Session> {\n * return {\n * token: tokenData.access_token,\n * raw: tokenData\n * };\n * }\n * ```\n * \n * @example\n * Database integration with user info extraction:\n * ```typescript\n * async createSession(\n * tokenData: OAuthTokenResponse,\n * providerMetadata: ProviderMetadata\n * ): Promise<Session> {\n * // Extract user info from token or userinfo endpoint\n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * \n * // Create or update user in database\n * const user = await db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name\n * });\n * \n * return {\n * token: generateSessionId(),\n * raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }\n * };\n * }\n * ```\n */\n createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @param providerMetadata - Provider metadata (not used in default implementation)\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session> {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: tokenData.access_token,\n raw: tokenData,\n };\n }\n}","import type { OAuthTokenResponse, ProviderMetadata } from \"../models/session\";\n\n/**\n * User information extracted from OAuth provider\n * \n * @public\n */\nexport interface UserInfo {\n email: string;\n id?: string | undefined;\n sub?: string | undefined;\n given_name?: string | undefined;\n family_name?: string | undefined;\n name?: string | undefined;\n picture?: string | undefined;\n email_verified?: boolean | undefined;\n iss?: string | undefined;\n}\n\n/**\n * Decode JWT ID token to extract user information\n * \n * @public\n */\nexport function decodeIdToken(idToken: string): UserInfo {\n const parts = idToken.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid ID token format: expected 3 parts separated by dots');\n }\n \n const base64Payload = parts[1];\n if (!base64Payload) {\n throw new Error('Invalid ID token: missing payload section');\n }\n const payload = Buffer.from(base64Payload, 'base64').toString();\n \n try {\n return JSON.parse(payload);\n } catch (error) {\n throw new Error('Invalid ID token: failed to parse payload JSON');\n }\n}\n\n/**\n * Determine OAuth provider from ID token issuer\n * \n * @public\n */\nexport function determineProviderFromIssuer(userInfo: UserInfo): string | null {\n if (!userInfo.iss) {\n return null;\n }\n \n const issuer = userInfo.iss.toLowerCase();\n \n if (issuer.includes('accounts.google.com')) {\n return 'google';\n }\n \n if (issuer.includes('github')) {\n return 'github';\n }\n \n // Unknown issuer\n return null;\n}\n\n/**\n * Fetch user info from OAuth provider's userinfo endpoint\n * \n * @param accessToken - OAuth access token\n * @param userInfoEndpoint - The provider's userinfo endpoint URL\n * @param providerName - Provider name for error messages (optional)\n * @returns User information from the provider\n * \n * @throws Error if the request fails or response is invalid\n * \n * @public\n */\nexport async function fetchUserInfo(\n accessToken: string, \n userInfoEndpoint: string\n): Promise<UserInfo> {\n const response = await fetch(userInfoEndpoint, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n \n if (!response.ok) {\n throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);\n }\n \n const data = await response.json();\n if (!data || typeof data !== 'object' || !('email' in data) || typeof data.email !== 'string') {\n throw new Error(`Invalid user info response: missing or invalid email`);\n }\n \n return {\n email: data.email,\n id: 'id' in data ? String(data.id) : undefined,\n sub: 'sub' in data ? String(data.sub) : undefined,\n given_name: 'given_name' in data ? String(data.given_name) : undefined,\n family_name: 'family_name' in data ? String(data.family_name) : undefined,\n name: 'name' in data ? String(data.name) : undefined,\n picture: 'picture' in data ? String(data.picture) : undefined,\n email_verified: 'email_verified' in data ? Boolean(data.email_verified) : undefined,\n iss: 'iss' in data ? String(data.iss) : undefined,\n };\n}\n\n/**\n * Extract user info from OAuth token data\n * \n * @param tokenData - OAuth token response from provider\n * @param providerMetadata - Provider metadata containing endpoints configuration\n * @returns User info extracted from token or fetched from provider\n * \n * @remarks\n * This function attempts to extract user information in the following order:\n * 1. Decode ID token if present (preferred method for OIDC providers)\n * 2. Fetch from userinfo endpoint using access token (uses providerMetadata.endpoints.userInfo)\n * \n * The function automatically determines the best method based on available token data.\n * For OIDC providers (like Google), it decodes the JWT ID token.\n * For OAuth-only providers (like GitHub), it fetches from the userinfo endpoint.\n * \n * @throws Error if no ID token or access token is available\n * @throws Error if userinfo endpoint is required but not provided in providerMetadata\n * \n * @example\n * With ID token (OIDC provider like Google):\n * ```typescript\n * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);\n * console.log(`User ${userInfo.email} authenticated`);\n * ```\n * \n * @example\n * Without ID token (OAuth provider like GitHub):\n * ```typescript\n * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);\n * // Automatically fetches from providerMetadata.endpoints.userInfo\n * console.log(`User ${userInfo.email} authenticated`);\n * ```\n * \n * @public\n */\nexport async function extractUserInfo(\n tokenData: OAuthTokenResponse,\n providerMetadata: ProviderMetadata\n): Promise<{ userInfo: UserInfo }> {\n let userInfo: UserInfo;\n const userInfoEndpoint = providerMetadata.endpoints.userInfo;\n \n if (tokenData.id_token) {\n // Decode ID token to get user info\n userInfo = decodeIdToken(tokenData.id_token);\n } else if (tokenData.access_token) {\n // Fetch user info from provider's API\n userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);\n } else {\n throw new Error('No ID token or access token available to fetch user info');\n }\n \n return { userInfo };\n}\n"],"mappings":"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","names":["NodeCache","import_crypto","import_node_cache","NodeCache","crypto"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1185,52 +1185,45 @@ declare function determineProviderFromIssuer(userInfo: UserInfo): string | null;
|
|
|
1185
1185
|
*
|
|
1186
1186
|
* @public
|
|
1187
1187
|
*/
|
|
1188
|
-
declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string
|
|
1188
|
+
declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string): Promise<UserInfo>;
|
|
1189
1189
|
/**
|
|
1190
1190
|
* Extract user info from OAuth token data
|
|
1191
1191
|
*
|
|
1192
1192
|
* @param tokenData - OAuth token response from provider
|
|
1193
|
-
* @param
|
|
1194
|
-
* @
|
|
1195
|
-
* @returns User info and detected provider name
|
|
1193
|
+
* @param providerMetadata - Provider metadata containing endpoints configuration
|
|
1194
|
+
* @returns User info extracted from token or fetched from provider
|
|
1196
1195
|
*
|
|
1197
1196
|
* @remarks
|
|
1198
1197
|
* This function attempts to extract user information in the following order:
|
|
1199
|
-
* 1. Decode ID token if present (preferred method)
|
|
1200
|
-
* 2. Fetch from userinfo endpoint using access token (
|
|
1198
|
+
* 1. Decode ID token if present (preferred method for OIDC providers)
|
|
1199
|
+
* 2. Fetch from userinfo endpoint using access token (uses providerMetadata.endpoints.userInfo)
|
|
1201
1200
|
*
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
* -
|
|
1205
|
-
* - Fallback: Use providerName parameter
|
|
1206
|
-
* - Throws error if provider cannot be determined
|
|
1201
|
+
* The function automatically determines the best method based on available token data.
|
|
1202
|
+
* For OIDC providers (like Google), it decodes the JWT ID token.
|
|
1203
|
+
* For OAuth-only providers (like GitHub), it fetches from the userinfo endpoint.
|
|
1207
1204
|
*
|
|
1208
1205
|
* @throws Error if no ID token or access token is available
|
|
1209
|
-
* @throws Error if
|
|
1210
|
-
* @throws Error if userInfoEndpoint is required but not provided
|
|
1206
|
+
* @throws Error if userinfo endpoint is required but not provided in providerMetadata
|
|
1211
1207
|
*
|
|
1212
1208
|
* @example
|
|
1213
|
-
* With ID token (provider
|
|
1209
|
+
* With ID token (OIDC provider like Google):
|
|
1214
1210
|
* ```typescript
|
|
1215
|
-
* const { userInfo
|
|
1216
|
-
* console.log(`User ${userInfo.email} authenticated
|
|
1211
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
1212
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
1217
1213
|
* ```
|
|
1218
1214
|
*
|
|
1219
1215
|
* @example
|
|
1220
|
-
* Without ID token (
|
|
1216
|
+
* Without ID token (OAuth provider like GitHub):
|
|
1221
1217
|
* ```typescript
|
|
1222
|
-
* const { userInfo
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1225
|
-
* 'custom'
|
|
1226
|
-
* );
|
|
1218
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
1219
|
+
* // Automatically fetches from providerMetadata.endpoints.userInfo
|
|
1220
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
1227
1221
|
* ```
|
|
1228
1222
|
*
|
|
1229
1223
|
* @public
|
|
1230
1224
|
*/
|
|
1231
|
-
declare function extractUserInfo(tokenData: OAuthTokenResponse,
|
|
1225
|
+
declare function extractUserInfo(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<{
|
|
1232
1226
|
userInfo: UserInfo;
|
|
1233
|
-
provider: string;
|
|
1234
1227
|
}>;
|
|
1235
1228
|
|
|
1236
1229
|
export { DefaultSessionStrategy, type IProvider, Lixa, type LixaConfig, type OAuthTokenResponse, type ProviderConfig, type ProviderMetadata, type SafeLixaConfig, type Session, type SessionDao, type SessionStrategy, type StateDao, type StateData, type UserInfo, decodeIdToken, determineProviderFromIssuer, extractUserInfo, fetchUserInfo };
|
package/dist/index.js
CHANGED
|
@@ -604,7 +604,7 @@ function determineProviderFromIssuer(userInfo) {
|
|
|
604
604
|
}
|
|
605
605
|
return null;
|
|
606
606
|
}
|
|
607
|
-
async function fetchUserInfo(accessToken, userInfoEndpoint
|
|
607
|
+
async function fetchUserInfo(accessToken, userInfoEndpoint) {
|
|
608
608
|
const response = await fetch(userInfoEndpoint, {
|
|
609
609
|
headers: {
|
|
610
610
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -612,13 +612,11 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
|
|
|
612
612
|
}
|
|
613
613
|
});
|
|
614
614
|
if (!response.ok) {
|
|
615
|
-
|
|
616
|
-
throw new Error(`Failed to fetch user info${providerLabel}: ${response.status} ${response.statusText}`);
|
|
615
|
+
throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);
|
|
617
616
|
}
|
|
618
617
|
const data = await response.json();
|
|
619
618
|
if (!data || typeof data !== "object" || !("email" in data) || typeof data.email !== "string") {
|
|
620
|
-
|
|
621
|
-
throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);
|
|
619
|
+
throw new Error(`Invalid user info response: missing or invalid email`);
|
|
622
620
|
}
|
|
623
621
|
return {
|
|
624
622
|
email: data.email,
|
|
@@ -632,41 +630,17 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
|
|
|
632
630
|
iss: "iss" in data ? String(data.iss) : void 0
|
|
633
631
|
};
|
|
634
632
|
}
|
|
635
|
-
async function extractUserInfo(tokenData,
|
|
633
|
+
async function extractUserInfo(tokenData, providerMetadata) {
|
|
636
634
|
let userInfo;
|
|
637
|
-
|
|
635
|
+
const userInfoEndpoint = providerMetadata.endpoints.userInfo;
|
|
638
636
|
if (tokenData.id_token) {
|
|
639
637
|
userInfo = decodeIdToken(tokenData.id_token);
|
|
640
|
-
provider = determineProviderFromIssuer(userInfo);
|
|
641
|
-
if (!provider && providerName) {
|
|
642
|
-
provider = providerName;
|
|
643
|
-
}
|
|
644
638
|
} else if (tokenData.access_token) {
|
|
645
|
-
|
|
646
|
-
provider = tokenData.provider;
|
|
647
|
-
} else if (providerName) {
|
|
648
|
-
provider = providerName;
|
|
649
|
-
}
|
|
650
|
-
if (!provider) {
|
|
651
|
-
throw new Error(
|
|
652
|
-
"Cannot determine OAuth provider: No ID token with issuer information, no provider field in token data, and no providerName provided. Unable to fetch user info."
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
if (!userInfoEndpoint) {
|
|
656
|
-
throw new Error(
|
|
657
|
-
`Cannot fetch user info for provider '${provider}': No ID token available and no userInfoEndpoint provided. Either ensure the provider returns an ID token or provide the userInfoEndpoint parameter.`
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint, provider);
|
|
639
|
+
userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
|
|
661
640
|
} else {
|
|
662
641
|
throw new Error("No ID token or access token available to fetch user info");
|
|
663
642
|
}
|
|
664
|
-
|
|
665
|
-
throw new Error(
|
|
666
|
-
"Cannot determine OAuth provider: ID token issuer not recognized and no provider name provided."
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
return { userInfo, provider };
|
|
643
|
+
return { userInfo };
|
|
670
644
|
}
|
|
671
645
|
export {
|
|
672
646
|
DefaultSessionStrategy,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts","../src/utils/user-info.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy, type OAuthTokenResponse, type ProviderMetadata } from \"./models/session\";\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n * Providers can be passed inline in the configuration, eliminating the need for pre-registration.\n *\n * @example\n * Using built-in providers from \\@vunexa/lixa-providers:\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa-providers';\n * \n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * ```\n * \n * @example\n * Using custom inline providers:\n * ```typescript\n * import { Lixa, IProvider } from '@vunexa/lixa';\n * \n * const customProvider: IProvider = {\n * authorizationEndpoint: 'https://custom.com/oauth/authorize',\n * tokenEndpoint: 'https://custom.com/oauth/token',\n * userInfoEndpoint: 'https://custom.com/api/user'\n * };\n * \n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: customProvider,\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/custom/callback',\n * scopes: ['read:user']\n * }\n * }\n * });\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<Record<string, ProviderConfig>> = LixaConfig> {\n private static DEFAULT_PROVIDERS: Map<string, IProvider> = new Map();\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map(); // Legacy registry for backward compatibility\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n * \n * @remarks\n * Providers can be passed inline in the configuration using the `provider` field.\n * Provider resolution priority: inline custom provider \\> default providers \\> legacy registry.\n * \n * @param config - The configuration object containing provider settings and optional session strategy\n * \n * @throws Error when provider configuration is missing required fields\n * @throws Error when provider implementation is missing required properties\n * @throws Error when provider is not available and no inline implementation is provided\n */\n constructor(config: TConfig) {\n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n \n this.log('INFO', 'Init', 'Initializing Lixa instance', { \n providers: Object.keys(config.providers),\n debug: this.debug \n });\n \n // Validate and extract providers from configuration\n for (const [providerName, providerConfig] of Object.entries(config.providers)) {\n const name = providerName.toLowerCase();\n const typedConfig: ProviderConfig = providerConfig;\n \n // Validate provider configuration has required credentials\n this.validateProviderConfig(providerName, typedConfig);\n \n // If provider config includes a custom provider implementation, validate it\n if (typedConfig.provider) {\n this.validateProviderImplementation(providerName, typedConfig.provider);\n this.log('INFO', 'Init', `Registered inline provider: ${providerName}`);\n } else {\n // Check if it's available in default providers or legacy registry\n if (!Lixa.DEFAULT_PROVIDERS.has(name) && !Lixa.CONFIGURED_PROVIDERS.has(name)) {\n this.log('ERROR', 'Init', `Provider '${providerName}' not available`);\n throw new Error(\n `Provider '${providerName}' is not available. ` +\n `Either import it from '@vunexa/lixa-providers' and include it in the configuration, ` +\n `or provide a custom implementation using the 'provider' field: ` +\n `{ provider: new CustomProvider(), clientId: '...', ... }`\n );\n }\n this.log('INFO', 'Init', `Using registered provider: ${providerName}`);\n }\n }\n \n this.log('INFO', 'Init', 'Lixa instance initialized successfully');\n }\n \n /**\n * Validates that a provider configuration has all required credentials.\n * \n * @param name - The provider name\n * @param config - The provider configuration\n * @throws Error when required fields are missing or invalid\n */\n private validateProviderConfig(name: string, config: ProviderConfig): void {\n const requiredFields: (keyof ProviderConfig)[] = ['clientId', 'clientSecret', 'redirectUri', 'scopes'];\n const missingFields = requiredFields.filter(field => {\n const value = config[field];\n return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');\n });\n \n if (missingFields.length > 0) {\n throw new Error(\n `Provider '${name}' configuration is missing required fields: ${missingFields.join(', ')}`\n );\n }\n \n // Validate scopes is an array\n if (!Array.isArray(config.scopes)) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' must be an array of strings`\n );\n }\n \n if (config.scopes.length === 0) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' array cannot be empty`\n );\n }\n }\n \n /**\n * Validates that a provider implementation has all required properties.\n * \n * @param name - The provider name\n * @param provider - The provider implementation\n * @throws Error when required properties are missing\n */\n private validateProviderImplementation(name: string, provider: IProvider): void {\n const requiredProps: (keyof IProvider)[] = ['authorizationEndpoint', 'tokenEndpoint', 'userInfoEndpoint'];\n const missingProps = requiredProps.filter(prop => {\n const value = provider[prop];\n return !value || typeof value !== 'string' || value.trim() === '';\n });\n \n if (missingProps.length > 0) {\n throw new Error(\n `Provider '${name}' implementation is missing required properties: ${missingProps.join(', ')}. ` +\n `All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`\n );\n }\n }\n\n /**\n * Structured debug logging with standardized format.\n * \n * @param level - Log level (INFO, WARN, ERROR)\n * @param context - Context of the log (Init, Auth, Token, Session, State)\n * @param message - Log message\n * @param data - Optional data to log\n * \n * @remarks\n * Format: [Lixa] [timestamp] [level] [context] message\n * Only logs when debug mode is enabled.\n */\n private log(level: 'INFO' | 'WARN' | 'ERROR', context: 'Init' | 'Auth' | 'Token' | 'Session' | 'State', message: string, data?: Record<string, string | number | boolean | string[]>): void {\n if (!this.debug) return;\n \n const timestamp = new Date().toISOString();\n const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;\n \n if (data !== undefined) {\n console.log(`${prefix} ${message}`, data);\n } else {\n console.log(`${prefix} ${message}`);\n }\n }\n\n /**\n * Checks if a provider is configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return this.config.providers.hasOwnProperty(providerType);\n }\n \n /**\n * Gets a provider implementation by name.\n * Resolution priority: inline custom provider \\> default providers \\> legacy registry\n * \n * @param name - The provider name (case-insensitive)\n * @param config - The provider configuration\n * @returns The provider implementation\n * @throws Error when provider is not found\n */\n private getProvider(name: string, config: ProviderConfig): IProvider {\n // First check if provider is inline in config\n if (config.provider) {\n return config.provider;\n }\n \n // Then check default providers\n const lowerName = name.toLowerCase();\n const defaultProvider = Lixa.DEFAULT_PROVIDERS.get(lowerName);\n if (defaultProvider) {\n return defaultProvider;\n }\n \n // Finally check legacy registry for backward compatibility\n const legacyProvider = Lixa.CONFIGURED_PROVIDERS.get(lowerName);\n if (legacyProvider) {\n return legacyProvider;\n }\n \n throw new Error(\n `Provider '${name}' not found. ` +\n `Ensure the provider is included in the configuration with a 'provider' field, ` +\n `or registered using Lixa.registerProvider().`\n );\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n * \n * @deprecated This method is maintained for backward compatibility.\n * The recommended approach is to pass providers inline in the configuration:\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: new CustomProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * Legacy usage (still supported):\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration.\n * \n * @deprecated This method is maintained for backward compatibility.\n * You can now pass configuration directly to the Lixa constructor without this helper.\n * \n * @param config - Configuration object with provider settings\n * @returns The same configuration object with type safety\n * \n * @example\n * New approach (recommended):\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<T> & { providers: T }\n ): LixaConfig<T> {\n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)\n *\n * @remarks\n * This method implements the code verifier generation as specified in RFC 7636 (PKCE).\n * \n * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that\n * prevents authorization code interception attacks. It's especially important for\n * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.\n * \n * **Generation methodology:**\n * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()\n * 2. Encode the bytes as a hexadecimal string (64 characters)\n * 3. The verifier is stored securely and used later in the token exchange\n * \n * **RFC 7636 Requirements:**\n * - Minimum length: 43 characters\n * - Maximum length: 128 characters\n * - Character set: [A-Z] / [a-z] / [0-9] / \"-\" / \".\" / \"_\" / \"~\"\n * - This implementation produces 64 hex characters, meeting the requirements\n * \n * The code verifier is:\n * - Generated when creating the authorization URL\n * - Stored in state cache with the state parameter\n * - Retrieved during callback handling\n * - Sent to the token endpoint to prove the client's identity\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see buildCodeChallenge for the corresponding challenge generation\n * \n * @internal\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n /**\n * Generates a code challenge from a code verifier for PKCE flows.\n *\n * @param codeVerifier - The code verifier string (64 hex characters)\n * @returns A base64url-encoded SHA-256 hash of the code verifier\n *\n * @remarks\n * This method implements the code challenge generation as specified in RFC 7636 (PKCE)\n * using the S256 (SHA-256) transformation method.\n * \n * **Challenge generation methodology:**\n * 1. Hash the code verifier using SHA-256\n * 2. Encode the hash as base64\n * 3. Convert to base64url format (RFC 4648):\n * - Replace '+' with '-'\n * - Replace '/' with '_'\n * - Remove trailing '=' padding\n * \n * **PKCE Flow:**\n * 1. Client generates code_verifier (random string)\n * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))\n * 3. Client sends code_challenge to authorization endpoint\n * 4. Authorization server stores the code_challenge\n * 5. Client sends code_verifier to token endpoint\n * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge\n * \n * **Security Benefits:**\n * - Prevents authorization code interception attacks\n * - Even if an attacker intercepts the authorization code, they cannot\n * exchange it for tokens without the original code_verifier\n * - The challenge is sent in the authorization request (public)\n * - The verifier is sent in the token request (should be kept secret)\n * \n * **RFC 7636 Transformation Methods:**\n * - plain: code_challenge = code_verifier (not recommended)\n * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}\n * @see generateCodeVerifier for the verifier generation\n * \n * @internal\n */\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url (RFC 4648 Section 5)\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n \n this.log('INFO', 'Auth', `Generating authorization URL for provider: ${providerType}`);\n \n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n this.log('INFO', 'State', `Saving state for provider: ${providerType}`, { state });\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n this.log('INFO', 'Auth', `Authorization URL generated successfully`, { \n provider: providerType,\n endpoint: providerImpl.authorizationEndpoint \n });\n\n return authUrl;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n const providerType = String(provider).toLowerCase();\n this.log('INFO', 'Auth', `Handling OAuth callback for provider: ${providerType}`);\n\n if (!code || code.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing authorization code in callback');\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing state in callback');\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n this.log('INFO', 'State', 'Validating state parameter', { state });\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n this.log('ERROR', 'State', 'State validation failed: state not found or expired', { state });\n throw new Error(\"Invalid or expired state\");\n }\n \n this.log('INFO', 'State', 'State validated successfully, removing from cache');\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n this.log('INFO', 'Token', `Exchanging authorization code for tokens`, { provider: providerType });\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n this.log('INFO', 'Token', 'Token exchange successful');\n this.log('INFO', 'Session', 'Creating user session');\n \n // Create provider metadata for session strategy\n const providerMetadata: ProviderMetadata = {\n name: providerType,\n endpoints: {\n authorization: providerImpl.authorizationEndpoint,\n token: providerImpl.tokenEndpoint,\n userInfo: providerImpl.userInfoEndpoint\n }\n };\n \n const session = await this.sessionStrategy.createSession(tokens, providerMetadata);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n this.log('INFO', 'Session', 'Storing session', { sessionId });\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n this.log('INFO', 'Session', 'Session created successfully', { sessionId });\n\n return sessionId;\n }\n\n public async fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return await this.sesionDao.getSession<Session>(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<OAuthTokenResponse> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('INFO', 'Token', 'Sending token exchange request', { \n endpoint: providerImpl.tokenEndpoint \n });\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.log('ERROR', 'Token', 'Token exchange failed', { \n status: response.status, \n statusText: response.statusText,\n error: errorBody \n });\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n this.log('INFO', 'Token', 'Token exchange response received successfully');\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n // Use Object.entries to safely iterate and find the provider\n for (const [key, value] of Object.entries(this.config.providers)) {\n if (key.toLowerCase() === providerType.toLowerCase()) {\n return value;\n }\n }\n return undefined;\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao, StateData } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<StateData | null> {\n return this.cache.get<StateData>(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\nimport type { Session } from \"../models/session\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession<T extends Session>(state: string, data: T, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession<T extends Session>(state: string): Promise<T | null> {\n return this.cache.get<T>(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * OAuth 2.0 token response structure.\n * Based on RFC 6749 Section 5.1 and OpenID Connect Core 1.0 Section 3.1.3.3\n * \n * @remarks\n * This interface represents the standard OAuth 2.0 token response with\n * optional OpenID Connect extensions. All OAuth providers should return\n * at minimum the required fields (access_token, token_type).\n * \n * @public\n */\nexport interface OAuthTokenResponse {\n /** \n * OAuth 2.0 access token (required).\n * Used to access protected resources on behalf of the user.\n */\n access_token: string;\n \n /** \n * Token type (required).\n * Typically \"Bearer\" for OAuth 2.0.\n */\n token_type: string;\n \n /** \n * Token expiration time in seconds (optional).\n * Time until the access token expires.\n */\n expires_in?: number;\n \n /** \n * OAuth 2.0 refresh token (optional).\n * Used to obtain new access tokens without re-authentication.\n */\n refresh_token?: string;\n \n /** \n * Granted OAuth scopes (optional).\n * Space-separated list of scopes that were granted.\n */\n scope?: string;\n \n /** \n * OpenID Connect ID token (optional).\n * JWT containing user identity claims (only present for OIDC providers).\n */\n id_token?: string;\n \n /**\n * Additional provider-specific fields.\n * Some providers may include extra fields like user_id, account_id, etc.\n */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Represents a user session after successful OAuth authentication.\n * \n * @remarks\n * The Session object is returned by SessionStrategy.createSession() and contains\n * the session identifier and any additional data needed for your application.\n * \n * The structure is intentionally flexible to support various session management\n * approaches (JWT tokens, session IDs, etc.).\n *\n * @public\n */\nexport interface Session<TRaw = OAuthTokenResponse> {\n /** \n * The session token or identifier.\n * This could be an access token, a session ID, a JWT, or any other identifier\n * that your application uses to track authenticated users.\n */\n token: string;\n \n /** \n * Raw session data.\n * Contains the complete OAuth token response and any additional data\n * your SessionStrategy adds (user info, database IDs, etc.).\n * \n * Typical OAuth token data includes:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (if requested)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (if using OIDC)\n * - scope: Granted scopes\n */\n raw: TRaw;\n}\n\n/**\n * Provider metadata passed to session strategy.\n * Contains provider name and endpoints for user info extraction.\n * \n * @public\n */\nexport interface ProviderMetadata {\n /** The provider name (e.g., 'google', 'github') */\n name: string;\n \n /** Provider endpoints */\n endpoints: {\n /** Authorization endpoint URL */\n authorization: string;\n /** Token endpoint URL */\n token: string;\n /** UserInfo endpoint URL */\n userInfo: string;\n };\n}\n\n/**\n * Strategy interface for custom session creation.\n * \n * @remarks\n * Implement this interface to customize how OAuth tokens are converted into\n * application sessions. This is where you typically:\n * - Decode ID tokens (for OpenID Connect)\n * - Look up or create users in your database\n * - Generate session identifiers\n * - Store session data\n * - Add custom claims or metadata\n * \n * The default implementation (DefaultSessionStrategy) simply extracts the\n * access token and returns it as the session token.\n * \n * @example\n * Custom session strategy with database integration:\n * ```typescript\n * interface CustomSessionData {\n * userId: string;\n * email: string;\n * provider: string;\n * accessToken: string;\n * refreshToken?: string;\n * expiresAt: number;\n * }\n * \n * class DatabaseSessionStrategy implements SessionStrategy {\n * constructor(private db: Database) {}\n * \n * async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {\n * // User info is already extracted by Lixa!\n * const { userInfo, provider, tokenData } = oauthContext;\n * \n * // Create or update user in database\n * const user = await this.db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name,\n * picture: userInfo.picture\n * });\n * \n * // Generate session ID\n * const sessionId = generateSecureId();\n * \n * // Store session with tokens\n * await this.db.sessions.create({\n * id: sessionId,\n * userId: user.id,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: new Date(Date.now() + (tokenData.expires_in || 3600) * 1000)\n * });\n * \n * return {\n * token: sessionId,\n * raw: {\n * userId: user.id,\n * email: user.email,\n * provider,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000\n * }\n * };\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * \n * @param tokenData - The token data received from the OAuth provider's token endpoint\n * @param providerMetadata - Provider metadata including name and endpoints\n * @returns A Promise that resolves to a Session object\n * \n * @remarks\n * This method is called after successfully exchanging the authorization code\n * for tokens. You receive:\n * \n * Token Data:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (optional)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (for OIDC providers)\n * - scope: Granted scopes\n * \n * Provider Metadata:\n * - name: The provider name (e.g., 'google', 'github')\n * - endpoints: Provider endpoints (authorization, token, userInfo)\n * \n * The providerMetadata.endpoints.userInfo can be used with extractUserInfo():\n * ```typescript\n * import { extractUserInfo } from '@vunexa/lixa';\n * \n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * ```\n * \n * Your session strategy should:\n * 1. Extract user info (using extractUserInfo or decode ID token)\n * 2. Create or lookup users in your database\n * 3. Generate session identifiers\n * 4. Store session data as needed\n * 5. Return a Session object with token and raw data\n * \n * @throws \\{Error\\} If session creation fails (e.g., database error, invalid token)\n * \n * @example\n * Simple implementation:\n * ```typescript\n * async createSession(tokenData: OAuthTokenResponse): Promise<Session> {\n * return {\n * token: tokenData.access_token,\n * raw: tokenData\n * };\n * }\n * ```\n * \n * @example\n * Database integration with user info extraction:\n * ```typescript\n * async createSession(\n * tokenData: OAuthTokenResponse,\n * providerMetadata: ProviderMetadata\n * ): Promise<Session> {\n * // Extract user info from token or userinfo endpoint\n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * \n * // Create or update user in database\n * const user = await db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name\n * });\n * \n * return {\n * token: generateSessionId(),\n * raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }\n * };\n * }\n * ```\n */\n createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @param providerMetadata - Provider metadata (not used in default implementation)\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session> {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: tokenData.access_token,\n raw: tokenData,\n };\n }\n}","import type { OAuthTokenResponse } from \"../models/session\";\n\n/**\n * User information extracted from OAuth provider\n * \n * @public\n */\nexport interface UserInfo {\n email: string;\n id?: string | undefined;\n sub?: string | undefined;\n given_name?: string | undefined;\n family_name?: string | undefined;\n name?: string | undefined;\n picture?: string | undefined;\n email_verified?: boolean | undefined;\n iss?: string | undefined;\n}\n\n/**\n * Decode JWT ID token to extract user information\n * \n * @public\n */\nexport function decodeIdToken(idToken: string): UserInfo {\n const parts = idToken.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid ID token format: expected 3 parts separated by dots');\n }\n \n const base64Payload = parts[1];\n if (!base64Payload) {\n throw new Error('Invalid ID token: missing payload section');\n }\n const payload = Buffer.from(base64Payload, 'base64').toString();\n \n try {\n return JSON.parse(payload);\n } catch (error) {\n throw new Error('Invalid ID token: failed to parse payload JSON');\n }\n}\n\n/**\n * Determine OAuth provider from ID token issuer\n * \n * @public\n */\nexport function determineProviderFromIssuer(userInfo: UserInfo): string | null {\n if (!userInfo.iss) {\n return null;\n }\n \n const issuer = userInfo.iss.toLowerCase();\n \n if (issuer.includes('accounts.google.com')) {\n return 'google';\n }\n \n if (issuer.includes('github')) {\n return 'github';\n }\n \n // Unknown issuer\n return null;\n}\n\n/**\n * Fetch user info from OAuth provider's userinfo endpoint\n * \n * @param accessToken - OAuth access token\n * @param userInfoEndpoint - The provider's userinfo endpoint URL\n * @param providerName - Provider name for error messages (optional)\n * @returns User information from the provider\n * \n * @throws Error if the request fails or response is invalid\n * \n * @public\n */\nexport async function fetchUserInfo(\n accessToken: string, \n userInfoEndpoint: string,\n providerName?: string\n): Promise<UserInfo> {\n const response = await fetch(userInfoEndpoint, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n \n if (!response.ok) {\n const providerLabel = providerName ? ` from ${providerName}` : '';\n throw new Error(`Failed to fetch user info${providerLabel}: ${response.status} ${response.statusText}`);\n }\n \n const data = await response.json();\n if (!data || typeof data !== 'object' || !('email' in data) || typeof data.email !== 'string') {\n const providerLabel = providerName ? ` from ${providerName}` : '';\n throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);\n }\n \n return {\n email: data.email,\n id: 'id' in data ? String(data.id) : undefined,\n sub: 'sub' in data ? String(data.sub) : undefined,\n given_name: 'given_name' in data ? String(data.given_name) : undefined,\n family_name: 'family_name' in data ? String(data.family_name) : undefined,\n name: 'name' in data ? String(data.name) : undefined,\n picture: 'picture' in data ? String(data.picture) : undefined,\n email_verified: 'email_verified' in data ? Boolean(data.email_verified) : undefined,\n iss: 'iss' in data ? String(data.iss) : undefined,\n };\n}\n\n/**\n * Extract user info from OAuth token data\n * \n * @param tokenData - OAuth token response from provider\n * @param userInfoEndpoint - Optional userinfo endpoint URL (required if no ID token)\n * @param providerName - Optional provider name for error messages\n * @returns User info and detected provider name\n * \n * @remarks\n * This function attempts to extract user information in the following order:\n * 1. Decode ID token if present (preferred method)\n * 2. Fetch from userinfo endpoint using access token (requires userInfoEndpoint parameter)\n * \n * Provider detection:\n * - Primary: Extract from ID token issuer field\n * - Fallback: Use provider field in token data (if present)\n * - Fallback: Use providerName parameter\n * - Throws error if provider cannot be determined\n * \n * @throws Error if no ID token or access token is available\n * @throws Error if provider cannot be determined\n * @throws Error if userInfoEndpoint is required but not provided\n * \n * @example\n * With ID token (provider auto-detected):\n * ```typescript\n * const { userInfo, provider } = await extractUserInfo(tokenData);\n * console.log(`User ${userInfo.email} authenticated via ${provider}`);\n * ```\n * \n * @example\n * Without ID token (requires userInfoEndpoint):\n * ```typescript\n * const { userInfo, provider } = await extractUserInfo(\n * tokenData,\n * 'https://api.example.com/user',\n * 'custom'\n * );\n * ```\n * \n * @public\n */\nexport async function extractUserInfo(\n tokenData: OAuthTokenResponse,\n userInfoEndpoint?: string,\n providerName?: string\n): Promise<{ userInfo: UserInfo; provider: string }> {\n let userInfo: UserInfo;\n let provider: string | null = null;\n \n if (tokenData.id_token) {\n // Decode ID token to get user info\n userInfo = decodeIdToken(tokenData.id_token);\n \n // Try to determine provider from issuer\n provider = determineProviderFromIssuer(userInfo);\n \n // Fallback to provided provider name\n if (!provider && providerName) {\n provider = providerName;\n }\n } else if (tokenData.access_token) {\n // Without ID token, we need to know which provider to fetch from\n // Check if provider info is in the token data (custom field)\n if ('provider' in tokenData && typeof tokenData.provider === 'string') {\n provider = tokenData.provider;\n } else if (providerName) {\n provider = providerName;\n }\n \n if (!provider) {\n throw new Error(\n 'Cannot determine OAuth provider: No ID token with issuer information, ' +\n 'no provider field in token data, and no providerName provided. Unable to fetch user info.'\n );\n }\n \n if (!userInfoEndpoint) {\n throw new Error(\n `Cannot fetch user info for provider '${provider}': No ID token available and no userInfoEndpoint provided. ` +\n 'Either ensure the provider returns an ID token or provide the userInfoEndpoint parameter.'\n );\n }\n \n // Fetch user info from provider's API\n userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint, provider);\n } else {\n throw new Error('No ID token or access token available to fetch user info');\n }\n \n // Final provider validation\n if (!provider) {\n throw new Error(\n 'Cannot determine OAuth provider: ID token issuer not recognized and no provider name provided.'\n );\n }\n \n return { userInfo, provider };\n}\n"],"mappings":"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","names":["NodeCache"]}
|
|
1
|
+
{"version":3,"sources":["../src/lixa.ts","../src/dao/state-cache.ts","../src/dao/session-cache.ts","../src/models/session.ts","../src/utils/user-info.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport { type LixaConfig, type ProviderConfig } from \"./types\";\nimport { IProvider } from \"./providers\";\nimport { LocalStateCache } from \"./dao/state-cache\";\nimport { SessionDao, StateDao } from \"./dao/types\";\nimport crypto from \"crypto\";\nimport { LocalSessionCache } from \"./dao/session-cache\";\nimport { type Session, type SessionStrategy, DefaultSessionStrategy, type OAuthTokenResponse, type ProviderMetadata } from \"./models/session\";\n\n/**\n * Type representing the keys of configured providers\n */\ntype ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];\n\n/**\n * A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library.\n *\n * @remarks\n * Lixa simplifies multi-provider authentication flows and supports extensible session management.\n * Providers can be passed inline in the configuration, eliminating the need for pre-registration.\n *\n * @example\n * Using built-in providers from \\@vunexa/lixa-providers:\n * ```typescript\n * import { Lixa } from '@vunexa/lixa';\n * import { GoogleProvider } from '@vunexa/lixa-providers';\n * \n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/google/callback',\n * scopes: ['openid', 'email', 'profile']\n * }\n * }\n * });\n * ```\n * \n * @example\n * Using custom inline providers:\n * ```typescript\n * import { Lixa, IProvider } from '@vunexa/lixa';\n * \n * const customProvider: IProvider = {\n * authorizationEndpoint: 'https://custom.com/oauth/authorize',\n * tokenEndpoint: 'https://custom.com/oauth/token',\n * userInfoEndpoint: 'https://custom.com/api/user'\n * };\n * \n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: customProvider,\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * redirectUri: 'https://yourapp.com/auth/custom/callback',\n * scopes: ['read:user']\n * }\n * }\n * });\n * ```\n *\n * @public\n */\nclass Lixa<TConfig extends LixaConfig<Record<string, ProviderConfig>> = LixaConfig> {\n private static DEFAULT_PROVIDERS: Map<string, IProvider> = new Map();\n private static CONFIGURED_PROVIDERS: Map<string, IProvider> = new Map(); // Legacy registry for backward compatibility\n private static LOCAL_STATE_CACHE = new LocalStateCache();\n private static LOCAL_SESSION_CACHE = new LocalSessionCache();\n private static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();\n private config: TConfig;\n private stateDao: StateDao;\n private sesionDao: SessionDao;\n private sessionStrategy: SessionStrategy;\n private debug: boolean;\n\n /**\n * Creates a new Lixa instance with the provided configuration.\n * \n * @remarks\n * Providers can be passed inline in the configuration using the `provider` field.\n * Provider resolution priority: inline custom provider \\> default providers \\> legacy registry.\n * \n * @param config - The configuration object containing provider settings and optional session strategy\n * \n * @throws Error when provider configuration is missing required fields\n * @throws Error when provider implementation is missing required properties\n * @throws Error when provider is not available and no inline implementation is provided\n */\n constructor(config: TConfig) {\n this.config = config;\n this.stateDao = config.stateDao || Lixa.LOCAL_STATE_CACHE;\n this.sesionDao = config.sessionDao || Lixa.LOCAL_SESSION_CACHE;\n this.sessionStrategy = config.sessionStrategy || Lixa.DEFAULT_SESSION_STRATEGY;\n this.debug = config.debug || false;\n \n this.log('INFO', 'Init', 'Initializing Lixa instance', { \n providers: Object.keys(config.providers),\n debug: this.debug \n });\n \n // Validate and extract providers from configuration\n for (const [providerName, providerConfig] of Object.entries(config.providers)) {\n const name = providerName.toLowerCase();\n const typedConfig: ProviderConfig = providerConfig;\n \n // Validate provider configuration has required credentials\n this.validateProviderConfig(providerName, typedConfig);\n \n // If provider config includes a custom provider implementation, validate it\n if (typedConfig.provider) {\n this.validateProviderImplementation(providerName, typedConfig.provider);\n this.log('INFO', 'Init', `Registered inline provider: ${providerName}`);\n } else {\n // Check if it's available in default providers or legacy registry\n if (!Lixa.DEFAULT_PROVIDERS.has(name) && !Lixa.CONFIGURED_PROVIDERS.has(name)) {\n this.log('ERROR', 'Init', `Provider '${providerName}' not available`);\n throw new Error(\n `Provider '${providerName}' is not available. ` +\n `Either import it from '@vunexa/lixa-providers' and include it in the configuration, ` +\n `or provide a custom implementation using the 'provider' field: ` +\n `{ provider: new CustomProvider(), clientId: '...', ... }`\n );\n }\n this.log('INFO', 'Init', `Using registered provider: ${providerName}`);\n }\n }\n \n this.log('INFO', 'Init', 'Lixa instance initialized successfully');\n }\n \n /**\n * Validates that a provider configuration has all required credentials.\n * \n * @param name - The provider name\n * @param config - The provider configuration\n * @throws Error when required fields are missing or invalid\n */\n private validateProviderConfig(name: string, config: ProviderConfig): void {\n const requiredFields: (keyof ProviderConfig)[] = ['clientId', 'clientSecret', 'redirectUri', 'scopes'];\n const missingFields = requiredFields.filter(field => {\n const value = config[field];\n return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');\n });\n \n if (missingFields.length > 0) {\n throw new Error(\n `Provider '${name}' configuration is missing required fields: ${missingFields.join(', ')}`\n );\n }\n \n // Validate scopes is an array\n if (!Array.isArray(config.scopes)) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' must be an array of strings`\n );\n }\n \n if (config.scopes.length === 0) {\n throw new Error(\n `Provider '${name}' configuration error: 'scopes' array cannot be empty`\n );\n }\n }\n \n /**\n * Validates that a provider implementation has all required properties.\n * \n * @param name - The provider name\n * @param provider - The provider implementation\n * @throws Error when required properties are missing\n */\n private validateProviderImplementation(name: string, provider: IProvider): void {\n const requiredProps: (keyof IProvider)[] = ['authorizationEndpoint', 'tokenEndpoint', 'userInfoEndpoint'];\n const missingProps = requiredProps.filter(prop => {\n const value = provider[prop];\n return !value || typeof value !== 'string' || value.trim() === '';\n });\n \n if (missingProps.length > 0) {\n throw new Error(\n `Provider '${name}' implementation is missing required properties: ${missingProps.join(', ')}. ` +\n `All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`\n );\n }\n }\n\n /**\n * Structured debug logging with standardized format.\n * \n * @param level - Log level (INFO, WARN, ERROR)\n * @param context - Context of the log (Init, Auth, Token, Session, State)\n * @param message - Log message\n * @param data - Optional data to log\n * \n * @remarks\n * Format: [Lixa] [timestamp] [level] [context] message\n * Only logs when debug mode is enabled.\n */\n private log(level: 'INFO' | 'WARN' | 'ERROR', context: 'Init' | 'Auth' | 'Token' | 'Session' | 'State', message: string, data?: Record<string, string | number | boolean | string[]>): void {\n if (!this.debug) return;\n \n const timestamp = new Date().toISOString();\n const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;\n \n if (data !== undefined) {\n console.log(`${prefix} ${message}`, data);\n } else {\n console.log(`${prefix} ${message}`);\n }\n }\n\n /**\n * Checks if a provider is configured for this instance.\n * This is a type guard that narrows the provider type for use with getAuthUrl.\n *\n * @param provider - The provider name to check (case-insensitive)\n * @returns True if the provider is configured, false otherwise\n *\n * @example\n * ```typescript\n * if (lixa.isProviderConfigured(provider)) {\n * // TypeScript now knows provider is a valid ConfiguredProviderKey\n * const authUrl = lixa.getAuthUrl(provider, state);\n * }\n * ```\n */\n public isProviderConfigured<T extends string>(provider: T): provider is T & ConfiguredProviderKey<TConfig> {\n const providerType = provider.toLowerCase();\n return this.config.providers.hasOwnProperty(providerType);\n }\n \n /**\n * Gets a provider implementation by name.\n * Resolution priority: inline custom provider \\> default providers \\> legacy registry\n * \n * @param name - The provider name (case-insensitive)\n * @param config - The provider configuration\n * @returns The provider implementation\n * @throws Error when provider is not found\n */\n private getProvider(name: string, config: ProviderConfig): IProvider {\n // First check if provider is inline in config\n if (config.provider) {\n return config.provider;\n }\n \n // Then check default providers\n const lowerName = name.toLowerCase();\n const defaultProvider = Lixa.DEFAULT_PROVIDERS.get(lowerName);\n if (defaultProvider) {\n return defaultProvider;\n }\n \n // Finally check legacy registry for backward compatibility\n const legacyProvider = Lixa.CONFIGURED_PROVIDERS.get(lowerName);\n if (legacyProvider) {\n return legacyProvider;\n }\n \n throw new Error(\n `Provider '${name}' not found. ` +\n `Ensure the provider is included in the configuration with a 'provider' field, ` +\n `or registered using Lixa.registerProvider().`\n );\n }\n\n /**\n * Registers custom OAuth providers for use with Lixa.\n * \n * @deprecated This method is maintained for backward compatibility.\n * The recommended approach is to pass providers inline in the configuration:\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * custom: {\n * provider: new CustomProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n *\n * @param providerMap - A map of provider names to IProvider implementations\n *\n * @example\n * Legacy usage (still supported):\n * ```typescript\n * class CustomProvider implements IProvider {\n * authorizationEndpoint = 'https://custom.com/oauth/authorize';\n * tokenEndpoint = 'https://custom.com/oauth/token';\n * userInfoEndpoint = 'https://custom.com/api/user';\n * }\n *\n * Lixa.registerProvider({ custom: new CustomProvider() });\n * ```\n */\n public static registerProvider<T extends Record<string, IProvider>>(providerMap: T): void {\n Object.entries(providerMap).forEach(([key, providerImpl]) => {\n Lixa.CONFIGURED_PROVIDERS.set(key.toLowerCase(), providerImpl);\n });\n }\n\n /**\n * Gets the list of registered provider names.\n * \n * @returns Array of registered provider names\n */\n public static getRegisteredProviders(): string[] {\n return Array.from(Lixa.CONFIGURED_PROVIDERS.keys());\n }\n\n /**\n * Creates a type-safe configuration.\n * \n * @deprecated This method is maintained for backward compatibility.\n * You can now pass configuration directly to the Lixa constructor without this helper.\n * \n * @param config - Configuration object with provider settings\n * @returns The same configuration object with type safety\n * \n * @example\n * New approach (recommended):\n * ```typescript\n * const lixa = new Lixa({\n * providers: {\n * google: {\n * provider: new GoogleProvider(),\n * clientId: '...',\n * // ...\n * }\n * }\n * });\n * ```\n */\n public static createConfig<T extends Record<string, ProviderConfig>>(\n config: LixaConfig<T> & { providers: T }\n ): LixaConfig<T> {\n return config;\n }\n\n /**\n * Generates a cryptographically secure random state parameter for OAuth flows.\n *\n * @returns A 32-character hexadecimal string\n *\n * @remarks\n * The state parameter is used to prevent CSRF attacks in OAuth flows.\n */\n public static generateRandomState(): string {\n return randomBytes(16).toString(\"hex\");\n }\n\n /**\n * Generates a cryptographically secure code verifier for PKCE flows.\n *\n * @returns A 64-character hexadecimal string (32 random bytes encoded as hex)\n *\n * @remarks\n * This method implements the code verifier generation as specified in RFC 7636 (PKCE).\n * \n * **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that\n * prevents authorization code interception attacks. It's especially important for\n * public clients (mobile apps, SPAs) but is recommended for all OAuth flows.\n * \n * **Generation methodology:**\n * 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()\n * 2. Encode the bytes as a hexadecimal string (64 characters)\n * 3. The verifier is stored securely and used later in the token exchange\n * \n * **RFC 7636 Requirements:**\n * - Minimum length: 43 characters\n * - Maximum length: 128 characters\n * - Character set: [A-Z] / [a-z] / [0-9] / \"-\" / \".\" / \"_\" / \"~\"\n * - This implementation produces 64 hex characters, meeting the requirements\n * \n * The code verifier is:\n * - Generated when creating the authorization URL\n * - Stored in state cache with the state parameter\n * - Retrieved during callback handling\n * - Sent to the token endpoint to prove the client's identity\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see buildCodeChallenge for the corresponding challenge generation\n * \n * @internal\n */\n private static generateCodeVerifier(): string {\n return randomBytes(32).toString(\"hex\");\n }\n\n /**\n * Generates a code challenge from a code verifier for PKCE flows.\n *\n * @param codeVerifier - The code verifier string (64 hex characters)\n * @returns A base64url-encoded SHA-256 hash of the code verifier\n *\n * @remarks\n * This method implements the code challenge generation as specified in RFC 7636 (PKCE)\n * using the S256 (SHA-256) transformation method.\n * \n * **Challenge generation methodology:**\n * 1. Hash the code verifier using SHA-256\n * 2. Encode the hash as base64\n * 3. Convert to base64url format (RFC 4648):\n * - Replace '+' with '-'\n * - Replace '/' with '_'\n * - Remove trailing '=' padding\n * \n * **PKCE Flow:**\n * 1. Client generates code_verifier (random string)\n * 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))\n * 3. Client sends code_challenge to authorization endpoint\n * 4. Authorization server stores the code_challenge\n * 5. Client sends code_verifier to token endpoint\n * 6. Authorization server verifies: SHA256(code_verifier) == code_challenge\n * \n * **Security Benefits:**\n * - Prevents authorization code interception attacks\n * - Even if an attacker intercepts the authorization code, they cannot\n * exchange it for tokens without the original code_verifier\n * - The challenge is sent in the authorization request (public)\n * - The verifier is sent in the token request (should be kept secret)\n * \n * **RFC 7636 Transformation Methods:**\n * - plain: code_challenge = code_verifier (not recommended)\n * - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)\n * \n * @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}\n * @see generateCodeVerifier for the verifier generation\n * \n * @internal\n */\n private static buildCodeChallenge(codeVerifier: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\");\n\n // Convert to base64url (RFC 4648 Section 5)\n return hash.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n /**\n * Generates the authorization URL for the specified provider.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param state - The state parameter for CSRF protection\n * @returns The complete authorization URL to redirect users to\n *\n * @throws Error when the provider is not configured\n *\n * @example\n * ```typescript\n * const state = Lixa.generateRandomState();\n * const authUrl = lixa.getAuthUrl('google', state);\n * res.redirect(authUrl);\n * ```\n */\n public getAuthUrl(provider: ConfiguredProviderKey<TConfig> | string, state: string): string {\n const providerType = String(provider).toLowerCase();\n \n this.log('INFO', 'Auth', `Generating authorization URL for provider: ${providerType}`);\n \n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n const codeVerifier = Lixa.generateCodeVerifier();\n const codeChallenge = Lixa.buildCodeChallenge(codeVerifier);\n\n this.log('INFO', 'State', `Saving state for provider: ${providerType}`, { state });\n\n // Cache the state paramaeter with TTL of 5 minutes (300 seconds)\n // We dont care about value. we are onl interested in key existence\n this.stateDao.saveState(\n state,\n {\n createdAt: Date.now(),\n provider: providerType,\n codeVerifier,\n },\n 300 // 5 minutes in seconds\n );\n\n const params = new URLSearchParams({\n client_id: providerConfig.clientId,\n redirect_uri: providerConfig.redirectUri,\n scope: providerConfig.scopes.join(\" \"),\n state,\n response_type: \"code\",\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n ...providerConfig.extraConfig,\n });\n\n const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;\n this.log('INFO', 'Auth', `Authorization URL generated successfully`, { \n provider: providerType,\n endpoint: providerImpl.authorizationEndpoint \n });\n\n return authUrl;\n }\n\n /**\n * Handles the OAuth callback and creates a user session.\n *\n * @param provider - The provider name (must be a configured provider key)\n * @param code - The authorization code from the provider\n * @param state - The state parameter for validation\n * @returns A Promise that resolves to the session ID\n *\n * @throws Error when code or state is missing/invalid, or provider is not configured\n *\n * @example\n * ```typescript\n * const sessionId = await lixa.handleCallback({\n * provider: 'google',\n * code: req.query.code,\n * state: req.query.state\n * });\n * ```\n */\n public async handleCallback({\n provider,\n code,\n state,\n }: {\n provider: ConfiguredProviderKey<TConfig> | string;\n code: string;\n state?: string;\n }): Promise<string> {\n const providerType = String(provider).toLowerCase();\n this.log('INFO', 'Auth', `Handling OAuth callback for provider: ${providerType}`);\n\n if (!code || code.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing authorization code in callback');\n throw new Error(\"Invalid or missing code in callback\");\n }\n\n if (!state || state.trim() === \"\") {\n this.log('ERROR', 'Auth', 'Invalid or missing state in callback');\n throw new Error(\"Invalid or missing state in callback\");\n }\n\n this.log('INFO', 'State', 'Validating state parameter', { state });\n\n //Validate state here\n const cachedState = await this.stateDao.getState(state);\n if (!cachedState) {\n this.log('ERROR', 'State', 'State validation failed: state not found or expired', { state });\n throw new Error(\"Invalid or expired state\");\n }\n \n this.log('INFO', 'State', 'State validated successfully, removing from cache');\n // State is valid, remove it from cache to prevent reuse\n await this.stateDao.deleteState(state);\n\n //Get code verifier from cached state\n const codeVerifier = cachedState.codeVerifier;\n\n const providerConfig = this.findProviderByType(providerType);\n\n if (!providerConfig) {\n this.log('ERROR', 'Auth', `Provider '${String(provider)}' is not configured`);\n throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);\n }\n\n const providerImpl = this.getProvider(providerType, providerConfig);\n\n this.log('INFO', 'Token', `Exchanging authorization code for tokens`, { provider: providerType });\n\n // Exchange code for tokens and fetch user info here.\n const tokens = await this.exchangeCodeForToken(\n code,\n providerConfig,\n providerImpl,\n codeVerifier\n );\n\n this.log('INFO', 'Token', 'Token exchange successful');\n this.log('INFO', 'Session', 'Creating user session');\n \n // Create provider metadata for session strategy\n const providerMetadata: ProviderMetadata = {\n name: providerType,\n endpoints: {\n authorization: providerImpl.authorizationEndpoint,\n token: providerImpl.tokenEndpoint,\n userInfo: providerImpl.userInfoEndpoint\n }\n };\n \n const session = await this.sessionStrategy.createSession(tokens, providerMetadata);\n\n // Generate unique session ID\n const sessionId = randomBytes(32).toString(\"hex\");\n\n this.log('INFO', 'Session', 'Storing session', { sessionId });\n // Store session with 24 hour TTL (86400 seconds)\n await this.sesionDao.saveSession(sessionId, session, 86400);\n\n this.log('INFO', 'Session', 'Session created successfully', { sessionId });\n\n return sessionId;\n }\n\n public async fetchSessionInfo(sessionId: string): Promise<Session | null> {\n return await this.sesionDao.getSession<Session>(sessionId);\n }\n\n private async exchangeCodeForToken(\n code: string,\n providerConfig: ProviderConfig,\n providerImpl: IProvider,\n codeVerifier: string\n ): Promise<OAuthTokenResponse> {\n // Build the request body\n const body: Record<string, string> = {\n client_id: providerConfig.clientId,\n client_secret: providerConfig.clientSecret,\n code,\n redirect_uri: providerConfig.redirectUri,\n grant_type: \"authorization_code\",\n };\n\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n const params = new URLSearchParams(body);\n\n this.log('INFO', 'Token', 'Sending token exchange request', { \n endpoint: providerImpl.tokenEndpoint \n });\n \n const response = await fetch(providerImpl.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n this.log('ERROR', 'Token', 'Token exchange failed', { \n status: response.status, \n statusText: response.statusText,\n error: errorBody \n });\n throw new Error(\n `Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`\n );\n }\n\n this.log('INFO', 'Token', 'Token exchange response received successfully');\n return response.json();\n }\n\n private findProviderByType(providerType: string): ProviderConfig | undefined {\n // Use Object.entries to safely iterate and find the provider\n for (const [key, value] of Object.entries(this.config.providers)) {\n if (key.toLowerCase() === providerType.toLowerCase()) {\n return value;\n }\n }\n return undefined;\n }\n}\n\nexport { Lixa };\n","import NodeCache from 'node-cache';\nimport { StateDao, StateData } from \"./types\";\n\nclass LocalStateCache implements StateDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getState(state: string): Promise<StateData | null> {\n return this.cache.get<StateData>(state) || null;\n }\n\n async deleteState(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalStateCache };\n","import NodeCache from 'node-cache';\nimport { SessionDao } from \"./types\";\nimport type { Session } from \"../models/session\";\n\nclass LocalSessionCache implements SessionDao {\n private cache: NodeCache;\n\n constructor(defaultTtlSeconds: number = 600) {\n this.cache = new NodeCache({ stdTTL: defaultTtlSeconds });\n }\n\n async saveSession<T extends Session>(state: string, data: T, expiresInSeconds: number): Promise<void> {\n this.cache.set(state, data, expiresInSeconds);\n }\n\n async getSession<T extends Session>(state: string): Promise<T | null> {\n return this.cache.get<T>(state) || null;\n }\n\n async deleteSession(state: string): Promise<void> {\n this.cache.del(state);\n }\n}\n\nexport { LocalSessionCache };\n","/**\n * OAuth 2.0 token response structure.\n * Based on RFC 6749 Section 5.1 and OpenID Connect Core 1.0 Section 3.1.3.3\n * \n * @remarks\n * This interface represents the standard OAuth 2.0 token response with\n * optional OpenID Connect extensions. All OAuth providers should return\n * at minimum the required fields (access_token, token_type).\n * \n * @public\n */\nexport interface OAuthTokenResponse {\n /** \n * OAuth 2.0 access token (required).\n * Used to access protected resources on behalf of the user.\n */\n access_token: string;\n \n /** \n * Token type (required).\n * Typically \"Bearer\" for OAuth 2.0.\n */\n token_type: string;\n \n /** \n * Token expiration time in seconds (optional).\n * Time until the access token expires.\n */\n expires_in?: number;\n \n /** \n * OAuth 2.0 refresh token (optional).\n * Used to obtain new access tokens without re-authentication.\n */\n refresh_token?: string;\n \n /** \n * Granted OAuth scopes (optional).\n * Space-separated list of scopes that were granted.\n */\n scope?: string;\n \n /** \n * OpenID Connect ID token (optional).\n * JWT containing user identity claims (only present for OIDC providers).\n */\n id_token?: string;\n \n /**\n * Additional provider-specific fields.\n * Some providers may include extra fields like user_id, account_id, etc.\n */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Represents a user session after successful OAuth authentication.\n * \n * @remarks\n * The Session object is returned by SessionStrategy.createSession() and contains\n * the session identifier and any additional data needed for your application.\n * \n * The structure is intentionally flexible to support various session management\n * approaches (JWT tokens, session IDs, etc.).\n *\n * @public\n */\nexport interface Session<TRaw = OAuthTokenResponse> {\n /** \n * The session token or identifier.\n * This could be an access token, a session ID, a JWT, or any other identifier\n * that your application uses to track authenticated users.\n */\n token: string;\n \n /** \n * Raw session data.\n * Contains the complete OAuth token response and any additional data\n * your SessionStrategy adds (user info, database IDs, etc.).\n * \n * Typical OAuth token data includes:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (if requested)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (if using OIDC)\n * - scope: Granted scopes\n */\n raw: TRaw;\n}\n\n/**\n * Provider metadata passed to session strategy.\n * Contains provider name and endpoints for user info extraction.\n * \n * @public\n */\nexport interface ProviderMetadata {\n /** The provider name (e.g., 'google', 'github') */\n name: string;\n \n /** Provider endpoints */\n endpoints: {\n /** Authorization endpoint URL */\n authorization: string;\n /** Token endpoint URL */\n token: string;\n /** UserInfo endpoint URL */\n userInfo: string;\n };\n}\n\n/**\n * Strategy interface for custom session creation.\n * \n * @remarks\n * Implement this interface to customize how OAuth tokens are converted into\n * application sessions. This is where you typically:\n * - Decode ID tokens (for OpenID Connect)\n * - Look up or create users in your database\n * - Generate session identifiers\n * - Store session data\n * - Add custom claims or metadata\n * \n * The default implementation (DefaultSessionStrategy) simply extracts the\n * access token and returns it as the session token.\n * \n * @example\n * Custom session strategy with database integration:\n * ```typescript\n * interface CustomSessionData {\n * userId: string;\n * email: string;\n * provider: string;\n * accessToken: string;\n * refreshToken?: string;\n * expiresAt: number;\n * }\n * \n * class DatabaseSessionStrategy implements SessionStrategy {\n * constructor(private db: Database) {}\n * \n * async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {\n * // User info is already extracted by Lixa!\n * const { userInfo, provider, tokenData } = oauthContext;\n * \n * // Create or update user in database\n * const user = await this.db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name,\n * picture: userInfo.picture\n * });\n * \n * // Generate session ID\n * const sessionId = generateSecureId();\n * \n * // Store session with tokens\n * await this.db.sessions.create({\n * id: sessionId,\n * userId: user.id,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: new Date(Date.now() + (tokenData.expires_in || 3600) * 1000)\n * });\n * \n * return {\n * token: sessionId,\n * raw: {\n * userId: user.id,\n * email: user.email,\n * provider,\n * accessToken: tokenData.access_token,\n * refreshToken: tokenData.refresh_token,\n * expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000\n * }\n * };\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * \n * @param tokenData - The token data received from the OAuth provider's token endpoint\n * @param providerMetadata - Provider metadata including name and endpoints\n * @returns A Promise that resolves to a Session object\n * \n * @remarks\n * This method is called after successfully exchanging the authorization code\n * for tokens. You receive:\n * \n * Token Data:\n * - access_token: OAuth access token\n * - refresh_token: OAuth refresh token (optional)\n * - expires_in: Token expiration time in seconds\n * - token_type: Token type (usually \"Bearer\")\n * - id_token: OpenID Connect ID token (for OIDC providers)\n * - scope: Granted scopes\n * \n * Provider Metadata:\n * - name: The provider name (e.g., 'google', 'github')\n * - endpoints: Provider endpoints (authorization, token, userInfo)\n * \n * The providerMetadata.endpoints.userInfo can be used with extractUserInfo():\n * ```typescript\n * import { extractUserInfo } from '@vunexa/lixa';\n * \n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * ```\n * \n * Your session strategy should:\n * 1. Extract user info (using extractUserInfo or decode ID token)\n * 2. Create or lookup users in your database\n * 3. Generate session identifiers\n * 4. Store session data as needed\n * 5. Return a Session object with token and raw data\n * \n * @throws \\{Error\\} If session creation fails (e.g., database error, invalid token)\n * \n * @example\n * Simple implementation:\n * ```typescript\n * async createSession(tokenData: OAuthTokenResponse): Promise<Session> {\n * return {\n * token: tokenData.access_token,\n * raw: tokenData\n * };\n * }\n * ```\n * \n * @example\n * Database integration with user info extraction:\n * ```typescript\n * async createSession(\n * tokenData: OAuthTokenResponse,\n * providerMetadata: ProviderMetadata\n * ): Promise<Session> {\n * // Extract user info from token or userinfo endpoint\n * const { userInfo } = await extractUserInfo(\n * tokenData,\n * providerMetadata.endpoints.userInfo,\n * providerMetadata.name\n * );\n * \n * // Create or update user in database\n * const user = await db.users.upsert({\n * email: userInfo.email,\n * name: userInfo.name\n * });\n * \n * return {\n * token: generateSessionId(),\n * raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }\n * };\n * }\n * ```\n */\n createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;\n}\n\n/**\n * Default session strategy that works with any OAuth provider.\n * Extracts common token information and creates a standardized session.\n *\n * @public\n */\nexport class DefaultSessionStrategy implements SessionStrategy {\n /**\n * Creates a session from OAuth token data.\n * Handles common OAuth token formats and extracts the access token.\n *\n * @param tokenData - The token data received from the OAuth provider\n * @param providerMetadata - Provider metadata (not used in default implementation)\n * @returns A Promise that resolves to a Session object\n */\n async createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session> {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('No valid access token found in OAuth response');\n }\n\n return {\n token: tokenData.access_token,\n raw: tokenData,\n };\n }\n}","import type { OAuthTokenResponse, ProviderMetadata } from \"../models/session\";\n\n/**\n * User information extracted from OAuth provider\n * \n * @public\n */\nexport interface UserInfo {\n email: string;\n id?: string | undefined;\n sub?: string | undefined;\n given_name?: string | undefined;\n family_name?: string | undefined;\n name?: string | undefined;\n picture?: string | undefined;\n email_verified?: boolean | undefined;\n iss?: string | undefined;\n}\n\n/**\n * Decode JWT ID token to extract user information\n * \n * @public\n */\nexport function decodeIdToken(idToken: string): UserInfo {\n const parts = idToken.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid ID token format: expected 3 parts separated by dots');\n }\n \n const base64Payload = parts[1];\n if (!base64Payload) {\n throw new Error('Invalid ID token: missing payload section');\n }\n const payload = Buffer.from(base64Payload, 'base64').toString();\n \n try {\n return JSON.parse(payload);\n } catch (error) {\n throw new Error('Invalid ID token: failed to parse payload JSON');\n }\n}\n\n/**\n * Determine OAuth provider from ID token issuer\n * \n * @public\n */\nexport function determineProviderFromIssuer(userInfo: UserInfo): string | null {\n if (!userInfo.iss) {\n return null;\n }\n \n const issuer = userInfo.iss.toLowerCase();\n \n if (issuer.includes('accounts.google.com')) {\n return 'google';\n }\n \n if (issuer.includes('github')) {\n return 'github';\n }\n \n // Unknown issuer\n return null;\n}\n\n/**\n * Fetch user info from OAuth provider's userinfo endpoint\n * \n * @param accessToken - OAuth access token\n * @param userInfoEndpoint - The provider's userinfo endpoint URL\n * @param providerName - Provider name for error messages (optional)\n * @returns User information from the provider\n * \n * @throws Error if the request fails or response is invalid\n * \n * @public\n */\nexport async function fetchUserInfo(\n accessToken: string, \n userInfoEndpoint: string\n): Promise<UserInfo> {\n const response = await fetch(userInfoEndpoint, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n \n if (!response.ok) {\n throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);\n }\n \n const data = await response.json();\n if (!data || typeof data !== 'object' || !('email' in data) || typeof data.email !== 'string') {\n throw new Error(`Invalid user info response: missing or invalid email`);\n }\n \n return {\n email: data.email,\n id: 'id' in data ? String(data.id) : undefined,\n sub: 'sub' in data ? String(data.sub) : undefined,\n given_name: 'given_name' in data ? String(data.given_name) : undefined,\n family_name: 'family_name' in data ? String(data.family_name) : undefined,\n name: 'name' in data ? String(data.name) : undefined,\n picture: 'picture' in data ? String(data.picture) : undefined,\n email_verified: 'email_verified' in data ? Boolean(data.email_verified) : undefined,\n iss: 'iss' in data ? String(data.iss) : undefined,\n };\n}\n\n/**\n * Extract user info from OAuth token data\n * \n * @param tokenData - OAuth token response from provider\n * @param providerMetadata - Provider metadata containing endpoints configuration\n * @returns User info extracted from token or fetched from provider\n * \n * @remarks\n * This function attempts to extract user information in the following order:\n * 1. Decode ID token if present (preferred method for OIDC providers)\n * 2. Fetch from userinfo endpoint using access token (uses providerMetadata.endpoints.userInfo)\n * \n * The function automatically determines the best method based on available token data.\n * For OIDC providers (like Google), it decodes the JWT ID token.\n * For OAuth-only providers (like GitHub), it fetches from the userinfo endpoint.\n * \n * @throws Error if no ID token or access token is available\n * @throws Error if userinfo endpoint is required but not provided in providerMetadata\n * \n * @example\n * With ID token (OIDC provider like Google):\n * ```typescript\n * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);\n * console.log(`User ${userInfo.email} authenticated`);\n * ```\n * \n * @example\n * Without ID token (OAuth provider like GitHub):\n * ```typescript\n * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);\n * // Automatically fetches from providerMetadata.endpoints.userInfo\n * console.log(`User ${userInfo.email} authenticated`);\n * ```\n * \n * @public\n */\nexport async function extractUserInfo(\n tokenData: OAuthTokenResponse,\n providerMetadata: ProviderMetadata\n): Promise<{ userInfo: UserInfo }> {\n let userInfo: UserInfo;\n const userInfoEndpoint = providerMetadata.endpoints.userInfo;\n \n if (tokenData.id_token) {\n // Decode ID token to get user info\n userInfo = decodeIdToken(tokenData.id_token);\n } else if (tokenData.access_token) {\n // Fetch user info from provider's API\n userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);\n } else {\n throw new Error('No ID token or access token available to fetch user info');\n }\n \n return { userInfo };\n}\n"],"mappings":"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","names":["NodeCache"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OAuthTokenResponse } from "../models/session";
|
|
1
|
+
import type { OAuthTokenResponse, ProviderMetadata } from "../models/session";
|
|
2
2
|
/**
|
|
3
3
|
* User information extracted from OAuth provider
|
|
4
4
|
*
|
|
@@ -39,51 +39,44 @@ export declare function determineProviderFromIssuer(userInfo: UserInfo): string
|
|
|
39
39
|
*
|
|
40
40
|
* @public
|
|
41
41
|
*/
|
|
42
|
-
export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string
|
|
42
|
+
export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string): Promise<UserInfo>;
|
|
43
43
|
/**
|
|
44
44
|
* Extract user info from OAuth token data
|
|
45
45
|
*
|
|
46
46
|
* @param tokenData - OAuth token response from provider
|
|
47
|
-
* @param
|
|
48
|
-
* @
|
|
49
|
-
* @returns User info and detected provider name
|
|
47
|
+
* @param providerMetadata - Provider metadata containing endpoints configuration
|
|
48
|
+
* @returns User info extracted from token or fetched from provider
|
|
50
49
|
*
|
|
51
50
|
* @remarks
|
|
52
51
|
* This function attempts to extract user information in the following order:
|
|
53
|
-
* 1. Decode ID token if present (preferred method)
|
|
54
|
-
* 2. Fetch from userinfo endpoint using access token (
|
|
52
|
+
* 1. Decode ID token if present (preferred method for OIDC providers)
|
|
53
|
+
* 2. Fetch from userinfo endpoint using access token (uses providerMetadata.endpoints.userInfo)
|
|
55
54
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* -
|
|
59
|
-
* - Fallback: Use providerName parameter
|
|
60
|
-
* - Throws error if provider cannot be determined
|
|
55
|
+
* The function automatically determines the best method based on available token data.
|
|
56
|
+
* For OIDC providers (like Google), it decodes the JWT ID token.
|
|
57
|
+
* For OAuth-only providers (like GitHub), it fetches from the userinfo endpoint.
|
|
61
58
|
*
|
|
62
59
|
* @throws Error if no ID token or access token is available
|
|
63
|
-
* @throws Error if
|
|
64
|
-
* @throws Error if userInfoEndpoint is required but not provided
|
|
60
|
+
* @throws Error if userinfo endpoint is required but not provided in providerMetadata
|
|
65
61
|
*
|
|
66
62
|
* @example
|
|
67
|
-
* With ID token (provider
|
|
63
|
+
* With ID token (OIDC provider like Google):
|
|
68
64
|
* ```typescript
|
|
69
|
-
* const { userInfo
|
|
70
|
-
* console.log(`User ${userInfo.email} authenticated
|
|
65
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
66
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
71
67
|
* ```
|
|
72
68
|
*
|
|
73
69
|
* @example
|
|
74
|
-
* Without ID token (
|
|
70
|
+
* Without ID token (OAuth provider like GitHub):
|
|
75
71
|
* ```typescript
|
|
76
|
-
* const { userInfo
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* 'custom'
|
|
80
|
-
* );
|
|
72
|
+
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
73
|
+
* // Automatically fetches from providerMetadata.endpoints.userInfo
|
|
74
|
+
* console.log(`User ${userInfo.email} authenticated`);
|
|
81
75
|
* ```
|
|
82
76
|
*
|
|
83
77
|
* @public
|
|
84
78
|
*/
|
|
85
|
-
export declare function extractUserInfo(tokenData: OAuthTokenResponse,
|
|
79
|
+
export declare function extractUserInfo(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<{
|
|
86
80
|
userInfo: UserInfo;
|
|
87
|
-
provider: string;
|
|
88
81
|
}>;
|
|
89
82
|
//# sourceMappingURL=user-info.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-info.d.ts","sourceRoot":"","sources":["../../src/utils/user-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-info.d.ts","sourceRoot":"","sources":["../../src/utils/user-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAiBvD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAiB7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,CAAC,CA4BnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,kBAAkB,EAC7B,gBAAgB,EAAE,gBAAgB,GACjC,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAejC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vunexa/lixa",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.29",
|
|
4
4
|
"description": "Lixa is a flexible, provider-agnostic OAuth and OpenID Connect (OIDC) client library that simplifies multi-provider authentication flows. It supports seamless integration with providers like Google and GitHub, offers extensible session management, and enables dynamic provider resolution based on callback URLs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"oauth",
|