@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 +428 -124
- package/dist/credentials/credentials-manager.d.ts +14 -0
- package/dist/credentials/credentials-manager.d.ts.map +1 -1
- package/dist/credentials/types.d.ts +23 -0
- package/dist/credentials/types.d.ts.map +1 -1
- package/dist/dao/types.d.ts +5 -0
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +391 -95
- package/dist/identity/composite.d.ts +65 -0
- package/dist/identity/composite.d.ts.map +1 -0
- package/dist/identity/index.d.ts +1 -0
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/self-managed.d.ts +16 -2
- package/dist/identity/self-managed.d.ts.map +1 -1
- package/dist/identity/types.d.ts +8 -0
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/index.cjs +900 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +353 -63
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +880 -127
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +89 -29
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +10 -4
- package/dist/models/session.d.ts.map +1 -1
- package/dist/types.d.ts +83 -30
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/user-info.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,65 +1,282 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
5
|
+
# 🚀 Lixa (`@vunexa/lixa`)
|
|
4
6
|
|
|
5
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
6
11
|
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
7
12
|
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
[](https://www.typescriptlang.org/)
|
|
14
|
+
|
|
15
|
+
---
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
+
Create `src/server.ts`:
|
|
106
342
|
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
### Step 4: Protect Routes & Use Tokens
|
|
134
376
|
|
|
135
377
|
```typescript
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
397
|
+
app.listen(3000, () => {
|
|
398
|
+
console.log("Server running at http://localhost:3000");
|
|
399
|
+
});
|
|
146
400
|
```
|
|
147
401
|
|
|
148
402
|
---
|
|
149
403
|
|
|
150
|
-
##
|
|
404
|
+
## 🔌 Connecting External Resources (AuthZ)
|
|
151
405
|
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
const resource = await lixa.getConnectedResource(sessionId, "github");
|
|
450
|
+
import { withLocalCache } from "@vunexa/lixa-extensions/storage";
|
|
185
451
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
*/
|