@vunexa/lixa 0.0.1-alpha.27 → 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 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, provider } = await extractUserInfo(
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 via ${provider}`);
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
- - `userInfoEndpoint` (string, optional) - Provider's userinfo endpoint URL (required if no ID token)
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, provider: string }` - User information and detected provider name
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. Provider detection: From ID token issuer, token data, or parameter
600
- 4. Throws clear errors if provider cannot be determined
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, provider } = await extractUserInfo(tokenData);
690
+ const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
694
691
 
695
- console.log(`User ${userInfo.email} authenticated via ${provider}`);
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 detection** - Determines provider from token data
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 fetching
739
- ✅ **Error handling** - Clear error messages when provider cannot be determined
740
- ✅ **Uses IProvider** - Leverages userInfoEndpoint from provider configuration
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
 
@@ -40,9 +40,10 @@ export declare class DefaultSessionStrategy implements SessionStrategy {
40
40
  * Handles common OAuth token formats and extracts the access token.
41
41
  *
42
42
  * @param tokenData - The token data received from the OAuth provider
43
+ * @param providerMetadata - Provider metadata (not used in default implementation)
43
44
  * @returns A Promise that resolves to a Session object
44
45
  */
45
- createSession(tokenData: OAuthTokenResponse): Promise<Session>;
46
+ createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;
46
47
  }
47
48
 
48
49
  /**
@@ -56,47 +57,40 @@ export declare function determineProviderFromIssuer(userInfo: UserInfo): string
56
57
  * Extract user info from OAuth token data
57
58
  *
58
59
  * @param tokenData - OAuth token response from provider
59
- * @param userInfoEndpoint - Optional userinfo endpoint URL (required if no ID token)
60
- * @param providerName - Optional provider name for error messages
61
- * @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
62
62
  *
63
63
  * @remarks
64
64
  * This function attempts to extract user information in the following order:
65
- * 1. Decode ID token if present (preferred method)
66
- * 2. Fetch from userinfo endpoint using access token (requires userInfoEndpoint parameter)
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)
67
67
  *
68
- * Provider detection:
69
- * - Primary: Extract from ID token issuer field
70
- * - Fallback: Use provider field in token data (if present)
71
- * - Fallback: Use providerName parameter
72
- * - 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.
73
71
  *
74
72
  * @throws Error if no ID token or access token is available
75
- * @throws Error if provider cannot be determined
76
- * @throws Error if userInfoEndpoint is required but not provided
73
+ * @throws Error if userinfo endpoint is required but not provided in providerMetadata
77
74
  *
78
75
  * @example
79
- * With ID token (provider auto-detected):
76
+ * With ID token (OIDC provider like Google):
80
77
  * ```typescript
81
- * const { userInfo, provider } = await extractUserInfo(tokenData);
82
- * console.log(`User ${userInfo.email} authenticated via ${provider}`);
78
+ * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
79
+ * console.log(`User ${userInfo.email} authenticated`);
83
80
  * ```
84
81
  *
85
82
  * @example
86
- * Without ID token (requires userInfoEndpoint):
83
+ * Without ID token (OAuth provider like GitHub):
87
84
  * ```typescript
88
- * const { userInfo, provider } = await extractUserInfo(
89
- * tokenData,
90
- * 'https://api.example.com/user',
91
- * 'custom'
92
- * );
85
+ * const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
86
+ * // Automatically fetches from providerMetadata.endpoints.userInfo
87
+ * console.log(`User ${userInfo.email} authenticated`);
93
88
  * ```
94
89
  *
95
90
  * @public
96
91
  */
97
- export declare function extractUserInfo(tokenData: OAuthTokenResponse, userInfoEndpoint?: string, providerName?: string): Promise<{
92
+ export declare function extractUserInfo(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<{
98
93
  userInfo: UserInfo;
99
- provider: string;
100
94
  }>;
101
95
 
102
96
  /**
@@ -111,7 +105,7 @@ export declare function extractUserInfo(tokenData: OAuthTokenResponse, userInfoE
111
105
  *
112
106
  * @public
113
107
  */
114
- export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string, providerName?: string): Promise<UserInfo>;
108
+ export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string): Promise<UserInfo>;
115
109
 
