@vunexa/lixa 0.1.6-alpha.15 → 0.1.6-alpha.16
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 +591 -374
- package/dist/export-types/index.d.ts +1 -0
- package/dist/index.cjs +15 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +1 -0
- package/dist/lixa.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,311 +1,292 @@
|
|
|
1
|
-
|
|
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>
|
|
1
|
+
# @vunexa/lixa
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
**A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library for Node.js.**
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
> Built for zero-friction developer experience, strict AuthN/AuthZ separation, multi-SSO account linking, normalized database persistence, and blazing-fast in-memory caching.
|
|
5
|
+
Lixa simplifies multi-provider authentication, identity management, and third-party resource authorization into a single, type-safe API. Use it with any OAuth provider — Google, GitHub, Microsoft, AWS Cognito, or your own custom provider — without being locked in to any vendor.
|
|
9
6
|
|
|
10
|
-
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
7
|
+
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
12
8
|
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
|
|
9
|
+
|
|
10
|
+
📖 **Full Documentation:** [lixa.vunexa.app](https://lixa.vunexa.app)
|
|
14
11
|
|
|
15
12
|
---
|
|
16
13
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
- [Why Lixa?](
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Why Lixa?](#why-lixa)
|
|
17
|
+
- [Architecture Overview](#architecture-overview)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Identity Providers](#identity-providers)
|
|
21
|
+
- [AWS Cognito](#aws-cognito)
|
|
22
|
+
- [Auth0](#auth0)
|
|
23
|
+
- [Okta](#okta)
|
|
24
|
+
- [Self-Managed (Database)](#self-managed-database)
|
|
25
|
+
- [Composite (Multiple Backends)](#composite-multiple-backends)
|
|
26
|
+
- [OAuth / SSO Providers](#oauth--sso-providers)
|
|
27
|
+
- [Built-in Providers](#built-in-providers)
|
|
28
|
+
- [Custom Providers](#custom-providers)
|
|
29
|
+
- [Credentials Authentication](#credentials-authentication)
|
|
30
|
+
- [Session Management](#session-management)
|
|
31
|
+
- [Account Linking](#account-linking)
|
|
32
|
+
- [Resource Connections (AuthZ)](#resource-connections-authz)
|
|
33
|
+
- [Database Storage](#database-storage)
|
|
34
|
+
- [Express Integration](#express-integration)
|
|
35
|
+
- [Error Handling](#error-handling)
|
|
36
|
+
- [API Reference](#api-reference)
|
|
37
|
+
- [License](#license)
|
|
36
38
|
|
|
37
39
|
---
|
|
38
40
|
|
|
39
|
-
##
|
|
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.
|
|
41
|
+
## Why Lixa?
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
| Feature | Lixa |
|
|
44
|
+
|---------|------|
|
|
45
|
+
| **Provider-agnostic** | Works with any OAuth 2.0 / OIDC provider |
|
|
46
|
+
| **Identity Management** | Built-in support for AWS Cognito, Auth0, Okta, or self-managed DB credentials |
|
|
47
|
+
| **PKCE by default** | Every OAuth flow uses RFC 7636 PKCE (S256) automatically |
|
|
48
|
+
| **AuthN / AuthZ separation** | Clean separation between login (identity) and post-login API access (resource connections) |
|
|
49
|
+
| **Account linking** | Auto-link multiple SSO providers (Google + GitHub + Cognito) under a single user |
|
|
50
|
+
| **Type-safe** | Full TypeScript with inferred provider keys and generic session types |
|
|
51
|
+
| **Zero vendor lock-in** | No AWS SDK, no Auth0 SDK — just `fetch()` calls to standard APIs |
|
|
52
|
+
| **Minimal dependencies** | Only `node-cache` as runtime dependency |
|
|
49
53
|
|
|
50
54
|
---
|
|
51
55
|
|
|
52
|
-
##
|
|
56
|
+
## Architecture Overview
|
|
57
|
+
|
|
58
|
+
Lixa is organized into a lightweight, modular **2-package architecture**:
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
```
|
|
61
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
62
|
+
│ Your Application │
|
|
63
|
+
├──────────────────────────────────────────────────────────────┤
|
|
64
|
+
│ │
|
|
65
|
+
│ ┌────────────────────────────────────────────────────────┐ │
|
|
66
|
+
│ │ @vunexa/lixa (Core Engine) │ │
|
|
67
|
+
│ │ │ │
|
|
68
|
+
│ │ • Lixa class • OAuth 2.0 + PKCE flows │ │
|
|
69
|
+
│ │ • IProvider • Session / State management │ │
|
|
70
|
+
│ │ • IIdentityProvider • Credentials (scrypt hashing) │ │
|
|
71
|
+
│ │ • Account linking • Resource connections (AuthZ) │ │
|
|
72
|
+
│ │ • Cookie utilities • Structured error hierarchy │ │
|
|
73
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
74
|
+
│ ▲ │
|
|
75
|
+
│ │ peer dependency │
|
|
76
|
+
│ ┌────────────────────────┴───────────────────────────────┐ │
|
|
77
|
+
│ │ @vunexa/lixa-extensions (Extensions) │ │
|
|
78
|
+
│ │ │ │
|
|
79
|
+
│ │ • OAuth Providers: Google, GitHub, Microsoft, Cognito │ │
|
|
80
|
+
│ │ • Identity: CognitoIdentityProvider, Auth0, Okta │ │
|
|
81
|
+
│ │ • Storage: Prisma adapter, Drizzle adapter, Cache │ │
|
|
82
|
+
│ │ • HTTP: Express route handlers & middleware │ │
|
|
83
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
84
|
+
│ │
|
|
85
|
+
└──────────────────────────────────────────────────────────────┘
|
|
86
|
+
```
|
|
60
87
|
|
|
61
88
|
---
|
|
62
89
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
|
|
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:
|
|
68
|
-
|
|
69
|
-
```mermaid
|
|
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
|
|
106
|
-
end
|
|
107
|
-
Express-->>User: 200 OK (User Profile)
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @vunexa/lixa @vunexa/lixa-extensions
|
|
108
94
|
```
|
|
109
95
|
|
|
110
|
-
|
|
96
|
+
```bash
|
|
97
|
+
# yarn
|
|
98
|
+
yarn add @vunexa/lixa @vunexa/lixa-extensions
|
|
111
99
|
|
|
112
|
-
|
|
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
|
|
100
|
+
# pnpm
|
|
101
|
+
pnpm add @vunexa/lixa @vunexa/lixa-extensions
|
|
145
102
|
```
|
|
146
103
|
|
|
147
104
|
---
|
|
148
105
|
|
|
149
|
-
|
|
106
|
+
## Quick Start
|
|
150
107
|
|
|
151
|
-
|
|
108
|
+
Get up and running with Google OAuth in under 5 minutes:
|
|
152
109
|
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
```
|
|
110
|
+
```typescript
|
|
111
|
+
import { Lixa } from "@vunexa/lixa";
|
|
112
|
+
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-extensions/providers";
|
|
168
113
|
|
|
169
|
-
|
|
114
|
+
// 1. Create a Lixa instance with your providers
|
|
115
|
+
const lixa = new Lixa({
|
|
116
|
+
providers: {
|
|
117
|
+
google: {
|
|
118
|
+
provider: new GoogleProvider(),
|
|
119
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
120
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
121
|
+
redirectUri: "http://localhost:3000/auth/google/callback",
|
|
122
|
+
scopes: ["openid", "email", "profile"],
|
|
123
|
+
},
|
|
124
|
+
github: {
|
|
125
|
+
provider: new GithubProvider(),
|
|
126
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
127
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
128
|
+
redirectUri: "http://localhost:3000/auth/github/callback",
|
|
129
|
+
scopes: ["read:user", "user:email"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
170
133
|
|
|
171
|
-
|
|
134
|
+
// 2. Generate an authorization URL and redirect the user
|
|
135
|
+
const authUrl = await lixa.getAuthUrl("google");
|
|
136
|
+
// → Redirect user to authUrl
|
|
172
137
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
// 3. Handle the callback after the user authorizes
|
|
139
|
+
const sessionId = await lixa.handleCallback({
|
|
140
|
+
provider: "google",
|
|
141
|
+
code: "authorization_code_from_query",
|
|
142
|
+
state: "state_from_query",
|
|
143
|
+
});
|
|
176
144
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
145
|
+
// 4. Fetch session info anywhere in your app
|
|
146
|
+
const session = await lixa.fetchSessionInfo(sessionId);
|
|
147
|
+
// → { sessionId, userId, email, provider: "google", token, raw }
|
|
180
148
|
```
|
|
181
149
|
|
|
182
150
|
---
|
|
183
151
|
|
|
184
|
-
##
|
|
152
|
+
## Identity Providers
|
|
185
153
|
|
|
186
|
-
|
|
154
|
+
Lixa supports multiple identity backends for username/password authentication alongside OAuth SSO. Configure them via the `identityProvider` (or `identity`) field in `LixaConfig`.
|
|
187
155
|
|
|
188
|
-
###
|
|
156
|
+
### AWS Cognito
|
|
189
157
|
|
|
190
|
-
|
|
158
|
+
The most mature integration. Communicates directly with AWS Cognito REST APIs — **zero AWS SDK dependency**.
|
|
191
159
|
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
import { Lixa, CompositeIdentityProvider } from "@vunexa/lixa";
|
|
162
|
+
import { CognitoIdentityProvider } from "@vunexa/lixa-extensions/identity/cognito";
|
|
163
|
+
import { GoogleProvider } from "@vunexa/lixa-extensions/providers";
|
|
164
|
+
|
|
165
|
+
// Option A: Cognito-only (username/password via User Pool)
|
|
166
|
+
const lixa = new Lixa({
|
|
167
|
+
identity: new CognitoIdentityProvider({
|
|
168
|
+
userPoolId: process.env.COGNITO_USER_POOL_ID!,
|
|
169
|
+
clientId: process.env.COGNITO_CLIENT_ID!,
|
|
170
|
+
region: "us-east-1",
|
|
171
|
+
clientSecret: process.env.COGNITO_CLIENT_SECRET, // optional
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
197
174
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
175
|
+
// Option B: Cognito + Hosted UI federated SSO (Google, Apple, SAML, etc.)
|
|
176
|
+
const cognito = new CognitoIdentityProvider({
|
|
177
|
+
userPoolId: process.env.COGNITO_USER_POOL_ID!,
|
|
178
|
+
clientId: process.env.COGNITO_CLIENT_ID!,
|
|
179
|
+
region: "us-east-1",
|
|
180
|
+
clientSecret: process.env.COGNITO_CLIENT_SECRET,
|
|
181
|
+
hostedUi: {
|
|
182
|
+
domain: "https://my-pool.auth.us-east-1.amazoncognito.com",
|
|
183
|
+
redirectUri: "http://localhost:3000/auth/cognito/callback",
|
|
184
|
+
federated: {
|
|
185
|
+
google: { identityProvider: "Google" },
|
|
186
|
+
apple: { identityProvider: "SignInWithApple" },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
201
190
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
}
|
|
191
|
+
const lixaWithSSO = new Lixa({
|
|
192
|
+
identity: cognito,
|
|
193
|
+
// cognito.getProviders() auto-registers 'cognito_sso', 'cognito_google', 'cognito_apple'
|
|
194
|
+
});
|
|
195
|
+
```
|
|
220
196
|
|
|
221
|
-
|
|
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
|
-
}
|
|
197
|
+
**Cognito API coverage:**
|
|
234
198
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
}
|
|
199
|
+
| Method | Cognito API Action |
|
|
200
|
+
|--------|--------------------|
|
|
201
|
+
| `lixa.signUp()` | `SignUp` |
|
|
202
|
+
| `lixa.signIn()` | `InitiateAuth` (USER_PASSWORD_AUTH) |
|
|
203
|
+
| `lixa.confirmSignUp()` | `ConfirmSignUp` |
|
|
204
|
+
| `lixa.resendConfirmationCode()` | `ResendConfirmationCode` |
|
|
205
|
+
| `lixa.forgotPassword()` | `ForgotPassword` |
|
|
206
|
+
| `lixa.confirmPasswordReset()` | `ConfirmForgotPassword` |
|
|
207
|
+
| `lixa.changePassword()` | `InitiateAuth` → `ChangePassword` |
|
|
250
208
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
```
|
|
209
|
+
**Features:**
|
|
210
|
+
- Automatic `SecretHash` HMAC-SHA256 computation when `clientSecret` is configured
|
|
211
|
+
- JWT ID token decoding for user profile extraction
|
|
212
|
+
- Cognito error mapping to Lixa error types (`UsernameExistsException` → `UserAlreadyExistsError`, etc.)
|
|
213
|
+
- Custom endpoint support for LocalStack, VPC endpoints, or mock servers
|
|
214
|
+
- Hosted UI federated SSO with automatic provider registration
|
|
265
215
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
216
|
+
### Auth0
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { Auth0IdentityProvider } from "@vunexa/lixa-extensions/identity/auth0";
|
|
220
|
+
|
|
221
|
+
const lixa = new Lixa({
|
|
222
|
+
identity: new Auth0IdentityProvider({
|
|
223
|
+
domain: "your-tenant.auth0.com",
|
|
224
|
+
clientId: process.env.AUTH0_CLIENT_ID!,
|
|
225
|
+
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
|
226
|
+
connection: "Username-Password-Authentication", // optional
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
269
229
|
```
|
|
270
230
|
|
|
271
|
-
|
|
231
|
+
### Okta
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { OktaIdentityProvider } from "@vunexa/lixa-extensions/identity/okta";
|
|
235
|
+
|
|
236
|
+
const lixa = new Lixa({
|
|
237
|
+
identity: new OktaIdentityProvider({
|
|
238
|
+
orgUrl: "https://your-org.okta.com",
|
|
239
|
+
clientId: process.env.OKTA_CLIENT_ID,
|
|
240
|
+
apiToken: process.env.OKTA_API_TOKEN, // for admin operations
|
|
241
|
+
}),
|
|
242
|
+
});
|
|
243
|
+
```
|
|
272
244
|
|
|
273
|
-
###
|
|
245
|
+
### Self-Managed (Database)
|
|
274
246
|
|
|
275
|
-
|
|
247
|
+
Use your own database for user credentials — no external identity service needed. Includes Node.js native `crypto.scrypt` hashing, configurable password policies, and timing-attack-safe verification.
|
|
276
248
|
|
|
277
249
|
```typescript
|
|
278
|
-
import { Lixa
|
|
279
|
-
import { CognitoIdentityProvider } from "@vunexa/lixa-extensions/identity";
|
|
280
|
-
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-extensions/providers";
|
|
250
|
+
import { Lixa } from "@vunexa/lixa";
|
|
281
251
|
import { createPrismaAdapter } from "@vunexa/lixa-extensions/storage/prisma";
|
|
282
252
|
import { PrismaClient } from "@prisma/client";
|
|
283
253
|
|
|
284
254
|
const prisma = new PrismaClient();
|
|
255
|
+
const storage = createPrismaAdapter(prisma);
|
|
256
|
+
|
|
257
|
+
const lixa = new Lixa({
|
|
258
|
+
storage,
|
|
259
|
+
credentials: {
|
|
260
|
+
policy: {
|
|
261
|
+
minLength: 10,
|
|
262
|
+
requireUppercase: true,
|
|
263
|
+
requireNumbers: true,
|
|
264
|
+
requireSpecialChars: true,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
```
|
|
285
269
|
|
|
286
|
-
|
|
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
|
-
}),
|
|
270
|
+
### Composite (Multiple Backends)
|
|
292
271
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
272
|
+
Combine multiple identity providers under a single Lixa instance:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { Lixa, CompositeIdentityProvider, SelfManagedIdentityProvider } from "@vunexa/lixa";
|
|
276
|
+
import { CognitoIdentityProvider } from "@vunexa/lixa-extensions/identity/cognito";
|
|
277
|
+
import { GoogleProvider } from "@vunexa/lixa-extensions/providers";
|
|
298
278
|
|
|
299
|
-
|
|
279
|
+
const lixa = new Lixa({
|
|
300
280
|
identityProvider: new CompositeIdentityProvider({
|
|
301
281
|
defaultProvider: "cognito",
|
|
302
282
|
providers: {
|
|
303
283
|
cognito: new CognitoIdentityProvider({
|
|
304
|
-
userPoolId:
|
|
305
|
-
clientId:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
284
|
+
userPoolId: "us-east-1_xxx",
|
|
285
|
+
clientId: "your-client-id",
|
|
286
|
+
region: "us-east-1",
|
|
287
|
+
}),
|
|
288
|
+
"self-managed": new SelfManagedIdentityProvider({
|
|
289
|
+
/* credentials config */
|
|
309
290
|
}),
|
|
310
291
|
},
|
|
311
292
|
oauthProviders: {
|
|
@@ -313,191 +294,427 @@ export const lixa = new Lixa({
|
|
|
313
294
|
provider: new GoogleProvider(),
|
|
314
295
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
315
296
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
316
|
-
redirectUri: "http://localhost:3000/
|
|
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
|
-
},
|
|
297
|
+
redirectUri: "http://localhost:3000/auth/google/callback",
|
|
298
|
+
scopes: ["openid", "email", "profile"],
|
|
331
299
|
},
|
|
332
300
|
},
|
|
333
301
|
}),
|
|
334
302
|
});
|
|
303
|
+
|
|
304
|
+
// Route to a specific backend:
|
|
305
|
+
await lixa.signIn({ identifier: "user@example.com", password: "pass", provider: "cognito" });
|
|
335
306
|
```
|
|
336
307
|
|
|
337
308
|
---
|
|
338
309
|
|
|
339
|
-
|
|
310
|
+
## OAuth / SSO Providers
|
|
311
|
+
|
|
312
|
+
### Built-in Providers
|
|
340
313
|
|
|
341
|
-
|
|
314
|
+
Available from `@vunexa/lixa-extensions/providers`:
|
|
315
|
+
|
|
316
|
+
| Provider | Class | Default Auth Scopes |
|
|
317
|
+
|----------|-------|-------------------|
|
|
318
|
+
| Google | `GoogleProvider` | `openid`, `profile`, `email` |
|
|
319
|
+
| GitHub | `GithubProvider` | `read:user`, `user:email` |
|
|
320
|
+
| Microsoft | `MicrosoftProvider` | `openid`, `profile`, `email`, `User.Read` |
|
|
321
|
+
| AWS Cognito SSO | `CognitoSSOProvider` | `openid`, `email`, `profile` |
|
|
342
322
|
|
|
343
323
|
```typescript
|
|
344
|
-
import
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
324
|
+
import { GoogleProvider, GithubProvider, MicrosoftProvider, CognitoSSOProvider } from "@vunexa/lixa-extensions/providers";
|
|
325
|
+
|
|
326
|
+
const lixa = new Lixa({
|
|
327
|
+
providers: {
|
|
328
|
+
google: {
|
|
329
|
+
provider: new GoogleProvider(),
|
|
330
|
+
clientId: "...", clientSecret: "...", redirectUri: "...",
|
|
331
|
+
scopes: ["openid", "email", "profile"],
|
|
332
|
+
},
|
|
333
|
+
microsoft: {
|
|
334
|
+
provider: new MicrosoftProvider({ tenant: "common" }),
|
|
335
|
+
clientId: "...", clientSecret: "...", redirectUri: "...",
|
|
336
|
+
scopes: ["openid", "profile", "email", "User.Read"],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Custom Providers
|
|
343
|
+
|
|
344
|
+
Integrate **any** OAuth 2.0 / OIDC provider by implementing the `IProvider` interface — just 3 URLs:
|
|
357
345
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
346
|
+
```typescript
|
|
347
|
+
import { type IProvider, Lixa } from "@vunexa/lixa";
|
|
348
|
+
|
|
349
|
+
const discordProvider: IProvider = {
|
|
350
|
+
authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
|
|
351
|
+
tokenEndpoint: "https://discord.com/api/oauth2/token",
|
|
352
|
+
userInfoEndpoint: "https://discord.com/api/users/@me",
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const lixa = new Lixa({
|
|
356
|
+
providers: {
|
|
357
|
+
discord: {
|
|
358
|
+
provider: discordProvider,
|
|
359
|
+
clientId: "...", clientSecret: "...", redirectUri: "...",
|
|
360
|
+
scopes: ["identify", "email"],
|
|
361
|
+
},
|
|
362
|
+
},
|
|
369
363
|
});
|
|
370
|
-
app.use("/api/v1/auth", authRouter);
|
|
371
364
|
```
|
|
372
365
|
|
|
373
366
|
---
|
|
374
367
|
|
|
375
|
-
|
|
368
|
+
## Credentials Authentication
|
|
369
|
+
|
|
370
|
+
Sign up, sign in, change password, and forgot password — all through a unified API:
|
|
376
371
|
|
|
377
372
|
```typescript
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
373
|
+
// Sign up a new user
|
|
374
|
+
const { user, sessionId } = await lixa.signUp({
|
|
375
|
+
identifier: "alice@example.com",
|
|
376
|
+
password: "SecurePassword123!",
|
|
377
|
+
email: "alice@example.com",
|
|
378
|
+
metadata: { name: "Alice Smith" },
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Sign in
|
|
382
|
+
const { user, sessionId, session } = await lixa.signIn({
|
|
383
|
+
identifier: "alice@example.com",
|
|
384
|
+
password: "SecurePassword123!",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Change password
|
|
388
|
+
await lixa.changePassword({
|
|
389
|
+
identifier: "alice@example.com",
|
|
390
|
+
oldPassword: "SecurePassword123!",
|
|
391
|
+
newPassword: "NewSecurePassword456!",
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Forgot password flow
|
|
395
|
+
const { deliveryMedium } = await lixa.forgotPassword({
|
|
396
|
+
identifier: "alice@example.com",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Confirm password reset with verification code
|
|
400
|
+
await lixa.confirmPasswordReset({
|
|
401
|
+
identifier: "alice@example.com",
|
|
402
|
+
confirmationCode: "123456",
|
|
403
|
+
newPassword: "ResetPassword789!",
|
|
395
404
|
});
|
|
396
405
|
|
|
397
|
-
|
|
398
|
-
|
|
406
|
+
// Verify credentials without creating a session
|
|
407
|
+
const identity = await lixa.verifyCredentials({
|
|
408
|
+
identifier: "alice@example.com",
|
|
409
|
+
password: "ResetPassword789!",
|
|
399
410
|
});
|
|
400
411
|
```
|
|
401
412
|
|
|
402
413
|
---
|
|
403
414
|
|
|
404
|
-
##
|
|
415
|
+
## Session Management
|
|
416
|
+
|
|
417
|
+
Lixa manages sessions with pluggable storage. By default, sessions are stored in-memory (suitable for development). For production, use a database adapter.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Fetch session info
|
|
421
|
+
const session = await lixa.fetchSessionInfo(sessionId);
|
|
422
|
+
// → { sessionId, userId, email, provider, token, raw }
|
|
423
|
+
|
|
424
|
+
// Delete session (logout)
|
|
425
|
+
await lixa.deleteSession(sessionId);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Cookie utilities** are included for secure session cookie management:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import {
|
|
432
|
+
createSessionCookie,
|
|
433
|
+
clearSessionCookie,
|
|
434
|
+
DEFAULT_SESSION_COOKIE_NAME,
|
|
435
|
+
} from "@vunexa/lixa";
|
|
436
|
+
|
|
437
|
+
// Create a session cookie (HttpOnly, Secure, SameSite=Lax)
|
|
438
|
+
const cookie = createSessionCookie(sessionId);
|
|
439
|
+
// → { name: "lixa_session", value: sessionId, options: { httpOnly, secure, ... } }
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Account Linking
|
|
405
445
|
|
|
406
|
-
|
|
446
|
+
Automatically link multiple SSO accounts (Google + GitHub + Cognito) under a single user identity based on verified email:
|
|
407
447
|
|
|
408
448
|
```typescript
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
449
|
+
import { Lixa, AccountLinkingStrategy } from "@vunexa/lixa";
|
|
450
|
+
|
|
451
|
+
const lixa = new Lixa({
|
|
452
|
+
accountLinking: {
|
|
453
|
+
mode: AccountLinkingStrategy.AUTO_LINK_BY_VERIFIED_EMAIL,
|
|
454
|
+
requireVerifiedEmail: true, // default
|
|
455
|
+
},
|
|
456
|
+
providers: { /* ... */ },
|
|
416
457
|
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Account linking strategies:**
|
|
461
|
+
|
|
462
|
+
| Strategy | Behavior |
|
|
463
|
+
|----------|----------|
|
|
464
|
+
| `AUTO_LINK_BY_VERIFIED_EMAIL` | Auto-merge accounts with the same verified email |
|
|
465
|
+
| `ISOLATED` | Keep each provider login as a separate account (default) |
|
|
466
|
+
| `STEP_UP_VERIFICATION` | Require password verification before linking a new provider |
|
|
467
|
+
|
|
468
|
+
**Manual account linking:**
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
// Explicitly link a provider to an active session
|
|
472
|
+
await lixa.linkAccount({ sessionId, provider: "github", code, state });
|
|
417
473
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
474
|
+
// Unlink a provider
|
|
475
|
+
await lixa.unlinkAccount(sessionId, "github");
|
|
476
|
+
|
|
477
|
+
// Get all linked accounts for a user
|
|
478
|
+
const accounts = await lixa.getUserAccounts(sessionId);
|
|
479
|
+
// → { google: { provider, email, linkedAt }, github: { ... } }
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Resource Connections (AuthZ)
|
|
485
|
+
|
|
486
|
+
Connect third-party APIs (Google Drive, GitHub Repos) **post-login** with clean AuthN / AuthZ separation:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Configure scopes for resource authorization
|
|
490
|
+
const lixa = new Lixa({
|
|
491
|
+
providers: {
|
|
492
|
+
github: {
|
|
493
|
+
provider: new GithubProvider(),
|
|
494
|
+
clientId: "...", clientSecret: "...", redirectUri: "...",
|
|
495
|
+
scopes: {
|
|
496
|
+
auth: ["read:user", "user:email"], // login scopes
|
|
497
|
+
resource: ["repo", "read:org"], // post-login API scopes
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
423
501
|
});
|
|
424
502
|
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
503
|
+
// 1. Generate resource auth URL (user must be logged in)
|
|
504
|
+
const resourceUrl = await lixa.getResourceAuthUrl({
|
|
505
|
+
sessionId,
|
|
506
|
+
provider: "github",
|
|
507
|
+
scopes: ["repo", "read:org"],
|
|
508
|
+
prompt: "consent",
|
|
509
|
+
});
|
|
431
510
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
511
|
+
// 2. Handle resource callback
|
|
512
|
+
const session = await lixa.handleResourceCallback({
|
|
513
|
+
sessionId,
|
|
514
|
+
provider: "github",
|
|
515
|
+
code: "auth_code",
|
|
516
|
+
state: "state_param",
|
|
438
517
|
});
|
|
518
|
+
|
|
519
|
+
// 3. Get connected resource token (auto-refreshes expired tokens)
|
|
520
|
+
const resource = await lixa.getConnectedResource(sessionId, "github");
|
|
521
|
+
// → { provider, accessToken, refreshToken, expiresAt, scopes, raw }
|
|
522
|
+
|
|
523
|
+
// 4. Or fetch by user ID directly (session-independent)
|
|
524
|
+
const resource = await lixa.getUserResource("user@example.com", "github");
|
|
525
|
+
|
|
526
|
+
// 5. Disconnect a resource
|
|
527
|
+
await lixa.disconnectResource(sessionId, "github");
|
|
439
528
|
```
|
|
440
529
|
|
|
441
530
|
---
|
|
442
531
|
|
|
443
|
-
##
|
|
532
|
+
## Database Storage
|
|
444
533
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
Lixa includes a high-performance **2-Minute In-Memory TTL Cache**:
|
|
534
|
+
Lixa uses in-memory storage by default. For production, use the database adapters from `@vunexa/lixa-extensions`:
|
|
448
535
|
|
|
449
536
|
```typescript
|
|
450
|
-
|
|
537
|
+
// Prisma
|
|
538
|
+
import { createPrismaAdapter } from "@vunexa/lixa-extensions/storage/prisma";
|
|
539
|
+
const storage = createPrismaAdapter(new PrismaClient());
|
|
540
|
+
|
|
541
|
+
// Drizzle
|
|
542
|
+
import { createDrizzleAdapter } from "@vunexa/lixa-extensions/storage/drizzle";
|
|
543
|
+
const storage = createDrizzleAdapter(db, {
|
|
544
|
+
sessions: schema.sessions,
|
|
545
|
+
userResources: schema.userResources,
|
|
546
|
+
oauthStates: schema.oauthStates,
|
|
547
|
+
userCredentials: schema.userCredentials,
|
|
548
|
+
});
|
|
451
549
|
|
|
452
|
-
//
|
|
453
|
-
const
|
|
454
|
-
|
|
550
|
+
// Pass to Lixa
|
|
551
|
+
const lixa = new Lixa({
|
|
552
|
+
storage,
|
|
553
|
+
providers: { /* ... */ },
|
|
455
554
|
});
|
|
456
555
|
```
|
|
457
556
|
|
|
458
|
-
|
|
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.
|
|
557
|
+
The adapters include a built-in **2-minute in-memory TTL cache** to eliminate redundant database reads on rapid HTTP requests. Disable it with `{ enableCache: false }` (Prisma) or by not calling `withLocalCache`.
|
|
463
558
|
|
|
464
559
|
---
|
|
465
560
|
|
|
466
|
-
##
|
|
561
|
+
## Express Integration
|
|
562
|
+
|
|
563
|
+
Drop-in Express route handlers and middleware from `@vunexa/lixa-extensions/express`:
|
|
467
564
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
565
|
+
```typescript
|
|
566
|
+
import {
|
|
567
|
+
handleLogin,
|
|
568
|
+
handleCallback,
|
|
569
|
+
handleLogout,
|
|
570
|
+
createRequireAuthMiddleware,
|
|
571
|
+
handleCredentialsSignUp,
|
|
572
|
+
handleCredentialsSignIn,
|
|
573
|
+
} from "@vunexa/lixa-extensions/express";
|
|
574
|
+
|
|
575
|
+
const requireAuth = createRequireAuthMiddleware(lixa);
|
|
576
|
+
|
|
577
|
+
// OAuth SSO
|
|
578
|
+
app.get("/login", (req, res) => handleLogin(req, res, lixa));
|
|
579
|
+
app.get("/auth/:provider/callback", (req, res) =>
|
|
580
|
+
handleCallback(req, res, lixa, {
|
|
581
|
+
successRedirectUrl: "/dashboard",
|
|
582
|
+
errorRedirectUrl: "/login?error=auth_failed",
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
// Credentials (username/password)
|
|
587
|
+
app.post("/auth/signup", (req, res) => handleCredentialsSignUp(req, res, lixa));
|
|
588
|
+
app.post("/auth/signin", (req, res) => handleCredentialsSignIn(req, res, lixa));
|
|
589
|
+
|
|
590
|
+
// Protected routes
|
|
591
|
+
app.get("/profile", requireAuth, (req, res) => {
|
|
592
|
+
res.json({ user: req.sessionInfo });
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Logout
|
|
596
|
+
app.post("/logout", (req, res) => handleLogout(req, res, lixa));
|
|
597
|
+
```
|
|
477
598
|
|
|
478
599
|
---
|
|
479
600
|
|
|
480
|
-
##
|
|
601
|
+
## Error Handling
|
|
481
602
|
|
|
482
|
-
|
|
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).
|
|
603
|
+
All Lixa errors extend `LixaError` with a `code` and optional `details`:
|
|
487
604
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
605
|
+
```typescript
|
|
606
|
+
import {
|
|
607
|
+
LixaError,
|
|
608
|
+
InvalidCredentialsError,
|
|
609
|
+
UserAlreadyExistsError,
|
|
610
|
+
WeakPasswordError,
|
|
611
|
+
ProviderNotConfiguredError,
|
|
612
|
+
SessionNotFoundError,
|
|
613
|
+
AccountLinkingChallengeRequiredError,
|
|
614
|
+
} from "@vunexa/lixa";
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
await lixa.signIn({ identifier: "user", password: "wrong" });
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (error instanceof InvalidCredentialsError) {
|
|
620
|
+
// error.code === "INVALID_CREDENTIALS"
|
|
621
|
+
}
|
|
622
|
+
if (error instanceof WeakPasswordError) {
|
|
623
|
+
// error.validationErrors === ["Must be at least 10 characters", ...]
|
|
624
|
+
}
|
|
625
|
+
if (error instanceof AccountLinkingChallengeRequiredError) {
|
|
626
|
+
// error.challengeToken, error.email, error.existingProviders
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Error codes reference:**
|
|
632
|
+
|
|
633
|
+
| Error Class | Code | When |
|
|
634
|
+
|-------------|------|------|
|
|
635
|
+
| `InvalidStateError` | `INVALID_STATE` | OAuth state expired or tampered |
|
|
636
|
+
| `ProviderNotConfiguredError` | `PROVIDER_NOT_CONFIGURED` | Provider not in config |
|
|
637
|
+
| `InvalidProviderConfigError` | `INVALID_PROVIDER_CONFIG` | Missing clientId/secret/redirectUri |
|
|
638
|
+
| `TokenExchangeError` | `TOKEN_EXCHANGE_FAILED` | Code-to-token exchange failed |
|
|
639
|
+
| `SessionNotFoundError` | `SESSION_NOT_FOUND` | Session expired or invalid |
|
|
640
|
+
| `InvalidCredentialsError` | `INVALID_CREDENTIALS` | Wrong username/password |
|
|
641
|
+
| `UserAlreadyExistsError` | `USER_ALREADY_EXISTS` | Duplicate registration |
|
|
642
|
+
| `UserNotFoundError` | `USER_NOT_FOUND` | User not in database |
|
|
643
|
+
| `WeakPasswordError` | `WEAK_PASSWORD` | Password policy violation |
|
|
644
|
+
| `IdentityNotConfiguredError` | `CREDENTIALS_NOT_CONFIGURED` | No identity provider set |
|
|
645
|
+
| `AccountLinkingChallengeRequiredError` | `ACCOUNT_LINKING_CHALLENGE_REQUIRED` | Step-up verification needed |
|
|
646
|
+
| `SSOAccountAlreadyExistsError` | `SSO_ACCOUNT_EXISTS` | Email already has SSO account |
|
|
647
|
+
| `AccountNotVerifiedError` | `ACCOUNT_NOT_VERIFIED` | Email not confirmed yet |
|
|
648
|
+
| `RefreshTokenError` | `REFRESH_TOKEN_ERROR` | Token refresh failed |
|
|
496
649
|
|
|
497
650
|
---
|
|
498
651
|
|
|
499
|
-
##
|
|
652
|
+
## API Reference
|
|
653
|
+
|
|
654
|
+
### `Lixa` Class
|
|
655
|
+
|
|
656
|
+
**Constructor:** `new Lixa(config: LixaConfig)`
|
|
657
|
+
|
|
658
|
+
| Config Property | Type | Description |
|
|
659
|
+
|----------------|------|-------------|
|
|
660
|
+
| `providers` | `Record<string, ProviderConfig>` | OAuth provider configurations |
|
|
661
|
+
| `identityProvider` / `identity` | `IIdentityProvider \| IdentityConfig` | Identity backend (Cognito, Auth0, Okta, Self-Managed) |
|
|
662
|
+
| `credentials` | `CredentialsConfig` | Self-managed credential settings (auto-creates SelfManagedIdentityProvider) |
|
|
663
|
+
| `storage` | `StorageAdapter` | Unified database adapter (Prisma / Drizzle) |
|
|
664
|
+
| `accountLinking` | `boolean \| AccountLinkingMode \| AccountLinkingConfig` | Multi-SSO account linking strategy |
|
|
665
|
+
| `sessionCookieName` | `string \| ((lixa, req?) => string)` | Custom session cookie name |
|
|
666
|
+
| `debug` | `boolean` | Enable structured debug logging |
|
|
667
|
+
| `logger` | `LixaLogger` | Custom logger implementation |
|
|
668
|
+
|
|
669
|
+
**OAuth Methods:**
|
|
670
|
+
|
|
671
|
+
| Method | Returns | Description |
|
|
672
|
+
|--------|---------|-------------|
|
|
673
|
+
| `getAuthUrl(provider, state?)` | `Promise<string>` | Generate authorization URL with PKCE |
|
|
674
|
+
| `handleCallback({ provider, code, state })` | `Promise<string>` | Exchange code for tokens, create session |
|
|
675
|
+
| `isProviderConfigured(provider)` | `boolean` | Check if a provider is configured |
|
|
676
|
+
|
|
677
|
+
**Identity Methods:**
|
|
678
|
+
|
|
679
|
+
| Method | Returns | Description |
|
|
680
|
+
|--------|---------|-------------|
|
|
681
|
+
| `signUp(params)` | `Promise<SignUpResult>` | Register a new user |
|
|
682
|
+
| `signIn(params)` | `Promise<SignInResult>` | Authenticate with credentials |
|
|
683
|
+
| `verifyCredentials(params)` | `Promise<UserIdentity \| null>` | Verify credentials without session |
|
|
684
|
+
| `changePassword(params)` | `Promise<boolean>` | Update user password |
|
|
685
|
+
| `forgotPassword(params)` | `Promise<ForgotPasswordResult>` | Initiate password reset |
|
|
686
|
+
| `confirmPasswordReset(params)` | `Promise<boolean>` | Complete password reset |
|
|
687
|
+
| `confirmSignUp(params)` | `Promise<boolean>` | Confirm registration with code |
|
|
688
|
+
| `resendConfirmationCode(params)` | `Promise<ResendConfirmationCodeResult>` | Resend verification code |
|
|
689
|
+
|
|
690
|
+
**Session & Account Methods:**
|
|
691
|
+
|
|
692
|
+
| Method | Returns | Description |
|
|
693
|
+
|--------|---------|-------------|
|
|
694
|
+
| `fetchSessionInfo(sessionId)` | `Promise<Session \| null>` | Get active session details |
|
|
695
|
+
| `deleteSession(sessionId)` | `Promise<void>` | Delete session (logout) |
|
|
696
|
+
| `linkAccount(params)` | `Promise<string>` | Link new OAuth provider to session |
|
|
697
|
+
| `unlinkAccount(sessionId, provider)` | `Promise<boolean>` | Unlink a provider account |
|
|
698
|
+
| `getUserAccounts(userIdOrSessionId)` | `Promise<Record<string, LinkedAccount>>` | Get all linked accounts |
|
|
699
|
+
|
|
700
|
+
**Resource Methods:**
|
|
701
|
+
|
|
702
|
+
| Method | Returns | Description |
|
|
703
|
+
|--------|---------|-------------|
|
|
704
|
+
| `getResourceAuthUrl(params)` | `Promise<string>` | Generate resource authorization URL |
|
|
705
|
+
| `handleResourceCallback(params)` | `Promise<Session>` | Handle resource OAuth callback |
|
|
706
|
+
| `getConnectedResource(sessionId, provider)` | `Promise<ConnectedResource \| null>` | Get resource token (auto-refreshes) |
|
|
707
|
+
| `getUserResource(userId, provider)` | `Promise<ConnectedResource \| null>` | Get resource by user ID |
|
|
708
|
+
| `disconnectResource(sessionId, provider)` | `Promise<boolean>` | Disconnect resource provider |
|
|
709
|
+
|
|
710
|
+
**Static Methods:**
|
|
711
|
+
|
|
712
|
+
| Method | Returns | Description |
|
|
713
|
+
|--------|---------|-------------|
|
|
714
|
+
| `Lixa.generateRandomState()` | `string` | Generate 32-char hex CSRF state |
|
|
715
|
+
|
|
716
|
+
---
|
|
500
717
|
|
|
501
|
-
|
|
718
|
+
## License
|
|
502
719
|
|
|
503
|
-
|
|
720
|
+
[MIT](https://opensource.org/licenses/MIT) © [Vunexa](https://vunexa.app)
|