@vunexa/lixa 0.0.1-alpha.26 → 0.0.1-alpha.28
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 +194 -0
- package/dist/export-types/index.d.ts +182 -14
- package/dist/index.cjs +116 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -15
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +111 -3
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +89 -14
- package/dist/models/session.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/user-info.d.ts +89 -0
- package/dist/utils/user-info.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -545,6 +545,200 @@ const strategy: SessionStrategy = {
|
|
|
545
545
|
};
|
|
546
546
|
```
|
|
547
547
|
|
|
548
|
+
## User Info Utilities
|
|
549
|
+
|
|
550
|
+
Lixa provides utilities to extract user information from OAuth tokens, making it easier to work with user data across different providers.
|
|
551
|
+
|
|
552
|
+
### `extractUserInfo()`
|
|
553
|
+
|
|
554
|
+
High-level function that automatically extracts user information from OAuth token data:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { extractUserInfo, type UserInfo } from "@vunexa/lixa";
|
|
558
|
+
|
|
559
|
+
// In your session strategy
|
|
560
|
+
const sessionStrategy = {
|
|
561
|
+
createSession: async (tokenData) => {
|
|
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
|
+
);
|
|
568
|
+
|
|
569
|
+
console.log(`User ${userInfo.email} authenticated via ${provider}`);
|
|
570
|
+
|
|
571
|
+
// Create user in database
|
|
572
|
+
const user = await db.users.upsert({
|
|
573
|
+
email: userInfo.email,
|
|
574
|
+
name: userInfo.name,
|
|
575
|
+
givenName: userInfo.given_name,
|
|
576
|
+
familyName: userInfo.family_name,
|
|
577
|
+
picture: userInfo.picture,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
token: generateSessionId(),
|
|
582
|
+
raw: { ...tokenData, userId: user.id }
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Parameters:**
|
|
589
|
+
- `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
|
|
592
|
+
|
|
593
|
+
**Returns:**
|
|
594
|
+
- `{ userInfo: UserInfo, provider: string }` - User information and detected provider name
|
|
595
|
+
|
|
596
|
+
**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
|
|
601
|
+
|
|
602
|
+
### `decodeIdToken()`
|
|
603
|
+
|
|
604
|
+
Decode JWT ID tokens to extract user information:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { decodeIdToken, type UserInfo } from "@vunexa/lixa";
|
|
608
|
+
|
|
609
|
+
const userInfo: UserInfo = decodeIdToken(tokenData.id_token);
|
|
610
|
+
console.log(userInfo.email, userInfo.name);
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Use case:** When you know the provider returns an ID token (OIDC providers like Google)
|
|
614
|
+
|
|
615
|
+
### `fetchUserInfo()`
|
|
616
|
+
|
|
617
|
+
Fetch user information from a provider's userinfo endpoint:
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
import { fetchUserInfo, type UserInfo } from "@vunexa/lixa";
|
|
621
|
+
|
|
622
|
+
const userInfo: UserInfo = await fetchUserInfo(
|
|
623
|
+
tokenData.access_token,
|
|
624
|
+
'https://www.googleapis.com/oauth2/v2/userinfo',
|
|
625
|
+
'google' // Optional: for error messages
|
|
626
|
+
);
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Use case:** When you need to fetch user info from the API (OAuth-only providers like GitHub, or when ID token is not available)
|
|
630
|
+
|
|
631
|
+
### `determineProviderFromIssuer()`
|
|
632
|
+
|
|
633
|
+
Detect OAuth provider from ID token issuer field:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
import { determineProviderFromIssuer, decodeIdToken } from "@vunexa/lixa";
|
|
637
|
+
|
|
638
|
+
const userInfo = decodeIdToken(tokenData.id_token);
|
|
639
|
+
const provider = determineProviderFromIssuer(userInfo);
|
|
640
|
+
// Returns: 'google', 'github', or null if unknown
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**Supported providers:**
|
|
644
|
+
- Google: Detects `accounts.google.com` in issuer
|
|
645
|
+
- GitHub: Detects `github` in issuer
|
|
646
|
+
- Returns `null` for unknown issuers
|
|
647
|
+
|
|
648
|
+
### `UserInfo` Type
|
|
649
|
+
|
|
650
|
+
Standard user information structure across all providers:
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
interface UserInfo {
|
|
654
|
+
email: string; // Required: User's email address
|
|
655
|
+
id?: string; // Optional: Provider-specific user ID
|
|
656
|
+
sub?: string; // Optional: Subject identifier (OIDC)
|
|
657
|
+
given_name?: string; // Optional: First name
|
|
658
|
+
family_name?: string; // Optional: Last name
|
|
659
|
+
name?: string; // Optional: Full name
|
|
660
|
+
picture?: string; // Optional: Profile picture URL
|
|
661
|
+
email_verified?: boolean; // Optional: Email verification status
|
|
662
|
+
iss?: string; // Optional: Token issuer (OIDC)
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Complete Example with User Info Utilities
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { Lixa, extractUserInfo, type UserInfo } from "@vunexa/lixa";
|
|
670
|
+
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
|
|
671
|
+
|
|
672
|
+
const lixa = new Lixa({
|
|
673
|
+
providers: {
|
|
674
|
+
google: {
|
|
675
|
+
provider: new GoogleProvider(),
|
|
676
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
677
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
678
|
+
redirectUri: "https://yourapp.com/auth/google/callback",
|
|
679
|
+
scopes: ["openid", "email", "profile"],
|
|
680
|
+
},
|
|
681
|
+
github: {
|
|
682
|
+
provider: new GithubProvider(),
|
|
683
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
684
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
685
|
+
redirectUri: "https://yourapp.com/auth/github/callback",
|
|
686
|
+
scopes: ["user:email"],
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
sessionStrategy: {
|
|
691
|
+
createSession: async (tokenData) => {
|
|
692
|
+
// Extract user info - works for both Google (ID token) and GitHub (API fetch)
|
|
693
|
+
const { userInfo, provider } = await extractUserInfo(tokenData);
|
|
694
|
+
|
|
695
|
+
console.log(`User ${userInfo.email} authenticated via ${provider}`);
|
|
696
|
+
|
|
697
|
+
// Create or update user in database
|
|
698
|
+
const user = await db.users.upsert({
|
|
699
|
+
email: userInfo.email,
|
|
700
|
+
name: userInfo.name || `${userInfo.given_name} ${userInfo.family_name}`.trim(),
|
|
701
|
+
givenName: userInfo.given_name,
|
|
702
|
+
familyName: userInfo.family_name,
|
|
703
|
+
picture: userInfo.picture,
|
|
704
|
+
emailVerified: userInfo.email_verified,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Create session
|
|
708
|
+
const sessionId = generateUniqueId();
|
|
709
|
+
await db.sessions.create({
|
|
710
|
+
id: sessionId,
|
|
711
|
+
userId: user.id,
|
|
712
|
+
provider,
|
|
713
|
+
accessToken: tokenData.access_token,
|
|
714
|
+
refreshToken: tokenData.refresh_token,
|
|
715
|
+
expiresAt: tokenData.expires_in
|
|
716
|
+
? new Date(Date.now() + tokenData.expires_in * 1000)
|
|
717
|
+
: null,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
token: sessionId,
|
|
722
|
+
raw: {
|
|
723
|
+
...tokenData,
|
|
724
|
+
userId: user.id,
|
|
725
|
+
provider,
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Benefits of User Info Utilities
|
|
734
|
+
|
|
735
|
+
✅ **Provider-agnostic** - Works with any OAuth provider
|
|
736
|
+
✅ **Automatic detection** - Determines provider from token data
|
|
737
|
+
✅ **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
|
|
741
|
+
|
|
548
742
|
## API Reference
|
|
549
743
|
|
|
550
744
|
### `Lixa` Class
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
*/
|
|
22
22
|
declare type ConfiguredProviderKey<T extends LixaConfig<Record<string, ProviderConfig>>> = keyof T['providers'];
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Decode JWT ID token to extract user information
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export declare function decodeIdToken(idToken: string): UserInfo;
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* Default session strategy that works with any OAuth provider.
|
|
26
33
|
* Extracts common token information and creates a standardized session.
|
|
@@ -33,11 +40,80 @@ export declare class DefaultSessionStrategy implements SessionStrategy {
|
|
|
33
40
|
* Handles common OAuth token formats and extracts the access token.
|
|
34
41
|
*
|
|
35
42
|
* @param tokenData - The token data received from the OAuth provider
|
|
43
|
+
* @param providerMetadata - Provider metadata (not used in default implementation)
|
|
36
44
|
* @returns A Promise that resolves to a Session object
|
|
37
45
|
*/
|
|
38
|
-
createSession(tokenData: OAuthTokenResponse): Promise<Session>;
|
|
46
|
+
createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Determine OAuth provider from ID token issuer
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export declare function determineProviderFromIssuer(userInfo: UserInfo): string | null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract user info from OAuth token data
|
|
58
|
+
*
|
|
59
|
+
* @param tokenData - OAuth token response from provider
|
|
60
|
+
* @param userInfoEndpoint - Optional userinfo endpoint URL (required if no ID token)
|
|
61
|
+
* @param providerName - Optional provider name for error messages
|
|
62
|
+
* @returns User info and detected provider name
|
|
63
|
+
*
|
|
64
|
+
* @remarks
|
|
65
|
+
* 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 (requires userInfoEndpoint parameter)
|
|
68
|
+
*
|
|
69
|
+
* Provider detection:
|
|
70
|
+
* - Primary: Extract from ID token issuer field
|
|
71
|
+
* - Fallback: Use provider field in token data (if present)
|
|
72
|
+
* - Fallback: Use providerName parameter
|
|
73
|
+
* - Throws error if provider cannot be determined
|
|
74
|
+
*
|
|
75
|
+
* @throws Error if no ID token or access token is available
|
|
76
|
+
* @throws Error if provider cannot be determined
|
|
77
|
+
* @throws Error if userInfoEndpoint is required but not provided
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* With ID token (provider auto-detected):
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const { userInfo, provider } = await extractUserInfo(tokenData);
|
|
83
|
+
* console.log(`User ${userInfo.email} authenticated via ${provider}`);
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* Without ID token (requires userInfoEndpoint):
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const { userInfo, provider } = await extractUserInfo(
|
|
90
|
+
* tokenData,
|
|
91
|
+
* 'https://api.example.com/user',
|
|
92
|
+
* 'custom'
|
|
93
|
+
* );
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export declare function extractUserInfo(tokenData: OAuthTokenResponse, userInfoEndpoint?: string, providerName?: string): Promise<{
|
|
99
|
+
userInfo: UserInfo;
|
|
100
|
+
provider: string;
|
|
101
|
+
}>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fetch user info from OAuth provider's userinfo endpoint
|
|
105
|
+
*
|
|
106
|
+
* @param accessToken - OAuth access token
|
|
107
|
+
* @param userInfoEndpoint - The provider's userinfo endpoint URL
|
|
108
|
+
* @param providerName - Provider name for error messages (optional)
|
|
109
|
+
* @returns User information from the provider
|
|
110
|
+
*
|
|
111
|
+
* @throws Error if the request fails or response is invalid
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
114
|
+
*/
|
|
115
|
+
export declare function fetchUserInfo(accessToken: string, userInfoEndpoint: string, providerName?: string): Promise<UserInfo>;
|
|
116
|
+
|
|
41
117
|
/**
|
|
42
118
|
* Interface for OAuth 2.0 and OpenID Connect provider implementations.
|
|
43
119
|
*
|
|
@@ -694,6 +770,26 @@ export declare type ProviderConfig = {
|
|
|
694
770
|
provider: IProvider;
|
|
695
771
|
});
|
|
696
772
|
|
|
773
|
+
/**
|
|
774
|
+
* Provider metadata passed to session strategy.
|
|
775
|
+
* Contains provider name and endpoints for user info extraction.
|
|
776
|
+
*
|
|
777
|
+
* @public
|
|
778
|
+
*/
|
|
779
|
+
export declare interface ProviderMetadata {
|
|
780
|
+
/** The provider name (e.g., 'google', 'github') */
|
|
781
|
+
name: string;
|
|
782
|
+
/** Provider endpoints */
|
|
783
|
+
endpoints: {
|
|
784
|
+
/** Authorization endpoint URL */
|
|
785
|
+
authorization: string;
|
|
786
|
+
/** Token endpoint URL */
|
|
787
|
+
token: string;
|
|
788
|
+
/** UserInfo endpoint URL */
|
|
789
|
+
userInfo: string;
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
697
793
|
/**
|
|
698
794
|
* Helper type to create a configuration with only registered providers.
|
|
699
795
|
* Use this with Lixa.createConfig() for type safety.
|
|
@@ -867,24 +963,27 @@ export declare interface SessionDao {
|
|
|
867
963
|
* @example
|
|
868
964
|
* Custom session strategy with database integration:
|
|
869
965
|
* ```typescript
|
|
870
|
-
* interface CustomSessionData
|
|
966
|
+
* interface CustomSessionData {
|
|
871
967
|
* userId: string;
|
|
872
968
|
* email: string;
|
|
969
|
+
* provider: string;
|
|
970
|
+
* accessToken: string;
|
|
971
|
+
* refreshToken?: string;
|
|
972
|
+
* expiresAt: number;
|
|
873
973
|
* }
|
|
874
974
|
*
|
|
875
975
|
* class DatabaseSessionStrategy implements SessionStrategy {
|
|
876
976
|
* constructor(private db: Database) {}
|
|
877
977
|
*
|
|
878
|
-
* async createSession(
|
|
879
|
-
* //
|
|
880
|
-
* const
|
|
881
|
-
* const payload = decodeJwt(idToken);
|
|
978
|
+
* async createSession(oauthContext: OAuthContext): Promise<Session<CustomSessionData>> {
|
|
979
|
+
* // User info is already extracted by Lixa!
|
|
980
|
+
* const { userInfo, provider, tokenData } = oauthContext;
|
|
882
981
|
*
|
|
883
982
|
* // Create or update user in database
|
|
884
983
|
* const user = await this.db.users.upsert({
|
|
885
|
-
* email:
|
|
886
|
-
* name:
|
|
887
|
-
* picture:
|
|
984
|
+
* email: userInfo.email,
|
|
985
|
+
* name: userInfo.name,
|
|
986
|
+
* picture: userInfo.picture
|
|
888
987
|
* });
|
|
889
988
|
*
|
|
890
989
|
* // Generate session ID
|
|
@@ -904,7 +1003,10 @@ export declare interface SessionDao {
|
|
|
904
1003
|
* raw: {
|
|
905
1004
|
* userId: user.id,
|
|
906
1005
|
* email: user.email,
|
|
907
|
-
*
|
|
1006
|
+
* provider,
|
|
1007
|
+
* accessToken: tokenData.access_token,
|
|
1008
|
+
* refreshToken: tokenData.refresh_token,
|
|
1009
|
+
* expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000
|
|
908
1010
|
* }
|
|
909
1011
|
* };
|
|
910
1012
|
* }
|
|
@@ -918,14 +1020,14 @@ export declare interface SessionStrategy {
|
|
|
918
1020
|
* Creates a session from OAuth token data.
|
|
919
1021
|
*
|
|
920
1022
|
* @param tokenData - The token data received from the OAuth provider's token endpoint
|
|
1023
|
+
* @param providerMetadata - Provider metadata including name and endpoints
|
|
921
1024
|
* @returns A Promise that resolves to a Session object
|
|
922
1025
|
*
|
|
923
1026
|
* @remarks
|
|
924
1027
|
* This method is called after successfully exchanging the authorization code
|
|
925
|
-
* for tokens.
|
|
926
|
-
* provider's token endpoint.
|
|
1028
|
+
* for tokens. You receive:
|
|
927
1029
|
*
|
|
928
|
-
*
|
|
1030
|
+
* Token Data:
|
|
929
1031
|
* - access_token: OAuth access token
|
|
930
1032
|
* - refresh_token: OAuth refresh token (optional)
|
|
931
1033
|
* - expires_in: Token expiration time in seconds
|
|
@@ -933,6 +1035,28 @@ export declare interface SessionStrategy {
|
|
|
933
1035
|
* - id_token: OpenID Connect ID token (for OIDC providers)
|
|
934
1036
|
* - scope: Granted scopes
|
|
935
1037
|
*
|
|
1038
|
+
* Provider Metadata:
|
|
1039
|
+
* - name: The provider name (e.g., 'google', 'github')
|
|
1040
|
+
* - endpoints: Provider endpoints (authorization, token, userInfo)
|
|
1041
|
+
*
|
|
1042
|
+
* The providerMetadata.endpoints.userInfo can be used with extractUserInfo():
|
|
1043
|
+
* ```typescript
|
|
1044
|
+
* import { extractUserInfo } from '@vunexa/lixa';
|
|
1045
|
+
*
|
|
1046
|
+
* const { userInfo } = await extractUserInfo(
|
|
1047
|
+
* tokenData,
|
|
1048
|
+
* providerMetadata.endpoints.userInfo,
|
|
1049
|
+
* providerMetadata.name
|
|
1050
|
+
* );
|
|
1051
|
+
* ```
|
|
1052
|
+
*
|
|
1053
|
+
* Your session strategy should:
|
|
1054
|
+
* 1. Extract user info (using extractUserInfo or decode ID token)
|
|
1055
|
+
* 2. Create or lookup users in your database
|
|
1056
|
+
* 3. Generate session identifiers
|
|
1057
|
+
* 4. Store session data as needed
|
|
1058
|
+
* 5. Return a Session object with token and raw data
|
|
1059
|
+
*
|
|
936
1060
|
* @throws \{Error\} If session creation fails (e.g., database error, invalid token)
|
|
937
1061
|
*
|
|
938
1062
|
* @example
|
|
@@ -945,8 +1069,35 @@ export declare interface SessionStrategy {
|
|
|
945
1069
|
* };
|
|
946
1070
|
* }
|
|
947
1071
|
* ```
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* Database integration with user info extraction:
|
|
1075
|
+
* ```typescript
|
|
1076
|
+
* async createSession(
|
|
1077
|
+
* tokenData: OAuthTokenResponse,
|
|
1078
|
+
* providerMetadata: ProviderMetadata
|
|
1079
|
+
* ): Promise<Session> {
|
|
1080
|
+
* // Extract user info from token or userinfo endpoint
|
|
1081
|
+
* const { userInfo } = await extractUserInfo(
|
|
1082
|
+
* tokenData,
|
|
1083
|
+
* providerMetadata.endpoints.userInfo,
|
|
1084
|
+
* providerMetadata.name
|
|
1085
|
+
* );
|
|
1086
|
+
*
|
|
1087
|
+
* // Create or update user in database
|
|
1088
|
+
* const user = await db.users.upsert({
|
|
1089
|
+
* email: userInfo.email,
|
|
1090
|
+
* name: userInfo.name
|
|
1091
|
+
* });
|
|
1092
|
+
*
|
|
1093
|
+
* return {
|
|
1094
|
+
* token: generateSessionId(),
|
|
1095
|
+
* raw: { userId: user.id, provider: providerMetadata.name, ...tokenData }
|
|
1096
|
+
* };
|
|
1097
|
+
* }
|
|
1098
|
+
* ```
|
|
948
1099
|
*/
|
|
949
|
-
createSession(tokenData: OAuthTokenResponse): Promise<Session>;
|
|
1100
|
+
createSession(tokenData: OAuthTokenResponse, providerMetadata: ProviderMetadata): Promise<Session>;
|
|
950
1101
|
}
|
|
951
1102
|
|
|
952
1103
|
/**
|
|
@@ -1096,4 +1247,21 @@ export declare interface StateData {
|
|
|
1096
1247
|
createdAt: number;
|
|
1097
1248
|
}
|
|
1098
1249
|
|
|
1250
|
+
/**
|
|
1251
|
+
* User information extracted from OAuth provider
|
|
1252
|
+
*
|
|
1253
|
+
* @public
|
|
1254
|
+
*/
|
|
1255
|
+
export declare interface UserInfo {
|
|
1256
|
+
email: string;
|
|
1257
|
+
id?: string | undefined;
|
|
1258
|
+
sub?: string | undefined;
|
|
1259
|
+
given_name?: string | undefined;
|
|
1260
|
+
family_name?: string | undefined;
|
|
1261
|
+
name?: string | undefined;
|
|
1262
|
+
picture?: string | undefined;
|
|
1263
|
+
email_verified?: boolean | undefined;
|
|
1264
|
+
iss?: string | undefined;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1099
1267
|
export { }
|
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
DefaultSessionStrategy: () => DefaultSessionStrategy,
|
|
34
|
-
Lixa: () => Lixa
|
|
34
|
+
Lixa: () => Lixa,
|
|
35
|
+
decodeIdToken: () => decodeIdToken,
|
|
36
|
+
determineProviderFromIssuer: () => determineProviderFromIssuer,
|
|
37
|
+
extractUserInfo: () => extractUserInfo,
|
|
38
|
+
fetchUserInfo: () => fetchUserInfo
|
|
35
39
|
});
|
|
36
40
|
module.exports = __toCommonJS(src_exports);
|
|
37
41
|
|
|
@@ -84,9 +88,10 @@ var DefaultSessionStrategy = class {
|
|
|
84
88
|
* Handles common OAuth token formats and extracts the access token.
|
|
85
89
|
*
|
|
86
90
|
* @param tokenData - The token data received from the OAuth provider
|
|
91
|
+
* @param providerMetadata - Provider metadata (not used in default implementation)
|
|
87
92
|
* @returns A Promise that resolves to a Session object
|
|
88
93
|
*/
|
|
89
|
-
async createSession(tokenData) {
|
|
94
|
+
async createSession(tokenData, providerMetadata) {
|
|
90
95
|
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
91
96
|
throw new Error("No valid access token found in OAuth response");
|
|
92
97
|
}
|
|
@@ -545,7 +550,15 @@ var Lixa = class _Lixa {
|
|
|
545
550
|
);
|
|
546
551
|
this.log("INFO", "Token", "Token exchange successful");
|
|
547
552
|
this.log("INFO", "Session", "Creating user session");
|
|
548
|
-
const
|
|
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);
|
|
549
562
|
const sessionId = (0, import_crypto.randomBytes)(32).toString("hex");
|
|
550
563
|
this.log("INFO", "Session", "Storing session", { sessionId });
|
|
551
564
|
await this.sesionDao.saveSession(sessionId, session, 86400);
|
|
@@ -601,9 +614,108 @@ var Lixa = class _Lixa {
|
|
|
601
614
|
return void 0;
|
|
602
615
|
}
|
|
603
616
|
};
|
|
617
|
+
|
|
618
|
+
// src/utils/user-info.ts
|
|
619
|
+
function decodeIdToken(idToken) {
|
|
620
|
+
const parts = idToken.split(".");
|
|
621
|
+
if (parts.length !== 3) {
|
|
622
|
+
throw new Error("Invalid ID token format: expected 3 parts separated by dots");
|
|
623
|
+
}
|
|
624
|
+
const base64Payload = parts[1];
|
|
625
|
+
if (!base64Payload) {
|
|
626
|
+
throw new Error("Invalid ID token: missing payload section");
|
|
627
|
+
}
|
|
628
|
+
const payload = Buffer.from(base64Payload, "base64").toString();
|
|
629
|
+
try {
|
|
630
|
+
return JSON.parse(payload);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
throw new Error("Invalid ID token: failed to parse payload JSON");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function determineProviderFromIssuer(userInfo) {
|
|
636
|
+
if (!userInfo.iss) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
const issuer = userInfo.iss.toLowerCase();
|
|
640
|
+
if (issuer.includes("accounts.google.com")) {
|
|
641
|
+
return "google";
|
|
642
|
+
}
|
|
643
|
+
if (issuer.includes("github")) {
|
|
644
|
+
return "github";
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
async function fetchUserInfo(accessToken, userInfoEndpoint, providerName) {
|
|
649
|
+
const response = await fetch(userInfoEndpoint, {
|
|
650
|
+
headers: {
|
|
651
|
+
Authorization: `Bearer ${accessToken}`,
|
|
652
|
+
Accept: "application/json"
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
if (!response.ok) {
|
|
656
|
+
const providerLabel = providerName ? ` from ${providerName}` : "";
|
|
657
|
+
throw new Error(`Failed to fetch user info${providerLabel}: ${response.status} ${response.statusText}`);
|
|
658
|
+
}
|
|
659
|
+
const data = await response.json();
|
|
660
|
+
if (!data || typeof data !== "object" || !("email" in data) || typeof data.email !== "string") {
|
|
661
|
+
const providerLabel = providerName ? ` from ${providerName}` : "";
|
|
662
|
+
throw new Error(`Invalid user info response${providerLabel}: missing or invalid email`);
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
email: data.email,
|
|
666
|
+
id: "id" in data ? String(data.id) : void 0,
|
|
667
|
+
sub: "sub" in data ? String(data.sub) : void 0,
|
|
668
|
+
given_name: "given_name" in data ? String(data.given_name) : void 0,
|
|
669
|
+
family_name: "family_name" in data ? String(data.family_name) : void 0,
|
|
670
|
+
name: "name" in data ? String(data.name) : void 0,
|
|
671
|
+
picture: "picture" in data ? String(data.picture) : void 0,
|
|
672
|
+
email_verified: "email_verified" in data ? Boolean(data.email_verified) : void 0,
|
|
673
|
+
iss: "iss" in data ? String(data.iss) : void 0
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
async function extractUserInfo(tokenData, userInfoEndpoint, providerName) {
|
|
677
|
+
let userInfo;
|
|
678
|
+
let provider = null;
|
|
679
|
+
if (tokenData.id_token) {
|
|
680
|
+
userInfo = decodeIdToken(tokenData.id_token);
|
|
681
|
+
provider = determineProviderFromIssuer(userInfo);
|
|
682
|
+
if (!provider && providerName) {
|
|
683
|
+
provider = providerName;
|
|
684
|
+
}
|
|
685
|
+
} else if (tokenData.access_token) {
|
|
686
|
+
if ("provider" in tokenData && typeof tokenData.provider === "string") {
|
|
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);
|
|
702
|
+
} else {
|
|
703
|
+
throw new Error("No ID token or access token available to fetch user info");
|
|
704
|
+
}
|
|
705
|
+
if (!provider) {
|
|
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 };
|
|
711
|
+
}
|
|
604
712
|
// Annotate the CommonJS export names for ESM import in node:
|
|
605
713
|
0 && (module.exports = {
|
|
606
714
|
DefaultSessionStrategy,
|
|
607
|
-
Lixa
|
|
715
|
+
Lixa,
|
|
716
|
+
decodeIdToken,
|
|
717
|
+
determineProviderFromIssuer,
|
|
718
|
+
extractUserInfo,
|
|
719
|
+
fetchUserInfo
|
|
608
720
|
});
|
|
609
721
|
//# sourceMappingURL=index.cjs.map
|