116
110
  /**
117
111
  * Interface for OAuth 2.0 and OpenID Connect provider implementations.
@@ -769,6 +763,26 @@ export declare type ProviderConfig = {
769
763
  provider: IProvider;
770
764
  });
771
765
 
766
+ /**
767
+ * Provider metadata passed to session strategy.
768
+ * Contains provider name and endpoints for user info extraction.
769
+ *
770
+ * @public
771
+ */
772
+ export declare interface ProviderMetadata {
773
+ /** The provider name (e.g., 'google', 'github') */
774
+ name: string;
775
+ /** Provider endpoints */
776
+ endpoints: {
777
+ /** Authorization endpoint URL */
778
+ authorization: string;
779
+ /** Token endpoint URL */
780
+ token: string;
781
+ /** UserInfo endpoint URL */
782
+ userInfo: string;
783
+ };
784
+ }
785
+
772
786
  /**
773
787
  * Helper type to create a configuration with only registered providers.
774
788
  * Use this with Lixa.createConfig() for type safety.
@@ -942,24 +956,27 @@ export declare interface SessionDao {
942
956
  * @example
943
957
  * Custom session strategy with database integration:
944
958
  * ```typescript
945
- * interface CustomSessionData extends OAuthTokenResponse {
959
+ * interface CustomSessionData {
946
960
  * userId: string;
947
961
  * email: string;
962
+ * provider: string;
963
+ * accessToken: string;
964
+ * refreshToken?: string;
965
+ * expiresAt: number;
948
966
  * }
949
967
  *
950
968
  * class DatabaseSessionStrategy implements SessionStrategy {
951
969
  * constructor(private db: Database) {}
952
970
  *
953
- * async createSession(tokenData: OAuthTokenResponse): Promise<Session<CustomSessionData>> {
954
- * // Decode ID token for OIDC providers
955
- * const idToken = tokenData.id_token;
956
- * const payload = decodeJwt(idToken);
971
+ * async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {
972
+ * // User info is already extracted by Lixa!
973
+ * const { userInfo, provider, tokenData } = oauthContext;
957
974
  *
958
975
  * // Create or update user in database
959
976
  * const user = await this.db.users.upsert({
960
- * email: payload.email,
961
- * name: payload.name,
962
- * picture: payload.picture
977
+ * email: userInfo.email,
978
+ * name: userInfo.name,
979
+ * picture: userInfo.picture
963
980
  * });
964
981
  *
965
982
  * // Generate session ID
@@ -979,7 +996,10 @@ export declare interface SessionDao {
979
996
  * raw: {
980
997
  * userId: user.id,
981
998
  * email: user.email,
982
- * ...tokenData
999
+ * provider,
1000
+ * accessToken: tokenData.access_token,
1001
+ * refreshToken: tokenData.refresh_token,
1002
+ * expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000
983
1003
  * }
984
1004
  * };
985
1005
  * }
@@ -993,14 +1013,14 @@ export declare interface SessionStrategy {
993
1013
  * Creates a session from OAuth token data.
994
1014
  *
995
1015
  * @param tokenData - The token data received from the OAuth provider's token endpoint
1016
+ * @param providerMetadata - Provider metadata including name and endpoints
996
1017
  * @returns A Promise that resolves to a Session object
997
1018
  *
998
1019
  * @remarks
999
1020
  * This method is called after successfully exchanging the authorization code
1000
- * for tokens. The tokenData parameter contains the raw response from the
1001
- * provider's token endpoint.
1021
+ * for tokens. You receive:
1002
1022
  *
1003
- * Common token data fields:
1023
+ * Token Data:
1004
1024
  * - access_token: OAuth access token
1005
1025
  * - refresh_token: OAuth refresh token (optional)
1006
1026
  * - expires_in: Token expiration time in seconds
@@ -1008,6 +1028,28 @@ export declare interface SessionStrategy {
1008
1028
  * - id_token: OpenID Connect ID token (for OIDC providers)
1009
1029
  * - scope: Granted scopes
1010
1030
  *
1031
+ * Provider Metadata:
1032
+ * - name: The provider name (e.g., 'google', 'github')
1033
+ * - endpoints: Provider endpoints (authorization, token, userInfo)
1034
+ *
1035
+ * The providerMetadata.endpoints.userInfo can be used with extractUserInfo():
1036
+ * ```typescript
1037
+ * import { extractUserInfo } from '@vunexa/lixa';
1038
+ *
1039
+ * const { userInfo } = await extractUserInfo(
1040
+ * tokenData,
1041
+ * providerMetadata.endpoints.userInfo,
1042
+ * providerMetadata.name
1043
+ * );
1044
+ * ```
1045
+ *
1046
+ * Your session strategy should:
1047
+ * 1. Extract user info (using extractUserInfo or decode ID token)
1048
+ * 2. Create or lookup users in your database
1049
+ * 3. Generate session identifiers
1050
+ * 4. Store session data as needed
1051
+ * 5. Return a Session object with token and raw data
1052
+ *
1011
1053
  * @throws \{Error\} If session creation fails (e.g., database error, invalid token)
1012
1054
  *
1013
1055
  * @example
@@ -1020,8 +1062,35 @@ export declare interface SessionStrategy {
1020
1062
  * };
1021
1063
  * }
1022
1064
  * ```
1065
+ *
1066
+ * @example
1067
+ * Database integration with user info extraction:
1068
+ * ```typescript
1069
+ * async createSession(
1070
+ * tokenData: OAuthTokenResponse,
1071
+ * providerMetadata: ProviderMetadata
1072
+ * ): Promise<Session> {
1073
+ * // Extract user info from token or userinfo endpoint
1074
+ * const { userInfo } = await extractUserInfo(
1075
+ * tokenData,
1076
+ * providerMetadata.endpoints.userInfo,
1077
+ * providerMetadata.name
1078
+ * );
1079
+ *
1080
+ * // Create or update user in database
1081
+ * const user = await db.users.upsert({
1082
+ * email: userInfo.email,
1083
+ * name: userInfo.name
1084
+ * });
1085
+ *
1086
+ * return {
1087
+ * token: generateSessionId(),
1088
+ * raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }
1089
+ * };
1090
+ * }
1091
+ * ```
1023
1092
  */
