@vunexa/lixa 0.0.1-alpha.20 → 0.0.1-alpha.21
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 +450 -44
- package/dist/dao/types.d.ts +239 -5
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +785 -43
- package/dist/index.cjs +277 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +787 -40
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +277 -41
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +185 -16
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +107 -4
- package/dist/models/session.d.ts.map +1 -1
- package/dist/providers/IProvider.d.ts +121 -4
- package/dist/providers/IProvider.d.ts.map +1 -1
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/types.d.ts +129 -14
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -16
- package/dist/IProvider-2tDwRAnH.d.cts +0 -18
- package/dist/IProvider-2tDwRAnH.d.ts +0 -18
- package/dist/export-types/providers.d.ts +0 -61
- package/dist/providers/github.d.ts +0 -13
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/google.d.ts +0 -13
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers-entry.cjs +0 -48
- package/dist/providers-entry.cjs.map +0 -1
- package/dist/providers-entry.d.cts +0 -25
- package/dist/providers-entry.d.ts +0 -22
- package/dist/providers-entry.d.ts.map +0 -1
- package/dist/providers-entry.js +0 -20
- package/dist/providers-entry.js.map +0 -1
package/README.md
CHANGED
|
@@ -33,14 +33,22 @@ yarn add @vunexa/lixa
|
|
|
33
33
|
|
|
34
34
|
## Quick Start
|
|
35
35
|
|
|
36
|
-
### 1.
|
|
36
|
+
### 1. Install packages
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @vunexa/lixa @vunexa/lixa-providers
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Configure Lixa with providers
|
|
37
43
|
|
|
38
44
|
```typescript
|
|
39
45
|
import { Lixa } from "@vunexa/lixa";
|
|
46
|
+
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
|
|
40
47
|
|
|
41
48
|
const lixa = new Lixa({
|
|
42
49
|
providers: {
|
|
43
50
|
google: {
|
|
51
|
+
provider: new GoogleProvider(),
|
|
44
52
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
45
53
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
46
54
|
redirectUri: "https://yourapp.com/auth/google/callback",
|
|
@@ -51,11 +59,11 @@ const lixa = new Lixa({
|
|
|
51
59
|
},
|
|
52
60
|
},
|
|
53
61
|
github: {
|
|
62
|
+
provider: new GithubProvider(),
|
|
54
63
|
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
55
64
|
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
56
65
|
redirectUri: "https://yourapp.com/auth/github/callback",
|
|
57
66
|
scopes: ["read:user", "user:email"],
|
|
58
|
-
extraConfig: {},
|
|
59
67
|
},
|
|
60
68
|
},
|
|
61
69
|
|
|
@@ -72,22 +80,22 @@ const lixa = new Lixa({
|
|
|
72
80
|
});
|
|
73
81
|
```
|
|
74
82
|
|
|
75
|
-
###
|
|
83
|
+
### 3. Redirect users to the provider's authorization URL
|
|
76
84
|
|
|
77
85
|
```typescript
|
|
78
86
|
app.get("/login", (req, res) => {
|
|
79
87
|
const provider = req.query.provider as string; // 'google' or 'github'
|
|
80
|
-
const state =
|
|
88
|
+
const state = Lixa.generateRandomState();
|
|
81
89
|
|
|
82
90
|
// Store state in session for validation
|
|
83
91
|
req.session.oauthState = state;
|
|
84
92
|
|
|
85
|
-
const authUrl = lixa.getAuthUrl(provider
|
|
93
|
+
const authUrl = lixa.getAuthUrl(provider, state);
|
|
86
94
|
res.redirect(authUrl);
|
|
87
95
|
});
|
|
88
96
|
```
|
|
89
97
|
|
|
90
|
-
###
|
|
98
|
+
### 4. Handle the provider callback and establish a session
|
|
91
99
|
|
|
92
100
|
```typescript
|
|
93
101
|
app.get("/auth/:provider/callback", async (req, res) => {
|
|
@@ -100,14 +108,17 @@ app.get("/auth/:provider/callback", async (req, res) => {
|
|
|
100
108
|
throw new Error("Invalid state parameter");
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
const
|
|
111
|
+
const sessionId = await lixa.handleCallback({
|
|
104
112
|
provider,
|
|
105
113
|
code: code as string,
|
|
106
114
|
state: state as string,
|
|
107
115
|
});
|
|
108
116
|
|
|
109
|
-
// Session established
|
|
110
|
-
|
|
117
|
+
// Session established - retrieve session info if needed
|
|
118
|
+
const session = await lixa.fetchSessionInfo(sessionId);
|
|
119
|
+
|
|
120
|
+
// Store session ID in cookie
|
|
121
|
+
res.cookie("session_id", sessionId, {
|
|
111
122
|
httpOnly: true,
|
|
112
123
|
secure: true,
|
|
113
124
|
sameSite: "strict",
|
|
@@ -121,6 +132,118 @@ app.get("/auth/:provider/callback", async (req, res) => {
|
|
|
121
132
|
});
|
|
122
133
|
```
|
|
123
134
|
|
|
135
|
+
## Provider Configuration
|
|
136
|
+
|
|
137
|
+
### Using Built-in Providers
|
|
138
|
+
|
|
139
|
+
Built-in providers are available in the `@vunexa/lixa-providers` package:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Lixa } from "@vunexa/lixa";
|
|
143
|
+
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
|
|
144
|
+
|
|
145
|
+
const lixa = new Lixa({
|
|
146
|
+
providers: {
|
|
147
|
+
google: {
|
|
148
|
+
provider: new GoogleProvider(),
|
|
149
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
150
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
151
|
+
redirectUri: "https://yourapp.com/auth/google/callback",
|
|
152
|
+
scopes: ["openid", "email", "profile"]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Using Custom Inline Providers
|
|
159
|
+
|
|
160
|
+
You can create custom providers by implementing the `IProvider` interface:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { Lixa, IProvider } from "@vunexa/lixa";
|
|
164
|
+
|
|
165
|
+
// Define your custom provider
|
|
166
|
+
const customProvider: IProvider = {
|
|
167
|
+
authorizationEndpoint: "https://provider.com/oauth/authorize",
|
|
168
|
+
tokenEndpoint: "https://provider.com/oauth/token",
|
|
169
|
+
userInfoEndpoint: "https://provider.com/api/user"
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Use it directly in configuration
|
|
173
|
+
const lixa = new Lixa({
|
|
174
|
+
providers: {
|
|
175
|
+
custom: {
|
|
176
|
+
provider: customProvider,
|
|
177
|
+
clientId: "your-client-id",
|
|
178
|
+
clientSecret: "your-client-secret",
|
|
179
|
+
redirectUri: "https://yourapp.com/auth/custom/callback",
|
|
180
|
+
scopes: ["read:user"]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Mixing Built-in and Custom Providers
|
|
187
|
+
|
|
188
|
+
You can use both built-in and custom providers in the same configuration:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { Lixa, IProvider } from "@vunexa/lixa";
|
|
192
|
+
import { GoogleProvider } from "@vunexa/lixa-providers";
|
|
193
|
+
|
|
194
|
+
const customProvider: IProvider = {
|
|
195
|
+
authorizationEndpoint: "https://custom.com/oauth/authorize",
|
|
196
|
+
tokenEndpoint: "https://custom.com/oauth/token",
|
|
197
|
+
userInfoEndpoint: "https://custom.com/api/user"
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const lixa = new Lixa({
|
|
201
|
+
providers: {
|
|
202
|
+
google: {
|
|
203
|
+
provider: new GoogleProvider(),
|
|
204
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
205
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
206
|
+
redirectUri: "https://yourapp.com/auth/google/callback",
|
|
207
|
+
scopes: ["openid", "email", "profile"]
|
|
208
|
+
},
|
|
209
|
+
custom: {
|
|
210
|
+
provider: customProvider,
|
|
211
|
+
clientId: process.env.CUSTOM_CLIENT_ID!,
|
|
212
|
+
clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
|
|
213
|
+
redirectUri: "https://yourapp.com/auth/custom/callback",
|
|
214
|
+
scopes: ["read:user"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Overriding Built-in Providers (Testing)
|
|
221
|
+
|
|
222
|
+
You can override built-in providers with custom implementations for testing:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { Lixa, IProvider } from "@vunexa/lixa";
|
|
226
|
+
|
|
227
|
+
// Mock provider for testing
|
|
228
|
+
const mockGoogleProvider: IProvider = {
|
|
229
|
+
authorizationEndpoint: "http://localhost:3000/mock/authorize",
|
|
230
|
+
tokenEndpoint: "http://localhost:3000/mock/token",
|
|
231
|
+
userInfoEndpoint: "http://localhost:3000/mock/userinfo"
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const lixa = new Lixa({
|
|
235
|
+
providers: {
|
|
236
|
+
google: {
|
|
237
|
+
provider: mockGoogleProvider, // Override with mock
|
|
238
|
+
clientId: "test-client-id",
|
|
239
|
+
clientSecret: "test-client-secret",
|
|
240
|
+
redirectUri: "http://localhost:3000/callback",
|
|
241
|
+
scopes: ["openid", "email"]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
124
247
|
## Advanced Usage
|
|
125
248
|
|
|
126
249
|
### Custom State Storage (StateDao)
|
|
@@ -275,9 +398,11 @@ Debug mode logs:
|
|
|
275
398
|
- Session creation
|
|
276
399
|
- Error details from OAuth providers
|
|
277
400
|
|
|
278
|
-
###
|
|
401
|
+
### Legacy Provider Registration (Deprecated)
|
|
402
|
+
|
|
403
|
+
> **Note**: This pattern is deprecated. Use inline providers instead (see examples above).
|
|
279
404
|
|
|
280
|
-
|
|
405
|
+
For backward compatibility, you can still register providers globally:
|
|
281
406
|
|
|
282
407
|
```typescript
|
|
283
408
|
import { Lixa, IProvider } from "@vunexa/lixa";
|
|
@@ -293,7 +418,7 @@ Lixa.registerProvider({
|
|
|
293
418
|
custom: new CustomProvider(),
|
|
294
419
|
});
|
|
295
420
|
|
|
296
|
-
// Use it in your configuration
|
|
421
|
+
// Use it in your configuration (without provider field)
|
|
297
422
|
const lixa = new Lixa({
|
|
298
423
|
providers: {
|
|
299
424
|
custom: {
|
|
@@ -306,17 +431,17 @@ const lixa = new Lixa({
|
|
|
306
431
|
});
|
|
307
432
|
```
|
|
308
433
|
|
|
309
|
-
### Check Provider
|
|
434
|
+
### Check Provider Configuration
|
|
310
435
|
|
|
311
436
|
```typescript
|
|
312
|
-
// Check if a provider is
|
|
313
|
-
if (
|
|
314
|
-
console.log("Google provider is
|
|
437
|
+
// Check if a provider is configured for this instance
|
|
438
|
+
if (lixa.isProviderConfigured("google")) {
|
|
439
|
+
console.log("Google provider is configured");
|
|
315
440
|
}
|
|
316
441
|
|
|
317
|
-
// Get list of all registered providers
|
|
442
|
+
// Get list of all registered providers (legacy)
|
|
318
443
|
const providers = Lixa.getRegisteredProviders();
|
|
319
|
-
console.log("
|
|
444
|
+
console.log("Registered providers:", providers);
|
|
320
445
|
```
|
|
321
446
|
|
|
322
447
|
## Common Pitfalls & Solutions
|
|
@@ -395,34 +520,38 @@ createSession: async (tokenData) => {
|
|
|
395
520
|
|
|
396
521
|
#### Static Methods
|
|
397
522
|
|
|
398
|
-
- `Lixa.
|
|
399
|
-
- `Lixa.
|
|
400
|
-
- `Lixa.getRegisteredProviders(): string[]` - Get list of all registered provider names
|
|
401
|
-
- `Lixa.generateRandomState(): string` - Generate a cryptographically secure random state string
|
|
523
|
+
- `Lixa.generateRandomState(): string` - Generate a cryptographically secure random state string for CSRF protection
|
|
524
|
+
- `Lixa.registerProvider(providerMap: { [key: string]: IProvider })` - **[Deprecated]** Register custom providers globally. Use inline providers instead.
|
|
525
|
+
- `Lixa.getRegisteredProviders(): string[]` - **[Deprecated]** Get list of all registered provider names from legacy registry
|
|
402
526
|
|
|
403
527
|
#### Instance Methods
|
|
404
528
|
|
|
405
|
-
- `getAuthUrl(provider: string, state: string): string` - Generate authorization URL for a provider. Automatically stores state with
|
|
406
|
-
- `handleCallback({ provider, code, state }): Promise<string>` - Handle OAuth callback, validate state, exchange code for tokens, and create session. Returns session ID.
|
|
407
|
-
- `fetchSessionInfo(sessionId: string): Promise<Session | null>` - Retrieve session information by session ID
|
|
529
|
+
- `getAuthUrl(provider: string, state: string): string` - Generate authorization URL for a provider. Automatically generates PKCE code verifier and challenge, stores state with code verifier for later validation.
|
|
530
|
+
- `handleCallback({ provider, code, state }): Promise<string>` - Handle OAuth callback, validate state parameter, retrieve PKCE code verifier, exchange authorization code for tokens, and create session using SessionStrategy. Returns session ID.
|
|
531
|
+
- `fetchSessionInfo(sessionId: string): Promise<Session | null>` - Retrieve session information by session ID from SessionDao.
|
|
532
|
+
- `isProviderConfigured(provider: string): boolean` - Check if a provider is configured for this Lixa instance. Useful for type guards and runtime validation.
|
|
408
533
|
|
|
409
534
|
### Types
|
|
410
535
|
|
|
411
536
|
```typescript
|
|
412
|
-
|
|
537
|
+
// Provider configuration with inline provider support
|
|
538
|
+
type ProviderConfig = {
|
|
413
539
|
clientId: string;
|
|
414
540
|
clientSecret: string;
|
|
415
541
|
redirectUri: string;
|
|
416
542
|
scopes: string[];
|
|
417
|
-
extraConfig?: Record<string, any>;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
543
|
+
extraConfig?: Record<string, any>;
|
|
544
|
+
} & (
|
|
545
|
+
| { provider?: never } // Built-in provider (no provider field)
|
|
546
|
+
| { provider: IProvider } // Custom provider (provider field required)
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
interface LixaConfig<TProviders = Record<string, ProviderConfig>> {
|
|
550
|
+
providers: TProviders;
|
|
422
551
|
sessionStrategy?: SessionStrategy;
|
|
423
|
-
stateDao?: StateDao;
|
|
424
|
-
sessionDao?: SessionDao;
|
|
425
|
-
debug?: boolean;
|
|
552
|
+
stateDao?: StateDao;
|
|
553
|
+
sessionDao?: SessionDao;
|
|
554
|
+
debug?: boolean;
|
|
426
555
|
}
|
|
427
556
|
|
|
428
557
|
interface SessionStrategy {
|
|
@@ -430,19 +559,25 @@ interface SessionStrategy {
|
|
|
430
559
|
}
|
|
431
560
|
|
|
432
561
|
interface Session {
|
|
433
|
-
token: string;
|
|
434
|
-
raw: any;
|
|
562
|
+
token: string;
|
|
563
|
+
raw: any;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
interface StateData {
|
|
567
|
+
provider: string; // Provider name for routing
|
|
568
|
+
codeVerifier: string; // PKCE code verifier (64 hex chars)
|
|
569
|
+
createdAt: number; // Unix timestamp in milliseconds
|
|
435
570
|
}
|
|
436
571
|
|
|
437
572
|
interface StateDao {
|
|
438
|
-
saveState(state: string, data:
|
|
439
|
-
getState(state: string): Promise<
|
|
573
|
+
saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void>;
|
|
574
|
+
getState(state: string): Promise<StateData | null>;
|
|
440
575
|
deleteState(state: string): Promise<void>;
|
|
441
576
|
}
|
|
442
577
|
|
|
443
578
|
interface SessionDao {
|
|
444
|
-
saveSession(sessionId: string, session:
|
|
445
|
-
getSession(sessionId: string): Promise<
|
|
579
|
+
saveSession(sessionId: string, session: any, expiresInSeconds: number): Promise<void>;
|
|
580
|
+
getSession(sessionId: string): Promise<any | null>;
|
|
446
581
|
deleteSession(sessionId: string): Promise<void>;
|
|
447
582
|
}
|
|
448
583
|
|
|
@@ -455,21 +590,292 @@ interface IProvider {
|
|
|
455
590
|
|
|
456
591
|
## Built-in Providers
|
|
457
592
|
|
|
458
|
-
|
|
593
|
+
Built-in providers are available in the `@vunexa/lixa-providers` package:
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
npm install @vunexa/lixa-providers
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Google (`GoogleProvider`)
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
import { GoogleProvider } from "@vunexa/lixa-providers";
|
|
603
|
+
```
|
|
604
|
+
|
|
459
605
|
- **Type**: OAuth 2.0 + OpenID Connect (OIDC)
|
|
460
606
|
- **Authorization Endpoint**: `https://accounts.google.com/o/oauth2/v2/auth`
|
|
461
607
|
- **Token Endpoint**: `https://oauth2.googleapis.com/token`
|
|
462
608
|
- **User Info Endpoint**: `https://www.googleapis.com/oauth2/v2/userinfo`
|
|
463
609
|
- **Supports**: PKCE, ID tokens, refresh tokens
|
|
464
|
-
- **Common Scopes**:
|
|
610
|
+
- **Common Scopes**:
|
|
611
|
+
- `openid` - Required for OpenID Connect
|
|
612
|
+
- `email` - Access to user's email address
|
|
613
|
+
- `profile` - Access to user's basic profile information
|
|
614
|
+
- `https://www.googleapis.com/auth/drive.readonly` - Read-only access to Google Drive
|
|
615
|
+
- `https://www.googleapis.com/auth/calendar.readonly` - Read-only access to Google Calendar
|
|
616
|
+
|
|
617
|
+
[Full list of Google OAuth scopes](https://developers.google.com/identity/protocols/oauth2/scopes)
|
|
618
|
+
|
|
619
|
+
### GitHub (`GithubProvider`)
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
import { GithubProvider } from "@vunexa/lixa-providers";
|
|
623
|
+
```
|
|
465
624
|
|
|
466
|
-
### GitHub
|
|
467
625
|
- **Type**: OAuth 2.0
|
|
468
626
|
- **Authorization Endpoint**: `https://github.com/login/oauth/authorize`
|
|
469
627
|
- **Token Endpoint**: `https://github.com/login/oauth/access_token`
|
|
470
628
|
- **User Info Endpoint**: `https://api.github.com/user`
|
|
471
629
|
- **Supports**: PKCE
|
|
472
|
-
- **Common Scopes**:
|
|
630
|
+
- **Common Scopes**:
|
|
631
|
+
- `user` - Read/write access to profile info
|
|
632
|
+
- `user:email` - Read access to user's email addresses
|
|
633
|
+
- `read:user` - Read-only access to profile info
|
|
634
|
+
- `repo` - Full control of private repositories
|
|
635
|
+
- `public_repo` - Access to public repositories
|
|
636
|
+
- `gist` - Create gists
|
|
637
|
+
- `read:org` - Read-only access to organization membership
|
|
638
|
+
|
|
639
|
+
[Full list of GitHub OAuth scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps)
|
|
640
|
+
|
|
641
|
+
## Extensibility Interfaces
|
|
642
|
+
|
|
643
|
+
### SessionStrategy Interface
|
|
644
|
+
|
|
645
|
+
The `SessionStrategy` interface allows you to customize how OAuth tokens are converted into application sessions:
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
interface SessionStrategy {
|
|
649
|
+
createSession(tokenData: any): Promise<Session>;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
interface Session {
|
|
653
|
+
token: string; // Your session identifier
|
|
654
|
+
raw: any; // Additional session data
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Use cases:**
|
|
659
|
+
- Decode ID tokens for user information
|
|
660
|
+
- Create or update users in your database
|
|
661
|
+
- Generate custom session identifiers
|
|
662
|
+
- Store tokens securely
|
|
663
|
+
- Add custom claims or metadata
|
|
664
|
+
|
|
665
|
+
**Example with database integration:**
|
|
666
|
+
```typescript
|
|
667
|
+
const dbSessionStrategy: SessionStrategy = {
|
|
668
|
+
createSession: async (tokenData) => {
|
|
669
|
+
// Decode ID token (for OIDC providers)
|
|
670
|
+
const payload = decodeJwt(tokenData.id_token);
|
|
671
|
+
|
|
672
|
+
// Create/update user
|
|
673
|
+
const user = await db.users.upsert({
|
|
674
|
+
email: payload.email,
|
|
675
|
+
name: payload.name
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Generate session
|
|
679
|
+
const sessionId = generateId();
|
|
680
|
+
await db.sessions.create({
|
|
681
|
+
id: sessionId,
|
|
682
|
+
userId: user.id,
|
|
683
|
+
accessToken: tokenData.access_token,
|
|
684
|
+
refreshToken: tokenData.refresh_token
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
token: sessionId,
|
|
689
|
+
raw: { userId: user.id, ...tokenData }
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### StateDao Interface
|
|
696
|
+
|
|
697
|
+
The `StateDao` interface manages OAuth state storage for CSRF protection and PKCE:
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
interface StateDao {
|
|
701
|
+
saveState(state: string, data: StateData, expiresInSeconds: number): Promise<void>;
|
|
702
|
+
getState(state: string): Promise<StateData | null>;
|
|
703
|
+
deleteState(state: string): Promise<void>;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
interface StateData {
|
|
707
|
+
provider: string; // Provider name for routing
|
|
708
|
+
codeVerifier: string; // PKCE code verifier (64 hex chars)
|
|
709
|
+
createdAt: number; // Unix timestamp in milliseconds
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**Required fields:**
|
|
714
|
+
- `provider`: Used to route callbacks to the correct provider configuration
|
|
715
|
+
- `codeVerifier`: Required for PKCE token exchange (RFC 7636)
|
|
716
|
+
- `createdAt`: Timestamp for debugging and validation
|
|
717
|
+
|
|
718
|
+
**Example with Redis:**
|
|
719
|
+
```typescript
|
|
720
|
+
const redisStateDao: StateDao = {
|
|
721
|
+
saveState: async (state, data, expiresInSeconds) => {
|
|
722
|
+
await redis.setex(
|
|
723
|
+
`oauth:state:${state}`,
|
|
724
|
+
expiresInSeconds,
|
|
725
|
+
JSON.stringify(data)
|
|
726
|
+
);
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
getState: async (state) => {
|
|
730
|
+
const data = await redis.get(`oauth:state:${state}`);
|
|
731
|
+
return data ? JSON.parse(data) : null;
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
deleteState: async (state) => {
|
|
735
|
+
await redis.del(`oauth:state:${state}`);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### SessionDao Interface
|
|
741
|
+
|
|
742
|
+
The `SessionDao` interface manages persistent user session storage:
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
interface SessionDao {
|
|
746
|
+
saveSession(sessionId: string, data: any, expiresInSeconds: number): Promise<void>;
|
|
747
|
+
getSession(sessionId: string): Promise<any | null>;
|
|
748
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**Example with database:**
|
|
753
|
+
```typescript
|
|
754
|
+
const dbSessionDao: SessionDao = {
|
|
755
|
+
saveSession: async (sessionId, data, expiresInSeconds) => {
|
|
756
|
+
const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
|
|
757
|
+
await db.sessions.create({
|
|
758
|
+
id: sessionId,
|
|
759
|
+
data: JSON.stringify(data),
|
|
760
|
+
expiresAt
|
|
761
|
+
});
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
getSession: async (sessionId) => {
|
|
765
|
+
const session = await db.sessions.findOne({
|
|
766
|
+
id: sessionId,
|
|
767
|
+
expiresAt: { $gt: new Date() }
|
|
768
|
+
});
|
|
769
|
+
return session ? JSON.parse(session.data) : null;
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
deleteSession: async (sessionId) => {
|
|
773
|
+
await db.sessions.delete({ id: sessionId });
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
## PKCE Implementation
|
|
779
|
+
|
|
780
|
+
Lixa automatically implements **PKCE (Proof Key for Code Exchange)** according to [RFC 7636](https://tools.ietf.org/html/rfc7636) for all OAuth flows.
|
|
781
|
+
|
|
782
|
+
### How it Works
|
|
783
|
+
|
|
784
|
+
1. **Code Verifier Generation**
|
|
785
|
+
- Generates 32 cryptographically random bytes
|
|
786
|
+
- Encodes as 64-character hexadecimal string
|
|
787
|
+
- Example: `a1b2c3d4e5f6...` (64 chars)
|
|
788
|
+
|
|
789
|
+
2. **Code Challenge Generation**
|
|
790
|
+
- Creates SHA-256 hash of the code verifier
|
|
791
|
+
- Encodes as base64url (RFC 4648)
|
|
792
|
+
- Sends with authorization request
|
|
793
|
+
|
|
794
|
+
3. **Token Exchange**
|
|
795
|
+
- Retrieves code verifier from state storage
|
|
796
|
+
- Sends original code verifier with token request
|
|
797
|
+
- Provider validates: `SHA256(code_verifier) === code_challenge`
|
|
798
|
+
|
|
799
|
+
### Security Benefits
|
|
800
|
+
|
|
801
|
+
- **Prevents authorization code interception**: Even if an attacker intercepts the authorization code, they cannot exchange it for tokens without the code verifier
|
|
802
|
+
- **No client secret required**: PKCE works without client secrets, making it suitable for public clients
|
|
803
|
+
- **Standards compliant**: Follows RFC 7636 specification
|
|
804
|
+
|
|
805
|
+
### Debug Logging
|
|
806
|
+
|
|
807
|
+
Enable debug mode to see PKCE flow details:
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
const lixa = new Lixa({
|
|
811
|
+
providers: { /* ... */ },
|
|
812
|
+
debug: true
|
|
813
|
+
});
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Debug output includes:
|
|
817
|
+
- `[Lixa] [timestamp] [INFO] [Auth]` - Code verifier and challenge generation
|
|
818
|
+
- `[Lixa] [timestamp] [INFO] [State]` - State storage with code verifier
|
|
819
|
+
- `[Lixa] [timestamp] [INFO] [Token]` - Token exchange with code verifier
|
|
820
|
+
|
|
821
|
+
## Migration Guide
|
|
822
|
+
|
|
823
|
+
### Migrating from Legacy Registration Pattern
|
|
824
|
+
|
|
825
|
+
**Old Pattern (Deprecated):**
|
|
826
|
+
```typescript
|
|
827
|
+
import { Lixa, IProvider } from "@vunexa/lixa";
|
|
828
|
+
import { GoogleProvider } from "@vunexa/lixa";
|
|
829
|
+
|
|
830
|
+
// Step 1: Register providers globally
|
|
831
|
+
Lixa.registerProvider({
|
|
832
|
+
google: new GoogleProvider()
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// Step 2: Configure with credentials only
|
|
836
|
+
const lixa = new Lixa({
|
|
837
|
+
providers: {
|
|
838
|
+
google: {
|
|
839
|
+
clientId: "...",
|
|
840
|
+
clientSecret: "...",
|
|
841
|
+
redirectUri: "...",
|
|
842
|
+
scopes: ["openid", "email"]
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
**New Pattern (Recommended):**
|
|
849
|
+
```typescript
|
|
850
|
+
import { Lixa } from "@vunexa/lixa";
|
|
851
|
+
import { GoogleProvider } from "@vunexa/lixa-providers";
|
|
852
|
+
|
|
853
|
+
// Single step: Pass provider inline
|
|
854
|
+
const lixa = new Lixa({
|
|
855
|
+
providers: {
|
|
856
|
+
google: {
|
|
857
|
+
provider: new GoogleProvider(), // ← Add this line
|
|
858
|
+
clientId: "...",
|
|
859
|
+
clientSecret: "...",
|
|
860
|
+
redirectUri: "...",
|
|
861
|
+
scopes: ["openid", "email"]
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
**Migration Steps:**
|
|
868
|
+
1. Install `@vunexa/lixa-providers`: `npm install @vunexa/lixa-providers`
|
|
869
|
+
2. Update imports: `import { GoogleProvider } from "@vunexa/lixa-providers"`
|
|
870
|
+
3. Remove `Lixa.registerProvider()` calls
|
|
871
|
+
4. Add `provider` field to each provider configuration
|
|
872
|
+
5. Test your OAuth flows
|
|
873
|
+
|
|
874
|
+
**Benefits:**
|
|
875
|
+
- ✅ No global state - each Lixa instance is independent
|
|
876
|
+
- ✅ Better testability - easy to mock providers per instance
|
|
877
|
+
- ✅ Clearer configuration - everything in one place
|
|
878
|
+
- ✅ Type safety - TypeScript knows which providers are configured
|
|
473
879
|
|
|
474
880
|
## Security Considerations
|
|
475
881
|
|