@vunexa/lixa 0.1.4 → 0.1.6-alpha.10
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 +530 -15
- package/README.template.md +288 -44
- package/dist/dao/session-cache.d.ts +2 -1
- package/dist/dao/session-cache.d.ts.map +1 -1
- package/dist/dao/state-cache.d.ts +4 -0
- package/dist/dao/state-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +68 -75
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/errors.d.ts +90 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/export-types/index.d.ts +435 -107
- package/dist/index.cjs +590 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +411 -108
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +569 -94
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +57 -21
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +17 -11
- package/dist/models/session.d.ts.map +1 -1
- package/dist/types.d.ts +39 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cookies.d.ts +142 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/package.json +1 -1
package/README.template.md
CHANGED
|
@@ -17,8 +17,7 @@ A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library
|
|
|
17
17
|
- **Multi-SSO Account Linking**: Seamlessly merge accounts sharing the same verified email address under a single unified user session.
|
|
18
18
|
- **Post-Login Resource Connection API**: Connect third-party API providers (GitHub Repositories, Google Drive, Slack) post-authentication and manage resource tokens on the user session.
|
|
19
19
|
- **Multi-provider OAuth/OIDC support** with unified API.
|
|
20
|
-
- **
|
|
21
|
-
- **Custom provider support** with extensible provider interface.
|
|
20
|
+
- **Pre-built Database Storage Adapters**: Official Prisma and Drizzle ORM adapters via `@vunexa/lixa-adapters` with support for PostgreSQL, MySQL, and SQLite.
|
|
22
21
|
- **Unified session management** via `SessionHandler` (generation + storage).
|
|
23
22
|
- **Unified state management** via `StateHandler` (PKCE + CSRF protection).
|
|
24
23
|
- **Automatic PKCE** (Proof Key for Code Exchange) for all OAuth flows.
|
|
@@ -277,15 +276,16 @@ app.get("/connect/github/callback", async (req, res) => {
|
|
|
277
276
|
res.redirect("/dashboard");
|
|
278
277
|
});
|
|
279
278
|
|
|
280
|
-
// 3. Query Connected Resource Access Token
|
|
279
|
+
// 3. Query Connected Resource Access Token (Auto-Refreshes Expired Tokens)
|
|
281
280
|
app.get("/api/github/repos", async (req, res) => {
|
|
281
|
+
// Queries by active session OR user ID, auto-refreshing expired access tokens
|
|
282
282
|
const resource = await lixa.getConnectedResource(req.cookies.session_id, "github");
|
|
283
283
|
|
|
284
284
|
if (!resource) {
|
|
285
285
|
return res.status(403).json({ error: "GitHub resource not connected" });
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
// Call GitHub API with resource access token
|
|
288
|
+
// Call GitHub API with active resource access token
|
|
289
289
|
const response = await fetch("https://api.github.com/user/repos", {
|
|
290
290
|
headers: { Authorization: `Bearer ${resource.accessToken}` },
|
|
291
291
|
});
|
|
@@ -294,7 +294,10 @@ app.get("/api/github/repos", async (req, res) => {
|
|
|
294
294
|
res.json(repos);
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
-
// 4.
|
|
297
|
+
// 4. Query Resource Directly by User ID (e.g. inside background cron jobs or webhooks)
|
|
298
|
+
const githubResource = await lixa.getUserResource(userId, "github");
|
|
299
|
+
|
|
300
|
+
// 5. Disconnect Resource Provider
|
|
298
301
|
app.delete("/connect/github", async (req, res) => {
|
|
299
302
|
await lixa.disconnectResource(req.cookies.session_id, "github");
|
|
300
303
|
res.json({ success: true });
|
|
@@ -310,26 +313,26 @@ export interface Session<TRaw = OAuthTokenResponse> {
|
|
|
310
313
|
/** Unique session ID generated by Lixa */
|
|
311
314
|
id?: string;
|
|
312
315
|
|
|
313
|
-
/** Primary access token or session token */
|
|
314
|
-
token: string;
|
|
315
|
-
|
|
316
316
|
/** Unified user ID across linked accounts */
|
|
317
317
|
userId?: string;
|
|
318
318
|
|
|
319
319
|
/** Primary user email */
|
|
320
320
|
email?: string;
|
|
321
321
|
|
|
322
|
-
/**
|
|
323
|
-
provider?: string;
|
|
324
|
-
|
|
325
|
-
/** Linked SSO provider accounts (AuthN) */
|
|
322
|
+
/** Linked SSO provider accounts (AuthN) - Single Source of Truth */
|
|
326
323
|
accounts?: Record<string, LinkedAccount>;
|
|
327
324
|
|
|
328
|
-
/** Connected third-party resource provider tokens (AuthZ) */
|
|
325
|
+
/** Connected third-party resource provider tokens (AuthZ) - Single Source of Truth */
|
|
329
326
|
resources?: Record<string, ConnectedResource>;
|
|
330
327
|
|
|
331
|
-
/**
|
|
332
|
-
|
|
328
|
+
/** Optional primary access token or session token */
|
|
329
|
+
token?: string;
|
|
330
|
+
|
|
331
|
+
/** Optional current active auth provider */
|
|
332
|
+
provider?: string;
|
|
333
|
+
|
|
334
|
+
/** Optional raw token response from provider */
|
|
335
|
+
raw?: TRaw;
|
|
333
336
|
}
|
|
334
337
|
```
|
|
335
338
|
|
|
@@ -432,10 +435,8 @@ export const lixa = new Lixa({
|
|
|
432
435
|
```json
|
|
433
436
|
{
|
|
434
437
|
"id": "e4a91f82c3b4a07f",
|
|
435
|
-
"token": "ya29.a0ARW5m7...",
|
|
436
438
|
"userId": "1049281048",
|
|
437
439
|
"email": "alex.developer@example.com",
|
|
438
|
-
"provider": "google",
|
|
439
440
|
"accounts": {
|
|
440
441
|
"google": {
|
|
441
442
|
"provider": "google",
|
|
@@ -451,19 +452,6 @@ export const lixa = new Lixa({
|
|
|
451
452
|
"accessToken": "gho_8f7b2a9e1c3...",
|
|
452
453
|
"linkedAt": 1771657250000
|
|
453
454
|
}
|
|
454
|
-
},
|
|
455
|
-
"resources": {
|
|
456
|
-
"github": {
|
|
457
|
-
"provider": "github",
|
|
458
|
-
"accessToken": "gho_resource_repo_9a8b7c...",
|
|
459
|
-
"scopes": ["repo", "read:org"],
|
|
460
|
-
"connectedAt": 1771657300000
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
"raw": {
|
|
464
|
-
"access_token": "ya29.a0ARW5m7...",
|
|
465
|
-
"token_type": "Bearer",
|
|
466
|
-
"expires_in": 3599
|
|
467
455
|
}
|
|
468
456
|
}
|
|
469
457
|
```
|
|
@@ -574,10 +562,8 @@ export const lixa = new Lixa({
|
|
|
574
562
|
"ttl": 1771743600,
|
|
575
563
|
"sessionData": {
|
|
576
564
|
"id": "e4a91f82c3b4a07f",
|
|
577
|
-
"token": "ya29.a0ARW5m7...",
|
|
578
565
|
"userId": "1049281048",
|
|
579
566
|
"email": "alex.developer@example.com",
|
|
580
|
-
"provider": "google",
|
|
581
567
|
"accounts": {
|
|
582
568
|
"google": {
|
|
583
569
|
"provider": "google",
|
|
@@ -593,22 +579,280 @@ export const lixa = new Lixa({
|
|
|
593
579
|
"accessToken": "gho_8f7b2a9e1c3...",
|
|
594
580
|
"linkedAt": 1771657250000
|
|
595
581
|
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Custom Resource Storage Implementation (`ResourceStorage`)
|
|
590
|
+
|
|
591
|
+
While `SessionStorage` manages short-lived user authentication sessions (indexed by `sessionId`), **`ResourceStorage`** manages long-lived third-party API tokens (**AuthZ**) bound directly to a **User ID** (or Email). This ensures that third-party credentials (such as GitHub Repositories or Google Drive) persist across session expirations and logouts.
|
|
592
|
+
|
|
593
|
+
### `ResourceStorage` Interface
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
export interface ResourceStorage {
|
|
597
|
+
saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void>;
|
|
598
|
+
getResource(userId: string, provider: string): Promise<ConnectedResource | null>;
|
|
599
|
+
getUserResources(userId: string): Promise<Record<string, ConnectedResource>>;
|
|
600
|
+
deleteResource(userId: string, provider: string): Promise<void>;
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### 1. SQLite Resource Storage (`better-sqlite3`)
|
|
605
|
+
|
|
606
|
+
#### Table Schema (SQL DDL)
|
|
607
|
+
|
|
608
|
+
```sql
|
|
609
|
+
CREATE TABLE IF NOT EXISTS user_resources (
|
|
610
|
+
user_id TEXT NOT NULL,
|
|
611
|
+
provider TEXT NOT NULL,
|
|
612
|
+
data TEXT NOT NULL,
|
|
613
|
+
updated_at INTEGER NOT NULL,
|
|
614
|
+
PRIMARY KEY (user_id, provider)
|
|
615
|
+
);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### TypeScript Implementation
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
import Database from "better-sqlite3";
|
|
622
|
+
import { Lixa, type ResourceStorage, type ConnectedResource } from "@vunexa/lixa";
|
|
623
|
+
|
|
624
|
+
export class SqliteResourceStorage implements ResourceStorage {
|
|
625
|
+
private db = new Database("lixa_resources.db");
|
|
626
|
+
|
|
627
|
+
constructor() {
|
|
628
|
+
this.db.exec(`
|
|
629
|
+
CREATE TABLE IF NOT EXISTS user_resources (
|
|
630
|
+
user_id TEXT NOT NULL,
|
|
631
|
+
provider TEXT NOT NULL,
|
|
632
|
+
data TEXT NOT NULL,
|
|
633
|
+
updated_at INTEGER NOT NULL,
|
|
634
|
+
PRIMARY KEY (user_id, provider)
|
|
635
|
+
);
|
|
636
|
+
`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void> {
|
|
640
|
+
const stmt = this.db.prepare(`
|
|
641
|
+
INSERT INTO user_resources (user_id, provider, data, updated_at)
|
|
642
|
+
VALUES (?, ?, ?, ?)
|
|
643
|
+
ON CONFLICT(user_id, provider) DO UPDATE SET
|
|
644
|
+
data = excluded.data,
|
|
645
|
+
updated_at = excluded.updated_at
|
|
646
|
+
`);
|
|
647
|
+
stmt.run(userId, provider.toLowerCase(), JSON.stringify(resource), Date.now());
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async getResource(userId: string, provider: string): Promise<ConnectedResource | null> {
|
|
651
|
+
const stmt = this.db.prepare(`SELECT data FROM user_resources WHERE user_id = ? AND provider = ?`);
|
|
652
|
+
const row = stmt.get(userId, provider.toLowerCase()) as { data: string } | undefined;
|
|
653
|
+
return row ? (JSON.parse(row.data) as ConnectedResource) : null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async getUserResources(userId: string): Promise<Record<string, ConnectedResource>> {
|
|
657
|
+
const stmt = this.db.prepare(`SELECT provider, data FROM user_resources WHERE user_id = ?`);
|
|
658
|
+
const rows = stmt.all(userId) as Array<{ provider: string; data: string }>;
|
|
659
|
+
const result: Record<string, ConnectedResource> = {};
|
|
660
|
+
for (const r of rows) {
|
|
661
|
+
result[r.provider] = JSON.parse(r.data);
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async deleteResource(userId: string, provider: string): Promise<void> {
|
|
667
|
+
const stmt = this.db.prepare(`DELETE FROM user_resources WHERE user_id = ? AND provider = ?`);
|
|
668
|
+
stmt.run(userId, provider.toLowerCase());
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Pass to Lixa instance via resourceHandler
|
|
673
|
+
export const lixa = new Lixa({
|
|
674
|
+
resourceHandler: {
|
|
675
|
+
resourceStorage: new SqliteResourceStorage(),
|
|
676
|
+
},
|
|
677
|
+
providers: { /* ... */ },
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
#### Saved JSON Records Example in SQLite (`user_resources` Table)
|
|
682
|
+
|
|
683
|
+
**Row 1 (GitHub Repositories)**:
|
|
684
|
+
```json
|
|
685
|
+
{
|
|
686
|
+
"user_id": "1049281048",
|
|
687
|
+
"provider": "github",
|
|
688
|
+
"data": {
|
|
689
|
+
"accessToken": "gho_resource_repo_9a8b7c...",
|
|
690
|
+
"refreshToken": "ghr_refresh_token_123...",
|
|
691
|
+
"scopes": ["repo", "read:org"],
|
|
692
|
+
"expiresAt": 1771743600000,
|
|
693
|
+
"connectedAt": 1771657300000
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**Row 2 (Google Drive)**:
|
|
699
|
+
```json
|
|
700
|
+
{
|
|
701
|
+
"user_id": "1049281048",
|
|
702
|
+
"provider": "google",
|
|
703
|
+
"data": {
|
|
704
|
+
"accessToken": "ya29.drive_resource_token_456...",
|
|
705
|
+
"refreshToken": "1//09abc_google_refresh_token...",
|
|
706
|
+
"scopes": ["https://www.googleapis.com/auth/drive.readonly"],
|
|
707
|
+
"expiresAt": 1771660800000,
|
|
708
|
+
"connectedAt": 1771657400000
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
### 2. AWS DynamoDB Resource Storage (`@aws-sdk/lib-dynamodb`)
|
|
716
|
+
|
|
717
|
+
For serverless AWS deployments storing user API credentials in DynamoDB:
|
|
718
|
+
|
|
719
|
+
#### Table Configuration
|
|
720
|
+
|
|
721
|
+
- **Table Name**: `LixaUserResources`
|
|
722
|
+
- **Partition Key (PK)**: `userId` (String)
|
|
723
|
+
- **Sort Key (SK)**: `provider` (String)
|
|
724
|
+
|
|
725
|
+
#### TypeScript Implementation
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
729
|
+
import { DynamoDBDocumentClient, PutCommand, GetCommand, DeleteCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";
|
|
730
|
+
import { Lixa, type ResourceStorage, type ConnectedResource } from "@vunexa/lixa";
|
|
731
|
+
|
|
732
|
+
export class DynamoDbResourceStorage implements ResourceStorage {
|
|
733
|
+
private docClient: DynamoDBDocumentClient;
|
|
734
|
+
private tableName = "LixaUserResources";
|
|
735
|
+
|
|
736
|
+
constructor() {
|
|
737
|
+
const client = new DynamoDBClient({ region: process.env.AWS_REGION || "us-east-1" });
|
|
738
|
+
this.docClient = DynamoDBDocumentClient.from(client);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void> {
|
|
742
|
+
await this.docClient.send(
|
|
743
|
+
new PutCommand({
|
|
744
|
+
TableName: this.tableName,
|
|
745
|
+
Item: {
|
|
746
|
+
userId,
|
|
747
|
+
provider: provider.toLowerCase(),
|
|
748
|
+
resourceData: resource,
|
|
749
|
+
updatedAt: Date.now(),
|
|
750
|
+
},
|
|
751
|
+
})
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async getResource(userId: string, provider: string): Promise<ConnectedResource | null> {
|
|
756
|
+
const res = await this.docClient.send(
|
|
757
|
+
new GetCommand({
|
|
758
|
+
TableName: this.tableName,
|
|
759
|
+
Key: {
|
|
760
|
+
userId,
|
|
761
|
+
provider: provider.toLowerCase(),
|
|
762
|
+
},
|
|
763
|
+
})
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
return res.Item ? (res.Item.resourceData as ConnectedResource) : null;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async getUserResources(userId: string): Promise<Record<string, ConnectedResource>> {
|
|
770
|
+
const res = await this.docClient.send(
|
|
771
|
+
new QueryCommand({
|
|
772
|
+
TableName: this.tableName,
|
|
773
|
+
KeyConditionExpression: "userId = :userId",
|
|
774
|
+
ExpressionAttributeValues: { ":userId": userId },
|
|
775
|
+
})
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
const result: Record<string, ConnectedResource> = {};
|
|
779
|
+
if (res.Items) {
|
|
780
|
+
for (const item of res.Items) {
|
|
781
|
+
result[item.provider] = item.resourceData as ConnectedResource;
|
|
603
782
|
}
|
|
604
|
-
},
|
|
605
|
-
"raw": {
|
|
606
|
-
"access_token": "ya29.a0ARW5m7...",
|
|
607
|
-
"token_type": "Bearer",
|
|
608
|
-
"expires_in": 3599
|
|
609
783
|
}
|
|
784
|
+
return result;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async deleteResource(userId: string, provider: string): Promise<void> {
|
|
788
|
+
await this.docClient.send(
|
|
789
|
+
new DeleteCommand({
|
|
790
|
+
TableName: this.tableName,
|
|
791
|
+
Key: {
|
|
792
|
+
userId,
|
|
793
|
+
provider: provider.toLowerCase(),
|
|
794
|
+
},
|
|
795
|
+
})
|
|
796
|
+
);
|
|
610
797
|
}
|
|
611
798
|
}
|
|
799
|
+
|
|
800
|
+
// Pass to Lixa instance via resourceHandler
|
|
801
|
+
export const lixa = new Lixa({
|
|
802
|
+
resourceHandler: {
|
|
803
|
+
resourceStorage: new DynamoDbResourceStorage(),
|
|
804
|
+
},
|
|
805
|
+
providers: { /* ... */ },
|
|
806
|
+
});
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### Saved DynamoDB Resource Items Example (`LixaUserResources` Table)
|
|
810
|
+
|
|
811
|
+
```json
|
|
812
|
+
[
|
|
813
|
+
{
|
|
814
|
+
"userId": "1049281048",
|
|
815
|
+
"provider": "github",
|
|
816
|
+
"updatedAt": 1771657300000,
|
|
817
|
+
"resourceData": {
|
|
818
|
+
"accessToken": "gho_resource_repo_9a8b7c...",
|
|
819
|
+
"refreshToken": "ghr_refresh_token_123...",
|
|
820
|
+
"scopes": ["repo", "read:org"],
|
|
821
|
+
"expiresAt": 1771743600000,
|
|
822
|
+
"connectedAt": 1771657300000
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
"userId": "1049281048",
|
|
827
|
+
"provider": "google",
|
|
828
|
+
"updatedAt": 1771657400000,
|
|
829
|
+
"resourceData": {
|
|
830
|
+
"accessToken": "ya29.drive_resource_token_456...",
|
|
831
|
+
"refreshToken": "1//09abc_google_refresh_token...",
|
|
832
|
+
"scopes": ["https://www.googleapis.com/auth/drive.readonly"],
|
|
833
|
+
"expiresAt": 1771660800000,
|
|
834
|
+
"connectedAt": 1771657400000
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
]
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
### 3. Querying Connected Resources Directly by User ID
|
|
843
|
+
|
|
844
|
+
`ResourceStorage` enables background workers, cron jobs, and webhooks to access third-party API credentials by `userId` without an active HTTP session:
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
// Background worker querying user's GitHub Repos token
|
|
848
|
+
const githubResource = await lixa.getUserResource(user.id, "github");
|
|
849
|
+
|
|
850
|
+
if (githubResource) {
|
|
851
|
+
// Lixa auto-refreshes expired access tokens transparently!
|
|
852
|
+
const response = await fetch("https://api.github.com/user/repos", {
|
|
853
|
+
headers: { Authorization: `Bearer ${githubResource.accessToken}` },
|
|
854
|
+
});
|
|
855
|
+
}
|
|
612
856
|
```
|
|
613
857
|
|
|
614
858
|
---
|
|
@@ -5,7 +5,8 @@ declare class LocalSessionHandler implements SessionHandler {
|
|
|
5
5
|
private emailToSessionMap;
|
|
6
6
|
sessionStorage: SessionStorage;
|
|
7
7
|
constructor(defaultTtlSeconds?: number);
|
|
8
|
-
|
|
8
|
+
generateSession<T extends Session>(tokenData: OAuthTokenResponse, _providerMetadata?: ProviderMetadata): Promise<T>;
|
|
9
|
+
GenerateSession<T extends Session>(tokenData: OAuthTokenResponse, providerMetadata?: ProviderMetadata): Promise<T>;
|
|
9
10
|
}
|
|
10
11
|
export { LocalSessionHandler };
|
|
11
12
|
//# sourceMappingURL=session-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-cache.d.ts","sourceRoot":"","sources":["../../src/dao/session-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,cAAM,mBAAoB,YAAW,cAAc;IACjD,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,iBAAiB,CAAkC;IACpD,cAAc,EAAE,cAAc,CAAC;gBAE1B,iBAAiB,GAAE,MAAY;
|
|
1
|
+
{"version":3,"file":"session-cache.d.ts","sourceRoot":"","sources":["../../src/dao/session-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,cAAM,mBAAoB,YAAW,cAAc;IACjD,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,iBAAiB,CAAkC;IACpD,cAAc,EAAE,cAAc,CAAC;gBAE1B,iBAAiB,GAAE,MAAY;IAwDrC,eAAe,CAAC,CAAC,SAAS,OAAO,EACrC,SAAS,EAAE,kBAAkB,EAC7B,iBAAiB,CAAC,EAAE,gBAAgB,GACnC,OAAO,CAAC,CAAC,CAAC;IAcP,eAAe,CAAC,CAAC,SAAS,OAAO,EACrC,SAAS,EAAE,kBAAkB,EAC7B,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,OAAO,CAAC,CAAC,CAAC;CAGd;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -7,6 +7,10 @@ declare class LocalStateHandler implements StateHandler {
|
|
|
7
7
|
state: string;
|
|
8
8
|
data: StateData;
|
|
9
9
|
}>;
|
|
10
|
+
GenerateState(provider: string): Promise<{
|
|
11
|
+
state: string;
|
|
12
|
+
data: StateData;
|
|
13
|
+
}>;
|
|
10
14
|
}
|
|
11
15
|
export { LocalStateHandler };
|
|
12
16
|
//# sourceMappingURL=state-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-cache.d.ts","sourceRoot":"","sources":["../../src/dao/state-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhE,cAAM,iBAAkB,YAAW,YAAY;IAC7C,OAAO,CAAC,KAAK,CAAY;IAClB,YAAY,EAAE,YAAY,CAAC;gBAEtB,iBAAiB,GAAE,MAAY;IAoBrC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"state-cache.d.ts","sourceRoot":"","sources":["../../src/dao/state-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhE,cAAM,iBAAkB,YAAW,YAAY;IAC7C,OAAO,CAAC,KAAK,CAAY;IAClB,YAAY,EAAE,YAAY,CAAC;gBAEtB,iBAAiB,GAAE,MAAY;IAoBrC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC;IAe5E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC;CAGnF;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/dao/types.d.ts
CHANGED
|
@@ -164,10 +164,20 @@ export interface StateHandler {
|
|
|
164
164
|
* }
|
|
165
165
|
* ```
|
|
166
166
|
*/
|
|
167
|
+
/**
|
|
168
|
+
* Generates OAuth state parameter and associated data (camelCase).
|
|
169
|
+
*/
|
|
167
170
|
generateState?(provider: string): Promise<{
|
|
168
171
|
state: string;
|
|
169
172
|
data: StateData;
|
|
170
173
|
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Generates OAuth state parameter and associated data (PascalCase alias for backward compatibility).
|
|
176
|
+
*/
|
|
177
|
+
GenerateState?(provider: string): Promise<{
|
|
178
|
+
state: string;
|
|
179
|
+
data: StateData;
|
|
180
|
+
}>;
|
|
171
181
|
/**
|
|
172
182
|
* State storage operations.
|
|
173
183
|
*
|
|
@@ -193,7 +203,7 @@ export interface SessionStorage {
|
|
|
193
203
|
* Saves a session with expiration.
|
|
194
204
|
*
|
|
195
205
|
* @param sessionId - Unique session identifier
|
|
196
|
-
* @param session - Session data from
|
|
206
|
+
* @param session - Session data from generateSession()
|
|
197
207
|
* @param expiresInSeconds - TTL in seconds (typically 86400 for 24 hours)
|
|
198
208
|
*/
|
|
199
209
|
saveSession<T extends Session>(sessionId: string, session: T, expiresInSeconds: number): Promise<void>;
|
|
@@ -220,19 +230,66 @@ export interface SessionStorage {
|
|
|
220
230
|
session: T;
|
|
221
231
|
} | null>;
|
|
222
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Resource storage operations interface.
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* Manages long-lived third-party resource provider tokens (AuthZ) bound to a user account
|
|
238
|
+
* (User ID or Email), independent of short-lived user sessions.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
242
|
+
export interface ResourceStorage {
|
|
243
|
+
/**
|
|
244
|
+
* Saves a connected resource token for a user.
|
|
245
|
+
*
|
|
246
|
+
* @param userId - Unique user identifier or email
|
|
247
|
+
* @param provider - Resource provider name (e.g. 'github', 'google')
|
|
248
|
+
* @param resource - Connected resource details including access & refresh tokens
|
|
249
|
+
*/
|
|
250
|
+
saveResource(userId: string, provider: string, resource: import("../models/session").ConnectedResource): Promise<void>;
|
|
251
|
+
/**
|
|
252
|
+
* Retrieves a connected resource token for a user.
|
|
253
|
+
*
|
|
254
|
+
* @param userId - Unique user identifier or email
|
|
255
|
+
* @param provider - Resource provider name
|
|
256
|
+
*/
|
|
257
|
+
getResource(userId: string, provider: string): Promise<import("../models/session").ConnectedResource | null>;
|
|
258
|
+
/**
|
|
259
|
+
* Retrieves all connected resources for a user.
|
|
260
|
+
*
|
|
261
|
+
* @param userId - Unique user identifier or email
|
|
262
|
+
*/
|
|
263
|
+
getUserResources(userId: string): Promise<Record<string, import("../models/session").ConnectedResource>>;
|
|
264
|
+
/**
|
|
265
|
+
* Deletes a connected resource token for a user.
|
|
266
|
+
*
|
|
267
|
+
* @param userId - Unique user identifier or email
|
|
268
|
+
* @param provider - Resource provider name
|
|
269
|
+
*/
|
|
270
|
+
deleteResource(userId: string, provider: string): Promise<void>;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Resource handler configuration.
|
|
274
|
+
*
|
|
275
|
+
* @public
|
|
276
|
+
*/
|
|
277
|
+
export interface ResourceHandler {
|
|
278
|
+
resourceStorage?: ResourceStorage;
|
|
279
|
+
}
|
|
223
280
|
/**
|
|
224
281
|
* Session handler for OAuth authentication.
|
|
225
282
|
*
|
|
226
283
|
* @remarks
|
|
227
284
|
* The SessionHandler manages session generation and storage after successful OAuth authentication.
|
|
228
285
|
*
|
|
229
|
-
* -
|
|
286
|
+
* - generateSession: Optional. Customizes how OAuth tokens are converted into session data.
|
|
230
287
|
* If not provided, uses default implementation (access token as session token).
|
|
231
288
|
*
|
|
232
|
-
* -
|
|
289
|
+
* - sessionStorage: Optional. Provides custom session storage (save/get/delete operations).
|
|
233
290
|
* If not provided, uses in-memory cache (not suitable for production).
|
|
234
291
|
*
|
|
235
|
-
* For production, implement both
|
|
292
|
+
* For production, implement both generateSession (for user creation/lookup) and sessionStorage
|
|
236
293
|
* (for persistent session storage with Redis, database, etc.).
|
|
237
294
|
*
|
|
238
295
|
* @example
|
|
@@ -241,7 +298,7 @@ export interface SessionStorage {
|
|
|
241
298
|
* import { SessionHandler, Session, OAuthTokenResponse, ProviderMetadata, extractUserInfo } from '@vunexa/lixa';
|
|
242
299
|
*
|
|
243
300
|
* const sessionHandler: SessionHandler = {
|
|
244
|
-
*
|
|
301
|
+
* generateSession: async (tokenData, providerMetadata) => {
|
|
245
302
|
* // Extract user info and create/retrieve user
|
|
246
303
|
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
247
304
|
* const user = await db.users.upsert({
|
|
@@ -260,7 +317,7 @@ export interface SessionStorage {
|
|
|
260
317
|
* };
|
|
261
318
|
* },
|
|
262
319
|
*
|
|
263
|
-
*
|
|
320
|
+
* sessionStorage: {
|
|
264
321
|
* saveSession: async (sessionId, session, expiresInSeconds) => {
|
|
265
322
|
* const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
|
|
266
323
|
* await db.sessions.create({
|
|
@@ -286,78 +343,21 @@ export interface SessionStorage {
|
|
|
286
343
|
* };
|
|
287
344
|
* ```
|
|
288
345
|
*
|
|
289
|
-
* @example
|
|
290
|
-
* Minimal implementation (uses defaults):
|
|
291
|
-
* ```typescript
|
|
292
|
-
* const sessionHandler: SessionHandler = {
|
|
293
|
-
* storage: {
|
|
294
|
-
* saveSession: async (sessionId, session, expiresInSeconds) => {
|
|
295
|
-
* await redis.setex(sessionId, expiresInSeconds, JSON.stringify(session));
|
|
296
|
-
* },
|
|
297
|
-
* getSession: async (sessionId) => {
|
|
298
|
-
* const data = await redis.get(sessionId);
|
|
299
|
-
* return data ? JSON.parse(data) : null;
|
|
300
|
-
* },
|
|
301
|
-
* deleteSession: async (sessionId) => {
|
|
302
|
-
* await redis.del(sessionId);
|
|
303
|
-
* }
|
|
304
|
-
* }
|
|
305
|
-
* };
|
|
306
|
-
* ```
|
|
307
|
-
*
|
|
308
346
|
* @public
|
|
309
347
|
*/
|
|
310
348
|
export interface SessionHandler {
|
|
311
349
|
/**
|
|
312
|
-
* Generates session data from OAuth token data.
|
|
350
|
+
* Generates session data from OAuth token data (camelCase).
|
|
313
351
|
*
|
|
314
352
|
* @param tokenData - The token data received from the OAuth provider's token endpoint
|
|
315
353
|
* @param providerMetadata - Provider metadata including name and endpoints
|
|
316
354
|
* @returns A Promise that resolves to session data
|
|
317
|
-
*
|
|
318
|
-
* @remarks
|
|
319
|
-
* This method is responsible for creating session data from OAuth tokens.
|
|
320
|
-
* It is called after successfully exchanging the authorization code for tokens.
|
|
321
|
-
*
|
|
322
|
-
* Token Data:
|
|
323
|
-
* - access_token: OAuth access token
|
|
324
|
-
* - refresh_token: OAuth refresh token (optional)
|
|
325
|
-
* - expires_in: Token expiration time in seconds
|
|
326
|
-
* - token_type: Token type (usually "Bearer")
|
|
327
|
-
* - id_token: OpenID Connect ID token (for OIDC providers)
|
|
328
|
-
* - scope: Granted scopes
|
|
329
|
-
*
|
|
330
|
-
* Provider Metadata:
|
|
331
|
-
* - name: The provider name (e.g., 'google', 'github')
|
|
332
|
-
* - endpoints: Provider endpoints (authorization, token, userInfo)
|
|
333
|
-
*
|
|
334
|
-
* Your implementation should:
|
|
335
|
-
* 1. Extract user info (using extractUserInfo or decode ID token)
|
|
336
|
-
* 2. Create or lookup users in your database
|
|
337
|
-
* 3. Build and return session data with any custom fields
|
|
338
|
-
*
|
|
339
|
-
* Note: This method should NOT store the session. Storage is handled by the storage object.
|
|
340
|
-
*
|
|
341
|
-
* If not provided, defaults to using the access token as the session token.
|
|
342
|
-
*
|
|
343
|
-
* @example
|
|
344
|
-
* ```typescript
|
|
345
|
-
* GenerateSession: async (tokenData, providerMetadata) => {
|
|
346
|
-
* const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
347
|
-
* const user = await db.users.upsert({ email: userInfo.email });
|
|
348
|
-
*
|
|
349
|
-
* return {
|
|
350
|
-
* token: tokenData.access_token,
|
|
351
|
-
* raw: {
|
|
352
|
-
* ...tokenData,
|
|
353
|
-
* userId: user.id,
|
|
354
|
-
* provider: providerMetadata.name
|
|
355
|
-
* }
|
|
356
|
-
* };
|
|
357
|
-
* }
|
|
358
|
-
* ```
|
|
359
355
|
*/
|
|
360
356
|
generateSession?<T extends Session>(tokenData: import('../models/session').OAuthTokenResponse, providerMetadata: import('../models/session').ProviderMetadata): Promise<T>;
|
|
357
|
+
/**
|
|
358
|
+
* Generates session data from OAuth token data (PascalCase alias for backward compatibility).
|
|
359
|
+
*/
|
|
360
|
+
GenerateSession?<T extends Session>(tokenData: import('../models/session').OAuthTokenResponse, providerMetadata: import('../models/session').ProviderMetadata): Promise<T>;
|
|
361
361
|
/**
|
|
362
362
|
* Session storage operations.
|
|
363
363
|
*
|
|
@@ -368,12 +368,5 @@ export interface SessionHandler {
|
|
|
368
368
|
* If not provided, uses in-memory cache (not suitable for production).
|
|
369
369
|
*/
|
|
370
370
|
sessionStorage?: SessionStorage;
|
|
371
|
-
/**
|
|
372
|
-
* Optional method to generate session data from OAuth tokens.
|
|
373
|
-
*
|
|
374
|
-
* @remarks
|
|
375
|
-
* If not provided, uses default implementation from LocalSessionHandler.
|
|
376
|
-
*/
|
|
377
|
-
generateSession?<T extends Session>(tokenData: import('../models/session').OAuthTokenResponse, providerMetadata: import('../models/session').ProviderMetadata): Promise<T>;
|
|
378
371
|
}
|
|
379
372
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/dao/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dao/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnF;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,aAAa,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IAE9E;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvG;;;;;OAKG;IACH,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEpE;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACzG;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dao/types.ts"],"names":[],"mappings":"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"}
|