1024
- createSession(tokenData: OAuthTokenResponse): Promise<Session>;
1093
+ createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;
1025
1094
  }
1026
1095
 
1027
1096
  /**
package/dist/index.cjs CHANGED
@@ -88,9 +88,10 @@ var DefaultSessionStrategy = class {
88
88
  * Handles common OAuth token formats and extracts the access token.
89
89
  *
90
90
  * @param tokenData - The token data received from the OAuth provider
91
+ * @param providerMetadata - Provider metadata (not used in default implementation)
91
92
  * @returns A Promise that resolves to a Session object
92
93
  */
93
- async createSession(tokenData) {
94
+ async createSession(tokenData, providerMetadata) {
94
95
  if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
95
96
  throw new Error("No valid access token found in OAuth response");
96
97
  }
@@ -549,7 +550,15 @@ var Lixa = class _Lixa {
549
550
  );
550
551
  this.log("INFO", "Token", "Token exchange successful");
551
552
  this.log("INFO", "Session", "Creating user session");
552
- const session = await this.sessionStrategy.createSession(tokens);
553
+ const providerMetadata = {
554
+ name: providerType,
555
+ endpoints: {
556
+ authorization: providerImpl.authorizationEndpoint,
557
+ token: providerImpl.tokenEndpoint,
558
+ userInfo: providerImpl.userInfoEndpoint
559
+ }
560
+ };
561
+ const session = await this.sessionStrategy.createSession(tokens, providerMetadata);
553
562
  const sessionId = (0, import_crypto.randomBytes)(32).toString("hex");
554
563
  this.log("INFO", "Session", "Storing session", { sessionId });
555
564
  await this.sesionDao.saveSession(sessionId, session, 86400);
@@ -636,7 +645,7 @@ function determineProviderFromIssuer(userInfo) {
636
645
  }
637
646
  return null;
638
647
  }
639
- async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
648
+ async function fetchUserInfo(accessToken, userInfoEndpoint) {
640
649
  const response = await fetch(userInfoEndpoint, {
641
650
  headers: {
642
651
  Authorization: `Bearer ${accessToken}`,
@@ -644,13 +653,11 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
644
653
  }
645
654
  });
646
655
  if (!response.ok) {
647
- const providerLabel = providerName ? ` from ${providerName}` : "";
648
- 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}`);
649
657
  }
650
658
  const data = await response.json();
651
659
  if (!data || typeof data !== "object" || !("email" in data) || typeof data.email !== "string") {
652
- const providerLabel = providerName ? ` from ${providerName}` : "";
653
- throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);
660
+ throw new Error(`Invalid user info response: missing or invalid email`);
654
661
  }
655
662
  return {
656
663
  email: data.email,
@@ -664,41 +671,17 @@ async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
664
671
  iss: "iss" in data ? String(data.iss) : void 0
665
672
  };
666
673
  }
667
- async function extractUserInfo(tokenData, userInfoEndpoint, providerName) {
674
+ async function extractUserInfo(tokenData, providerMetadata) {
668
675
  let userInfo;
669
- let provider = null;
676
+ const userInfoEndpoint = providerMetadata.endpoints.userInfo;
670
677
  if (tokenData.id_token) {
671
678
  userInfo = decodeIdToken(tokenData.id_token);
672
- provider = determineProviderFromIssuer(userInfo);
673
- if (!provider && providerName) {
674
- provider = providerName;
675
- }
676
679
  } else if (tokenData.access_token) {
677
- if ("provider" in tokenData && typeof tokenData.provider === "string") {
678
- provider = tokenData.provider;
679
- } else if (providerName) {
680
- provider = providerName;
681
- }
682
- if (!provider) {
683
- throw new Error(
684
- "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."
685
- );
686
- }
687
- if (!userInfoEndpoint) {
688
- throw new Error(
689
- `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.`
690
- );
691
- }
692
- userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint, provider);
680
+ userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
693
681
  } else {
694
682
  throw new Error("No ID token or access token available to fetch user info");
695
683
  }
696
- if (!provider) {
697
- throw new Error(
698
- "Cannot determine OAuth provider: ID token issuer not recognized and no provider name provided."
699
- );
700
- }
701
- return { userInfo, provider };
684
+ return { userInfo };
702
685
  }
703
686
  // Annotate the CommonJS export names for ESM import in node:
704
687
  0 && (module.exports = {