@vunexa/lixa 0.1.6-alpha.14 → 0.1.6-alpha.15

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
@@ -1,65 +1,282 @@
1
- # @vunexa/lixa
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/vunexa/lixa/master/docs/assets/lixa-banner.png" alt="Lixa Banner" width="600" onerror="this.style.display='none'"/>
3
+ </p>
2
4
 
3
- > Package is in active development.
5
+ # 🚀 Lixa (`@vunexa/lixa`)
4
6
 
5
- [![npm version](https://img.shields.io/npm/v/@vunexa/lixa.svg)](https://www.npmjs.com/package/@vunexa/lixa)
7
+ > **The modern, developer-first TypeScript authentication & authorization framework for Node.js.**
8
+ > Built for zero-friction developer experience, strict AuthN/AuthZ separation, multi-SSO account linking, normalized database persistence, and blazing-fast in-memory caching.
9
+
10
+ [![npm version](https://img.shields.io/npm/v/@vunexa/lixa.svg?color=blue)](https://www.npmjs.com/package/@vunexa/lixa)
6
11
  [![npm downloads](https://img.shields.io/npm/dm/@vunexa/lixa.svg)](https://www.npmjs.com/package/@vunexa/lixa)
7
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
14
+
15
+ ---
8
16
 
9
- A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library for backend Node.js applications.
10
- `@vunexa/lixa` simplifies multi-provider authentication (e.g. Google, GitHub, Microsoft, AWS Cognito), enforces strict **AuthN/AuthZ separation**, enables **multi-SSO account linking**, and provides a dedicated post-login **Resource Connection API**.
17
+ ## 📑 Table of Contents
18
+
19
+ - [Why Lixa?](#-why-lixa)
20
+ - [Key Features](#-key-features)
21
+ - [Architecture & Sequence Diagrams](#-architecture--sequence-diagrams)
22
+ - [1. Authentication Flow (AuthN)](#1-authentication-flow-authn)
23
+ - [2. Post-Login Resource Connection (AuthZ)](#2-post-login-resource-connection-authz)
24
+ - [3. Multi-SSO Account Linking](#3-multi-sso-account-linking)
25
+ - [Installation](#-installation)
26
+ - [Quickstart: AWS Cognito + Express + Prisma](#-quickstart-aws-cognito--express--prisma)
27
+ - [Step 1: Database Schema (Prisma)](#step-1-database-schema-prisma)
28
+ - [Step 2: Initialize Lixa & Storage with 2-Minute Cache](#step-2-initialize-lixa--storage-with-2-minute-cache)
29
+ - [Step 3: Setup Express Server & Routes](#step-3-setup-express-server--routes)
30
+ - [Step 4: Protect Routes & Use Tokens](#step-4-protect-routes--use-tokens)
31
+ - [Connecting External Resources (AuthZ)](#-connecting-external-resources-authz)
32
+ - [In-Memory 2-Minute Caching (`withLocalCache`)](#-in-memory-2-minute-caching-withlocalcache)
33
+ - [Supported Identity Providers](#-supported-identity-providers)
34
+ - [TypeScript API Reference](#-typescript-api-reference)
35
+ - [Contributing & License](#-contributing--license)
11
36
 
12
37
  ---
13
38
 
14
- ## Features
39
+ ## 💡 Why Lixa?
40
+
41
+ Most auth libraries blur the line between **authenticating a user** (AuthN) and **requesting access to third-party APIs** (AuthZ). They cram bloated, multi-token JSON blobs into session cookies or single database rows, making user sessions fragile, bloated, and vulnerable.
15
42
 
16
- - **Strict AuthN / AuthZ Separation**: Primary login is automatically restricted to minimal identity scopes (`openid`, `email`, `profile`, `read:user`, `user:email`). Resource permissions are isolated to post-login resource connection.
17
- - **Multi-SSO Account Linking**: Seamlessly merge multiple authentication providers (e.g. AWS Cognito + Google + GitHub) under a single user profile based on verified email or active session context.
18
- - **Persistent Account Storage (`AccountStorage`)**: Linked identity accounts remain saved in persistent database storage across session logouts and log-ins.
19
- - **Post-Login Resource Connection API**: Connect third-party API providers (GitHub Repositories, Google Drive, Slack) post-authentication and manage resource access tokens bound to the user profile.
20
- - **Multi-Identity Provider Architecture**: Built-in support for Self-Managed Database credentials, AWS Cognito, Auth0, and Okta via `@vunexa/lixa-extensions`.
21
- - **Automatic PKCE** (Proof Key for Code Exchange) and state CSRF validation for all OAuth flows.
22
- - **TypeScript-first**: Comprehensive type safety across all core APIs and HTTP adapters.
43
+ **Lixa solves this cleanly:**
44
+ 1. **Single-Purpose Sessions**: A user session contains *only* the authentication tokens (`accessToken`, `refreshToken`, `idToken`) needed for that specific session.
45
+ 2. **First-Class Normalized Database Columns**: No opaque JSON blobs. Every table (`sessions`, `user_accounts`, `user_resources`, `oauth_states`, `user_credentials`) uses primitive typed columns (`userId`, `provider`, `accessToken`, `expiresAt`, etc.) with built-in database indexing.
46
+ 3. **Built-in 2-Minute Local In-Memory Cache**: Eliminates redundant database reads on high-frequency API endpoints while guaranteeing safe invalidation on logout or token updates.
47
+ 4. **Universal Multi-SSO Account Linking**: Seamlessly links AWS Cognito, Google, GitHub, Okta, Auth0, or username/password credentials to a single unified user ID by verified email.
48
+ 5. **Zero-Boilerplate DevXP**: Type-safe out of the box, with full Express middleware, Prisma and Drizzle ORM adapters, and automatic PKCE security.
23
49
 
24
50
  ---
25
51
 
26
- ## Architecture Overview
52
+ ## ✨ Key Features
53
+
54
+ - 🛡️ **Strict AuthN / AuthZ Separation**: Identity tokens stay in `sessions`. External API tokens (e.g. GitHub repos, Google Drive) are isolated in `user_resources`.
55
+ - ⚡ **2-Minute In-Memory Cache (`withLocalCache`)**: Blazing-fast response times for session checks and user lookups with zero SQLite/Postgres I/O overhead.
56
+ - 🔗 **Multi-SSO Account Linking**: Automatic or interactive linking of multiple identity providers under one user account.
57
+ - 🗄️ **Clean Database Schema**: Fully normalized Prisma and Drizzle models with primitive column types and indexed foreign keys.
58
+ - 🔐 **PKCE & State Validation**: Automatic RFC 7636 Proof Key for Code Exchange and cryptographic CSRF state verification.
59
+ - ☁️ **Enterprise Identity Backends**: First-class support for **AWS Cognito**, **Auth0**, **Okta**, and **Self-Managed Database** credentials.
60
+
61
+ ---
62
+
63
+ ## 📐 Architecture & Sequence Diagrams
64
+
65
+ ### 1. Authentication Flow (AuthN)
66
+
67
+ This sequence diagram illustrates how a user logs in via AWS Cognito (or Google/GitHub SSO), receives a secure session cookie, and benefits from Lixa's 2-minute memory cache:
27
68
 
28
69
  ```mermaid
29
- flowchart TD
30
- Client["Client App / Frontend"] -->|1. AuthN / Login Request| LixaEngine["Lixa Core Engine"]
31
-
32
- subgraph AuthN ["Primary Authentication & Account Linking (AuthN)"]
33
- LixaEngine -->|Minimal Scopes| IdP["Identity Provider / Social SSO (Cognito, Google, GitHub)"]
34
- IdP -->|Authorization Code| Callback["/callback Endpoint"]
35
- Callback -->|Verify Code + Link Accounts| SessionStore["Session & Account Storage (Prisma / Drizzle / DB)"]
36
- end
37
-
38
- subgraph AuthZ ["Post-Login Resource Connection (AuthZ)"]
39
- LixaEngine -->|Resource Scopes (e.g. repo, drive)| ResourceProvider["External Resource APIs (GitHub API, Google Drive)"]
40
- ResourceProvider -->|Resource Tokens| ResourceStore["Resource Storage (user_resources DB)"]
70
+ sequenceDiagram
71
+ autonumber
72
+ actor User as User Browser
73
+ participant Express as Express App (Lixa Middleware)
74
+ participant Lixa as Lixa Engine
75
+ participant Cache as In-Memory Cache (120s TTL)
76
+ participant DB as Prisma / Database
77
+ participant Cognito as AWS Cognito (OIDC / Hosted UI)
78
+
79
+ User->>Express: GET /api/v1/auth/login?provider=cognito
80
+ Express->>Lixa: getAuthUrl("cognito")
81
+ Lixa->>DB: Store state & PKCE codeVerifier in `oauth_states`
82
+ Lixa-->>Express: Return Authorization URL
83
+ Express-->>User: 302 Redirect to AWS Cognito
84
+
85
+ User->>Cognito: User signs in & consents
86
+ Cognito-->>User: 302 Redirect to /api/v1/auth/callback?code=...&state=...
87
+
88
+ User->>Express: GET /api/v1/auth/callback?code=...&state=...
89
+ Express->>Lixa: handleCallback("cognito", code, state)
90
+ Lixa->>DB: Validate & delete state from `oauth_states`
91
+ Lixa->>Cognito: Exchange code + PKCE verifier for tokens (AT, RT, idToken)
92
+ Cognito-->>Lixa: Return Cognito Tokens & User Claims
93
+ Lixa->>DB: Upsert normalized row in `sessions` (userId, AT, RT, idToken)
94
+ Lixa->>Cache: Cache session for 120 seconds
95
+ Lixa-->>Express: Issue Session ID (UUID)
96
+ Express-->>User: Set-Cookie: app_session=<sessionId>; HttpOnly; Secure
97
+
98
+ Note over User, Express: Subsequent Authenticated Requests
99
+ User->>Express: GET /api/v1/me (Cookie: app_session=...)
100
+ Express->>Cache: Check session in memory cache
101
+ alt Cache Hit (< 2 mins)
102
+ Cache-->>Express: Return cached session (0 DB queries!)
103
+ else Cache Miss / Expired
104
+ Express->>DB: Query `sessions` by id
105
+ DB-->>Express: Return session & refresh cache
41
106
  end
107
+ Express-->>User: 200 OK (User Profile)
108
+ ```
109
+
110
+ ---
42
111
 
43
- SessionStore -->|Session Cookie| Client
44
- ResourceStore -->|Auto-Refreshed Tokens| Client
112
+ ### 2. Post-Login Resource Connection (AuthZ)
113
+
114
+ When an authenticated user grants your application access to external APIs (e.g. GitHub repos or Google Drive), tokens are stored in `user_resources` and bound to the user's account—completely separate from their login session:
115
+
116
+ ```mermaid
117
+ sequenceDiagram
118
+ autonumber
119
+ actor User as Authenticated User
120
+ participant Express as Express App
121
+ participant Lixa as Lixa Engine
122
+ participant DB as Database (`user_resources`)
123
+ participant GitHub as GitHub OAuth (Resource Provider)
124
+
125
+ User->>Express: GET /api/v1/resources/connect/github
126
+ Express->>Lixa: getResourceAuthUrl("github", { userId: req.session.userId })
127
+ Lixa-->>Express: Return OAuth URL with resource scopes (`repo`, `read:user`)
128
+ Express-->>User: 302 Redirect to GitHub OAuth
129
+
130
+ User->>GitHub: Authorize application for repository access
131
+ GitHub-->>User: 302 Redirect to /api/v1/resources/callback/github?code=...
132
+
133
+ User->>Express: GET /api/v1/resources/callback/github?code=...
134
+ Express->>Lixa: handleResourceCallback("github", code, userId)
135
+ Lixa->>GitHub: Exchange authorization code for Resource Access Token
136
+ GitHub-->>Lixa: Return Access Token & Scopes
137
+ Lixa->>DB: Save to `user_resources` (userId, provider="github", accessToken, scopes)
138
+ Lixa-->>Express: Resource Connected
139
+ Express-->>User: 200 OK: GitHub Connected Successfully!
140
+
141
+ Note over Express, GitHub: Background Tasks / API Requests
142
+ Express->>DB: Get GitHub Resource Token for userId
143
+ Express->>GitHub: Fetch user repositories with Resource Token
144
+ GitHub-->>Express: Repository List
45
145
  ```
46
146
 
47
147
  ---
48
148
 
49
- ## Installation
149
+ ### 3. Multi-SSO Account Linking
150
+
151
+ Lixa automatically links multiple identity providers (e.g. AWS Cognito + Google) sharing the same verified email address:
152
+
153
+ ```mermaid
154
+ sequenceDiagram
155
+ autonumber
156
+ actor User as User with alex@example.com
157
+ participant Lixa as Lixa Engine
158
+ participant DB as Database (`user_accounts`)
159
+
160
+ Note over User, DB: User previously registered with AWS Cognito (userId="usr_123")
161
+ User->>Lixa: Signs in via Google SSO (email="alex@example.com", verified=true)
162
+ Lixa->>DB: Find existing account by verified email in `user_accounts`
163
+ DB-->>Lixa: Found existing account (userId="usr_123", provider="cognito")
164
+ Lixa->>DB: Link Google identity to same userId in `user_accounts` (userId="usr_123", provider="google")
165
+ Lixa->>DB: Create active session for userId="usr_123"
166
+ Lixa-->>User: Logged in! Unified profile and data preserved across both logins.
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 📦 Installation
50
172
 
51
173
  ```bash
174
+ # Core framework & official extensions
52
175
  npm install @vunexa/lixa @vunexa/lixa-extensions
176
+
177
+ # If using Prisma ORM (recommended)
178
+ npm install @prisma/client
179
+ npm install -D prisma
53
180
  ```
54
181
 
55
182
  ---
56
183
 
57
- ## Quick Start
184
+ ## 🏁 Quickstart: AWS Cognito + Express + Prisma
185
+
186
+ Here is a complete, production-ready example configuring **AWS Cognito SSO**, **Prisma storage with 2-minute caching**, and **Express**.
187
+
188
+ ### Step 1: Database Schema (Prisma)
189
+
190
+ Create or update your `prisma/schema.prisma` file with normalized primitive columns:
191
+
192
+ ```prisma
193
+ datasource db {
194
+ provider = "sqlite" // or "postgresql", "mysql"
195
+ url = env("DATABASE_URL")
196
+ }
197
+
198
+ generator client {
199
+ provider = "prisma-client-js"
200
+ }
201
+
202
+ // 1. Active User Sessions (AuthN)
203
+ model Session {
204
+ id String @id @map("id")
205
+ userId String? @map("user_id")
206
+ userEmail String? @map("user_email")
207
+ provider String? @map("provider")
208
+ accessToken String? @map("access_token")
209
+ refreshToken String? @map("refresh_token")
210
+ idToken String? @map("id_token")
211
+ expiresAt DateTime @map("expires_at")
212
+ createdAt DateTime @default(now()) @map("created_at")
213
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
214
+
215
+ @@index([userId])
216
+ @@index([userEmail])
217
+ @@index([expiresAt])
218
+ @@map("sessions")
219
+ }
220
+
221
+ // 2. Multi-SSO Linked Identity Accounts (AuthN)
222
+ model UserAccount {
223
+ userId String @map("user_id")
224
+ provider String @map("provider")
225
+ providerUserId String? @map("provider_user_id")
226
+ email String? @map("email")
227
+ linkedAt DateTime @default(now()) @map("linked_at")
228
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
229
+
230
+ @@id([userId, provider])
231
+ @@index([email])
232
+ @@map("user_accounts")
233
+ }
234
+
235
+ // 3. Connected Third-Party API Resources (AuthZ)
236
+ model ConnectedResource {
237
+ userId String @map("user_id")
238
+ provider String @map("provider")
239
+ accessToken String @map("access_token")
240
+ refreshToken String? @map("refresh_token")
241
+ idToken String? @map("id_token")
242
+ scopes String? @map("scopes")
243
+ expiresAt DateTime? @map("expires_at")
244
+ connectedAt DateTime @default(now()) @map("connected_at")
245
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
246
+
247
+ @@id([userId, provider])
248
+ @@map("user_resources")
249
+ }
250
+
251
+ // 4. PKCE & OAuth Flow State
252
+ model OAuthState {
253
+ state String @id @map("state")
254
+ provider String? @map("provider")
255
+ redirectUri String? @map("redirect_uri")
256
+ codeVerifier String? @map("code_verifier")
257
+ codeChallenge String? @map("code_challenge")
258
+ expiresAt DateTime @map("expires_at")
259
+ createdAt DateTime @default(now()) @map("created_at")
260
+
261
+ @@index([expiresAt])
262
+ @@map("oauth_states")
263
+ }
264
+ ```
58
265
 
59
- ### 1. Configure Lixa Instance
266
+ Push the schema to your database:
267
+ ```bash
268
+ npx prisma db push
269
+ ```
270
+
271
+ ---
272
+
273
+ ### Step 2: Initialize Lixa & Storage with 2-Minute Cache
274
+
275
+ Create `src/config/lixa.ts`:
60
276
 
61
277
  ```typescript
62
- import { Lixa, AccountLinkingStrategy } from "@vunexa/lixa";
278
+ import { Lixa, CompositeIdentityProvider, AccountLinkingStrategy } from "@vunexa/lixa";
279
+ import { CognitoIdentityProvider } from "@vunexa/lixa-extensions/identity";
63
280
  import { GoogleProvider, GithubProvider } from "@vunexa/lixa-extensions/providers";
64
281
  import { createPrismaAdapter } from "@vunexa/lixa-extensions/storage/prisma";
65
282
  import { PrismaClient } from "@prisma/client";
@@ -67,133 +284,220 @@ import { PrismaClient } from "@prisma/client";
67
284
  const prisma = new PrismaClient();
68
285
 
69
286
  export const lixa = new Lixa({
70
- // Unified database storage for sessions, OAuth state, resources, and account linking
71
- storage: createPrismaAdapter(prisma),
287
+ // 1. Unified storage with automatic 2-minute (120s) in-memory cache
288
+ storage: createPrismaAdapter(prisma, {
289
+ enableCache: true,
290
+ cacheTtlSeconds: 120, // Cache session validations locally
291
+ }),
72
292
 
73
- // Configure Multi-SSO Account Linking
293
+ // 2. Multi-SSO account linking strategy
74
294
  accountLinking: {
75
295
  mode: AccountLinkingStrategy.AUTO_LINK_BY_VERIFIED_EMAIL,
76
296
  requireVerifiedEmail: true,
77
297
  },
78
298
 
79
- // Federated OAuth Providers
80
- federatedOAuthProviders: {
81
- google: {
82
- provider: new GoogleProvider(),
83
- clientId: process.env.GOOGLE_CLIENT_ID!,
84
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
85
- redirectUri: "http://localhost:3000/api/v1/oidc/google/callback",
86
- scopes: ["openid", "email", "profile"],
299
+ // 3. Composite Identity Provider (AWS Cognito + Social SSO)
300
+ identityProvider: new CompositeIdentityProvider({
301
+ defaultProvider: "cognito",
302
+ providers: {
303
+ cognito: new CognitoIdentityProvider({
304
+ userPoolId: process.env.COGNITO_USER_POOL_ID!,
305
+ clientId: process.env.COGNITO_CLIENT_ID!,
306
+ clientSecret: process.env.COGNITO_CLIENT_SECRET, // optional
307
+ region: process.env.AWS_REGION || "us-east-1",
308
+ domain: process.env.COGNITO_DOMAIN, // e.g. "my-app.auth.us-east-1.amazoncognito.com"
309
+ }),
87
310
  },
88
- github: {
89
- provider: new GithubProvider(),
90
- clientId: process.env.GITHUB_CLIENT_ID!,
91
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
92
- redirectUri: "http://localhost:3000/api/v1/oidc/github/callback",
93
- scopes: ["read:user", "user:email"],
311
+ oauthProviders: {
312
+ google: {
313
+ provider: new GoogleProvider(),
314
+ clientId: process.env.GOOGLE_CLIENT_ID!,
315
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
316
+ redirectUri: "http://localhost:3000/api/v1/auth/google/callback",
317
+ scopes: {
318
+ auth: ["openid", "email", "profile"],
319
+ resource: ["https://www.googleapis.com/auth/drive.readonly"],
320
+ },
321
+ },
322
+ github: {
323
+ provider: new GithubProvider(),
324
+ clientId: process.env.GITHUB_CLIENT_ID!,
325
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
326
+ redirectUri: "http://localhost:3000/api/v1/auth/github/callback",
327
+ scopes: {
328
+ auth: ["read:user", "user:email"],
329
+ resource: ["repo", "read:org"],
330
+ },
331
+ },
94
332
  },
95
- },
333
+ }),
96
334
  });
97
335
  ```
98
336
 
99
337
  ---
100
338
 
101
- ## Multi-SSO Account Linking
102
-
103
- Lixa allows users to sign in with different identity providers (e.g. Username/Password, AWS Cognito, Google, GitHub) and links them to the same underlying user account.
339
+ ### Step 3: Setup Express Server & Routes
104
340
 
105
- ### Account Linking Sequence
341
+ Create `src/server.ts`:
106
342
 
107
- ```mermaid
108
- sequenceDiagram
109
- autonumber
110
- actor User
111
- participant Frontend
112
- participant Express as Backend Express App
113
- participant Lixa as Lixa Core Engine
114
- participant GitHub as GitHub OAuth
115
- participant DB as Prisma / Drizzle DB
116
-
117
- User->>Frontend: Click "Link GitHub"
118
- Frontend->>Express: GET /login?provider=github
119
- Express->>Lixa: getAuthUrl("github")
120
- Lixa-->>Express: Returns Auth URL + State
121
- Express-->>User: 302 Redirect to GitHub
122
-
123
- User->>GitHub: Authenticate & Grant Identity Scope
124
- GitHub-->>Express: GET /callback?code=123&state=abc (Cookie: session_id)
125
- Express->>Lixa: handleCallback({ provider: "github", code, state, sessionId })
126
- Lixa->>GitHub: Exchange Code for Access Token
127
- GitHub-->>Lixa: Return Access Token + Profile (email)
128
- Lixa->>DB: Save Account Link (accounts.github) to user_resources DB
129
- Lixa-->>Express: Return Updated Session ID
130
- Express-->>Frontend: Redirect to /dashboard with updated session cookie
343
+ ```typescript
344
+ import express from "express";
345
+ import cookieParser from "cookie-parser";
346
+ import cors from "cors";
347
+ import { createAuthRouter, createRequireAuthMiddleware } from "@vunexa/lixa-extensions/http/express";
348
+ import { lixa } from "./config/lixa";
349
+
350
+ const app = express();
351
+ app.use(express.json());
352
+ app.use(cookieParser(process.env.COOKIE_SECRET || "super-secret-key"));
353
+ app.use(cors({ origin: "http://localhost:5173", credentials: true }));
354
+
355
+ // Auth guard middleware
356
+ const requireAuth = createRequireAuthMiddleware(lixa);
357
+
358
+ // Mount Lixa authentication routes:
359
+ // - GET /api/v1/auth/login?provider=cognito
360
+ // - GET /api/v1/auth/callback
361
+ // - POST /api/v1/auth/logout
362
+ // - GET /api/v1/auth/me
363
+ const authRouter = express.Router();
364
+ createAuthRouter(lixa, {
365
+ router: authRouter,
366
+ requireAuth,
367
+ successRedirectUrl: "http://localhost:5173/dashboard",
368
+ errorRedirectUrl: "http://localhost:5173/login?error=auth_failed",
369
+ });
370
+ app.use("/api/v1/auth", authRouter);
131
371
  ```
132
372
 
133
- ### Account Linking Usage
373
+ ---
374
+
375
+ ### Step 4: Protect Routes & Use Tokens
134
376
 
135
377
  ```typescript
136
- // Explicitly link a new provider while authenticated
137
- const sessionId = await lixa.handleCallback({
138
- provider: "github",
139
- code: req.query.code as string,
140
- state: req.query.state as string,
141
- sessionId: activeSessionId, // Links GitHub directly to the active session
378
+ // Protected route example
379
+ app.get("/api/v1/profile", requireAuth, async (req: any, res) => {
380
+ // req.session is populated automatically
381
+ const { userId, email, provider, accessToken, idToken } = req.session;
382
+
383
+ res.json({
384
+ message: "Authenticated successfully!",
385
+ user: {
386
+ userId,
387
+ email,
388
+ provider,
389
+ },
390
+ tokens: {
391
+ accessTokenPreview: accessToken ? `${accessToken.substring(0, 15)}...` : null,
392
+ idTokenPreview: idToken ? `${idToken.substring(0, 15)}...` : null,
393
+ },
394
+ });
142
395
  });
143
396
 
144
- // Unlink a provider account
145
- await lixa.unlinkAccount(activeSessionId, "github");
397
+ app.listen(3000, () => {
398
+ console.log("Server running at http://localhost:3000");
399
+ });
146
400
  ```
147
401
 
148
402
  ---
149
403
 
150
- ## Post-Login Resource Connection (AuthZ API)
404
+ ## 🔌 Connecting External Resources (AuthZ)
151
405
 
152
- Primary authentication is strictly limited to identity scopes. To request third-party API permissions (such as GitHub Repositories or Google Drive access), use Lixa's post-login **Resource Connection API**.
406
+ Unlike login authentication, **Resource Connections** allow your application to interact with external APIs (like GitHub repositories or Google Drive files) on behalf of the user.
153
407
 
154
- ### Resource Connection Sequence
408
+ ```typescript
409
+ // 1. Generate connection redirect URL
410
+ app.get("/api/v1/resources/github/connect", requireAuth, async (req: any, res) => {
411
+ const url = await lixa.getResourceAuthUrl("github", {
412
+ userId: req.session.userId,
413
+ redirectUri: "http://localhost:3000/api/v1/resources/github/callback",
414
+ });
415
+ res.redirect(url);
416
+ });
155
417
 
156
- ```mermaid
157
- sequenceDiagram
158
- autonumber
159
- actor User
160
- participant Frontend
161
- participant Express as Backend Express App
162
- participant Lixa as Lixa Core Engine
163
- participant ResourceAPI as GitHub / Google Drive API
164
-
165
- User->>Frontend: Click "Connect GitHub Repos"
166
- Frontend->>Express: GET /connect-resource/github
167
- Express->>Lixa: getResourceAuthUrl({ sessionId, provider: "github", scopes: ["repo"] })
168
- Lixa-->>Express: Returns Consent URL + Resource State
169
- Express-->>User: 302 Redirect to Provider Consent Page
170
-
171
- User->>ResourceAPI: Grant Resource Permission ("repo")
172
- ResourceAPI-->>Express: GET /callback?code=xyz (Cookie: session_id, lixa_resource_flow=true)
173
- Express->>Lixa: handleResourceCallback({ sessionId, provider: "github", code })
174
- Lixa->>ResourceAPI: Exchange Code for Resource Access & Refresh Tokens
175
- Lixa->>DB: Save Resource Tokens to user_resources Table
176
- Lixa-->>Express: Return Updated Session
177
- Express-->>Frontend: 200 OK / Redirect to Dashboard
418
+ // 2. Handle resource callback & store token
419
+ app.get("/api/v1/resources/github/callback", requireAuth, async (req: any, res) => {
420
+ const { code } = req.query;
421
+ await lixa.handleResourceCallback("github", code as string, req.session.userId);
422
+ res.redirect("http://localhost:5173/settings?connected=github");
423
+ });
424
+
425
+ // 3. Make third-party API calls using stored resource tokens
426
+ app.get("/api/v1/resources/github/repos", requireAuth, async (req: any, res) => {
427
+ const resource = await lixa.getResource(req.session.userId, "github");
428
+ if (!resource) {
429
+ return res.status(404).json({ error: "GitHub account not connected" });
430
+ }
431
+
432
+ // Call GitHub API with resource.accessToken
433
+ const response = await fetch("https://api.github.com/user/repos", {
434
+ headers: { Authorization: `Bearer ${resource.accessToken}` },
435
+ });
436
+ const repos = await response.json();
437
+ res.json({ repos });
438
+ });
178
439
  ```
179
440
 
180
- ### Querying Connected Resource Access Tokens
441
+ ---
442
+
443
+ ## ⚡ In-Memory 2-Minute Caching (`withLocalCache`)
444
+
445
+ Under high traffic, hitting your database on every single API request to validate a session creates heavy I/O bottlenecks.
446
+
447
+ Lixa includes a high-performance **2-Minute In-Memory TTL Cache**:
181
448
 
182
449
  ```typescript
183
- // Query connected resource (automatically handles token refreshes)
184
- const resource = await lixa.getConnectedResource(sessionId, "github");
450
+ import { withLocalCache } from "@vunexa/lixa-extensions/storage";
185
451
 
186
- if (resource) {
187
- // Use access token to call external API
188
- const reposResponse = await fetch("https://api.github.com/user/repos", {
189
- headers: { Authorization: `Bearer ${resource.accessToken}` },
190
- });
191
- const repos = await reposResponse.json();
192
- }
452
+ // Automatically applied when using createPrismaAdapter
453
+ const cachedStorage = withLocalCache(baseAdapter, {
454
+ ttlSeconds: 120, // 2 minutes (default)
455
+ });
193
456
  ```
194
457
 
458
+ ### Cache Features & Invalidation Guarantees:
459
+ - **Zero Database I/O**: Session lookups within 120 seconds return in sub-millisecond memory time.
460
+ - **Auto-Invalidation on Logout**: Calling `deleteSession(sessionId)` immediately clears both the database record and memory cache.
461
+ - **Auto-Invalidation on Updates**: Saving or updating accounts/credentials purges stale cached keys automatically.
462
+ - **Safe Isolation**: OAuth states (single-use CSRF tokens) bypass the cache and validate directly against persistent storage.
463
+
464
+ ---
465
+
466
+ ## 🌐 Supported Identity Providers
467
+
468
+ | Provider | Type | AuthN (SSO) | AuthZ (Resources) | Package |
469
+ | :--- | :--- | :---: | :---: | :--- |
470
+ | **AWS Cognito** | Enterprise IdP | ✅ | — | `@vunexa/lixa-extensions/identity` |
471
+ | **Google** | Social / OIDC | ✅ | ✅ (Drive, Gmail) | `@vunexa/lixa-extensions/providers` |
472
+ | **GitHub** | OAuth 2.0 | ✅ | ✅ (Repos, Orgs) | `@vunexa/lixa-extensions/providers` |
473
+ | **Microsoft** | Azure AD / OIDC | ✅ | ✅ (Graph API) | `@vunexa/lixa-extensions/providers` |
474
+ | **Auth0** | Enterprise IdP | ✅ | — | `@vunexa/lixa-extensions/identity` |
475
+ | **Okta** | Enterprise IdP | ✅ | — | `@vunexa/lixa-extensions/identity` |
476
+ | **Self-Managed** | SQLite / Postgres | ✅ | — | `@vunexa/lixa` (Built-in) |
477
+
195
478
  ---
196
479
 
197
- ## License
480
+ ## 📚 TypeScript API Reference
481
+
482
+ ### `new Lixa(options)`
483
+ - `storage`: Storage adapter created via `createPrismaAdapter` or custom implementation.
484
+ - `identityProvider`: `CompositeIdentityProvider` or standalone IdP instance.
485
+ - `accountLinking`: Configuration for cross-provider email matching (`AccountLinkingStrategy.AUTO_LINK_BY_VERIFIED_EMAIL`).
486
+ - `sessionHandler`: Custom session serialization logic (optional).
487
+
488
+ ### Methods
489
+ - `lixa.getAuthUrl(provider, options)`: Returns authorization URL with PKCE verifier stored in state.
490
+ - `lixa.handleCallback(provider, code, state)`: Validates state, exchanges code for tokens, creates session, and links account.
491
+ - `lixa.getSession(sessionId)`: Retrieves active session from memory cache or database.
492
+ - `lixa.deleteSession(sessionId)`: Deletes session and purges local cache.
493
+ - `lixa.getResourceAuthUrl(provider, options)`: Returns resource authorization URL.
494
+ - `lixa.handleResourceCallback(provider, code, userId)`: Stores connected resource tokens under `userId`.
495
+ - `lixa.getResource(userId, provider)`: Retrieves active resource token for third-party API requests.
496
+
497
+ ---
498
+
499
+ ## 🤝 Contributing & License
500
+
501
+ Contributions are welcome! Please open an issue or pull request on GitHub.
198
502
 
199
- MIT © [Vunexa](https://github.com/vunexa)
503
+ Distributed under the **MIT License**. See `LICENSE` for details.
@@ -45,6 +45,20 @@ export declare class CredentialsManager {
45
45
  * @returns True if password was successfully updated
46
46
  */
47
47
  changePassword(params: ChangePasswordParams): Promise<boolean>;
48
+ /**
49
+ * Sets a password for a user who doesn't have one yet (e.g. SSO-first users),
50
+ * or updates an existing password without requiring the old one.
51
+ * This should only be called from an authenticated context (user already verified their identity via SSO).
52
+ *
53
+ * @param params - userId and newPassword
54
+ * @returns True if password was successfully set
55
+ */
56
+ setPassword(params: {
57
+ userId: string;
58
+ newPassword: string;
59
+ identifier?: string;
60
+ email?: string;
61
+ }): Promise<boolean>;
48
62
  /**
49
63
  * Finds a user by ID and returns safe user data.
50
64
  */