@vunexa/lixa 0.0.1-alpha.9 → 0.1.1
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 +173 -143
- package/README.template.md +360 -0
- package/dist/dao/session-cache.d.ts +11 -0
- package/dist/dao/session-cache.d.ts.map +1 -0
- package/dist/dao/state-cache.d.ts +8 -6
- package/dist/dao/state-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +376 -3
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +1397 -0
- package/dist/export-types/tsdoc-metadata.json +11 -0
- package/dist/index.cjs +1035 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1361 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +992 -9
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +280 -15
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +316 -0
- package/dist/models/session.d.ts.map +1 -0
- package/dist/providers/IProvider.d.ts +127 -4
- package/dist/providers/IProvider.d.ts.map +1 -1
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/types.d.ts +195 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/user-info.d.ts +82 -0
- package/dist/utils/user-info.d.ts.map +1 -0
- package/docs/api/index.md +32 -0
- package/docs/api/lixa.iprovider.authorizationendpoint.md +13 -0
- package/docs/api/lixa.iprovider.md +100 -0
- package/docs/api/lixa.iprovider.tokenendpoint.md +13 -0
- package/docs/api/lixa.iprovider.userinfoendpoint.md +13 -0
- package/docs/api/lixa.lixa._constructor_.md +50 -0
- package/docs/api/lixa.lixa.generaterandomstate.md +23 -0
- package/docs/api/lixa.lixa.getauthurl.md +85 -0
- package/docs/api/lixa.lixa.handlecallback.md +73 -0
- package/docs/api/lixa.lixa.isproviderregistered.md +65 -0
- package/docs/api/lixa.lixa.md +164 -0
- package/docs/api/lixa.lixa.registerprovider.md +69 -0
- package/docs/api/lixa.lixaconfig.md +96 -0
- package/docs/api/lixa.lixaconfig.providers.md +13 -0
- package/docs/api/lixa.lixaconfig.sessionstrategy.md +13 -0
- package/docs/api/lixa.lixaconfig.statedao.md +13 -0
- package/docs/api/lixa.md +108 -0
- package/docs/api/lixa.providerconfig.clientid.md +13 -0
- package/docs/api/lixa.providerconfig.clientsecret.md +13 -0
- package/docs/api/lixa.providerconfig.extraconfig.md +13 -0
- package/docs/api/lixa.providerconfig.md +134 -0
- package/docs/api/lixa.providerconfig.redirecturi.md +13 -0
- package/docs/api/lixa.providerconfig.scopes.md +13 -0
- package/docs/api/lixa.session.md +77 -0
- package/docs/api/lixa.session.raw.md +13 -0
- package/docs/api/lixa.session.token.md +13 -0
- package/docs/api/lixa.sessionstrategy.createsession.md +56 -0
- package/docs/api/lixa.sessionstrategy.md +40 -0
- package/docs/images/account-linking.svg +1 -0
- package/docs/images/architecture.svg +1 -0
- package/docs/images/resource-connection.svg +1 -0
- package/package.json +18 -10
- package/dist/dao/state-cache.js +0 -18
- package/dist/dao/state-cache.js.map +0 -1
- package/dist/dao/types.js +0 -2
- package/dist/dao/types.js.map +0 -1
- package/dist/lixa.js +0 -244
- package/dist/lixa.js.map +0 -1
- package/dist/providers/IProvider.js +0 -2
- package/dist/providers/IProvider.js.map +0 -1
- package/dist/providers/github.d.ts +0 -9
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/github.js +0 -8
- package/dist/providers/github.js.map +0 -1
- package/dist/providers/google.d.ts +0 -9
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js +0 -8
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/index.js +0 -3
- package/dist/providers/index.js.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/constants.js +0 -4
- package/dist/utils/constants.js.map +0 -1
- package/index.d.ts +0 -229
package/README.md
CHANGED
|
@@ -1,240 +1,270 @@
|
|
|
1
1
|
# @vunexa/lixa
|
|
2
2
|
|
|
3
|
-
> Package is in
|
|
3
|
+
> Package is in active development.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
6
6
|
[](https://www.npmjs.com/package/@vunexa/lixa)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
9
|
A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library for backend applications.
|
|
10
|
-
|
|
10
|
+
`@vunexa/lixa` simplifies multi-provider authentication (e.g. Google, GitHub, Microsoft), enforces strict **AuthN/AuthZ separation**, enables **multi-SSO account linking**, and provides a dedicated post-login **Resource Connection API**.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
16
|
+
- **Strict AuthN / AuthZ Separation**: Primary login is automatically restricted to minimal identity scopes (`openid`, `email`, `profile`). Resource scopes are isolated to post-login connection.
|
|
17
|
+
- **Multi-SSO Account Linking**: Seamlessly merge accounts sharing the same verified email address under a single unified user session.
|
|
18
|
+
- **Post-Login Resource Connection API**: Connect third-party API providers (GitHub Repositories, Google Drive, Slack) post-authentication and manage resource tokens on the user session.
|
|
19
|
+
- **Multi-provider OAuth/OIDC support** with unified API.
|
|
20
|
+
- **Built-in providers** for Google, GitHub, and more via `@vunexa/lixa-providers`.
|
|
21
|
+
- **Custom provider support** with extensible provider interface.
|
|
22
|
+
- **Unified session management** via `SessionHandler` (generation + storage).
|
|
23
|
+
- **Unified state management** via `StateHandler` (PKCE + CSRF protection).
|
|
24
|
+
- **Automatic PKCE** (Proof Key for Code Exchange) for all OAuth flows.
|
|
25
|
+
- **TypeScript-first** with full type safety (zero `any` types).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture Overview
|
|
30
|
+
|
|
31
|
+

|
|
23
32
|
|
|
24
33
|
---
|
|
25
34
|
|
|
26
35
|
## Installation
|
|
27
36
|
|
|
28
37
|
```bash
|
|
29
|
-
npm install @vunexa/lixa
|
|
38
|
+
npm install @vunexa/lixa @vunexa/lixa-providers
|
|
30
39
|
# or
|
|
31
|
-
yarn add @vunexa/lixa
|
|
40
|
+
yarn add @vunexa/lixa @vunexa/lixa-providers
|
|
32
41
|
```
|
|
33
42
|
|
|
43
|
+
---
|
|
44
|
+
|
|
34
45
|
## Quick Start
|
|
35
46
|
|
|
36
|
-
### 1. Configure
|
|
47
|
+
### 1. Configure Lixa with Account Linking & Providers
|
|
37
48
|
|
|
38
49
|
```typescript
|
|
39
|
-
import { Lixa } from
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
import { Lixa, AccountLinkingStrategy } from "@vunexa/lixa";
|
|
51
|
+
import { GoogleProvider, GithubProvider } from "@vunexa/lixa-providers";
|
|
52
|
+
|
|
53
|
+
export const lixa = new Lixa({
|
|
54
|
+
// Configure Multi-SSO Account Linking Strategy
|
|
55
|
+
accountLinking: {
|
|
56
|
+
mode: AccountLinkingStrategy.AUTO_LINK_BY_VERIFIED_EMAIL,
|
|
57
|
+
requireVerifiedEmail: true,
|
|
58
|
+
},
|
|
42
59
|
providers: {
|
|
43
60
|
google: {
|
|
61
|
+
provider: new GoogleProvider(),
|
|
44
62
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
45
63
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
46
|
-
redirectUri:
|
|
47
|
-
scopes: [
|
|
48
|
-
extraConfig: {
|
|
49
|
-
prompt: 'consent',
|
|
50
|
-
access_type: 'offline',
|
|
51
|
-
},
|
|
64
|
+
redirectUri: "http://localhost:3000/auth/google/callback",
|
|
65
|
+
scopes: ["openid", "email", "profile"],
|
|
52
66
|
},
|
|
53
67
|
github: {
|
|
68
|
+
provider: new GithubProvider(),
|
|
54
69
|
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
55
70
|
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
56
|
-
redirectUri:
|
|
57
|
-
scopes: [
|
|
58
|
-
extraConfig: {},
|
|
71
|
+
redirectUri: "http://localhost:3000/auth/github/callback",
|
|
72
|
+
scopes: ["read:user", "user:email"],
|
|
59
73
|
},
|
|
60
74
|
},
|
|
61
|
-
|
|
62
|
-
// Optional: custom session strategy
|
|
63
|
-
sessionStrategy: {
|
|
64
|
-
createSession: async (tokenData) => {
|
|
65
|
-
// Custom session creation logic
|
|
66
|
-
return {
|
|
67
|
-
token: 'custom-session-token',
|
|
68
|
-
raw: tokenData
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
75
|
});
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
### 2.
|
|
78
|
+
### 2. Primary Authentication & Callback
|
|
76
79
|
|
|
77
80
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
// Store state in session for validation
|
|
83
|
-
req.session.oauthState = state;
|
|
84
|
-
|
|
85
|
-
const authUrl = lixa.getAuthUrl(provider.toUpperCase(), state);
|
|
81
|
+
// 1. Redirect to provider authorization URL
|
|
82
|
+
app.get("/auth/:provider", async (req, res) => {
|
|
83
|
+
const authUrl = await lixa.getAuthUrl(req.params.provider);
|
|
86
84
|
res.redirect(authUrl);
|
|
87
85
|
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### 3. Handle the provider callback and establish a session
|
|
91
86
|
|
|
92
|
-
|
|
87
|
+
// 2. Handle provider callback
|
|
93
88
|
app.get("/auth/:provider/callback", async (req, res) => {
|
|
94
89
|
const { code, state } = req.query;
|
|
95
90
|
const provider = req.params.provider;
|
|
96
91
|
|
|
97
92
|
try {
|
|
98
|
-
|
|
99
|
-
if (state !== req.session.oauthState) {
|
|
100
|
-
throw new Error('Invalid state parameter');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const session = await lixa.handleCallback({
|
|
93
|
+
const sessionId = await lixa.handleCallback({
|
|
104
94
|
provider,
|
|
105
95
|
code: code as string,
|
|
106
96
|
state: state as string,
|
|
107
97
|
});
|
|
108
98
|
|
|
109
|
-
|
|
110
|
-
res.
|
|
111
|
-
httpOnly: true,
|
|
112
|
-
secure: true,
|
|
113
|
-
sameSite: 'strict'
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
res.redirect("/dashboard");
|
|
99
|
+
res.cookie("session_id", sessionId, { httpOnly: true, secure: true });
|
|
100
|
+
res.redirect("/profile");
|
|
117
101
|
} catch (error) {
|
|
118
|
-
|
|
119
|
-
res.status(500).send("Authentication failed");
|
|
102
|
+
res.status(401).send("Authentication failed");
|
|
120
103
|
}
|
|
121
104
|
});
|
|
122
105
|
```
|
|
123
106
|
|
|
124
|
-
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Multi-SSO Account Linking
|
|
110
|
+
|
|
111
|
+
Lixa supports automatic identity merging for users signing in with different SSO providers (e.g. Google and GitHub) that share the same verified email address.
|
|
125
112
|
|
|
126
|
-
###
|
|
113
|
+
### Account Linking Sequence Diagram
|
|
127
114
|
|
|
128
|
-
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
### Account Linking Configuration Modes
|
|
129
118
|
|
|
130
119
|
```typescript
|
|
131
|
-
import {
|
|
120
|
+
import { AccountLinkingStrategy } from "@vunexa/lixa";
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
// Mode 1: Auto-link by verified email (Recommended)
|
|
123
|
+
accountLinking: {
|
|
124
|
+
mode: AccountLinkingStrategy.AUTO_LINK_BY_VERIFIED_EMAIL,
|
|
125
|
+
requireVerifiedEmail: true,
|
|
137
126
|
}
|
|
138
127
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Use it in your configuration
|
|
145
|
-
const lixa = new Lixa({
|
|
146
|
-
providers: {
|
|
147
|
-
custom: {
|
|
148
|
-
clientId: 'your-client-id',
|
|
149
|
-
clientSecret: 'your-client-secret',
|
|
150
|
-
redirectUri: 'https://yourapp.com/auth/custom/callback',
|
|
151
|
-
scopes: ['read:user'],
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
128
|
+
// Mode 2: Treat provider logins as separate accounts
|
|
129
|
+
accountLinking: {
|
|
130
|
+
mode: AccountLinkingStrategy.ISOLATED,
|
|
131
|
+
}
|
|
155
132
|
```
|
|
156
133
|
|
|
157
|
-
###
|
|
134
|
+
### Explicit Account Linking & Unlinking APIs
|
|
158
135
|
|
|
159
136
|
```typescript
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
137
|
+
// Explicitly link a new provider while authenticated
|
|
138
|
+
await lixa.linkAccount({
|
|
139
|
+
sessionId: req.cookies.session_id,
|
|
140
|
+
provider: "github",
|
|
141
|
+
code: req.query.code as string,
|
|
142
|
+
state: req.query.state as string,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Unlink a provider account
|
|
146
|
+
await lixa.unlinkAccount(req.cookies.session_id, "github");
|
|
164
147
|
```
|
|
165
148
|
|
|
166
|
-
|
|
149
|
+
---
|
|
167
150
|
|
|
168
|
-
|
|
151
|
+
## Post-Login Resource Connection API (AuthZ)
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
- `new Lixa(config: LixaConfig)` - Creates a new Lixa instance
|
|
153
|
+
Primary authentication is strictly limited to identity scopes. To request third-party API permissions (such as GitHub Repositories or Google Drive), use Lixa's post-login **Resource Connection API**.
|
|
172
154
|
|
|
173
|
-
|
|
174
|
-
- `Lixa.registerProvider(providerMap: { [key: string]: IProvider })` - Register custom providers
|
|
175
|
-
- `Lixa.isProviderRegistered(provider: string): boolean` - Check if a provider is registered
|
|
155
|
+
### Resource Connection Sequence Diagram
|
|
176
156
|
|
|
177
|
-
|
|
178
|
-
- `generateRandomState(): string` - Generate a random state parameter for OAuth flow
|
|
179
|
-
- `getAuthUrl(provider: string, state: string): string` - Get authorization URL for a provider
|
|
180
|
-
- `handleCallback({ provider, code, state }): Promise<Session>` - Handle OAuth callback and create session
|
|
157
|
+

|
|
181
158
|
|
|
182
|
-
###
|
|
159
|
+
### Resource Connection Usage Example
|
|
183
160
|
|
|
184
161
|
```typescript
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
redirectUri: string;
|
|
189
|
-
scopes: string[];
|
|
190
|
-
extraConfig?: Record<string, any>;
|
|
191
|
-
}
|
|
162
|
+
// 1. Generate Resource Authorization URL (Requires Active Session)
|
|
163
|
+
app.get("/connect/github", async (req, res) => {
|
|
164
|
+
const sessionId = req.cookies.session_id;
|
|
192
165
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
166
|
+
const resourceAuthUrl = await lixa.getResourceAuthUrl({
|
|
167
|
+
sessionId,
|
|
168
|
+
provider: "github",
|
|
169
|
+
scopes: ["repo", "read:org"], // Resource permissions requested post-login
|
|
170
|
+
});
|
|
197
171
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
172
|
+
res.redirect(resourceAuthUrl);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 2. Handle Resource Callback
|
|
176
|
+
app.get("/connect/github/callback", async (req, res) => {
|
|
177
|
+
const sessionId = req.cookies.session_id;
|
|
178
|
+
|
|
179
|
+
const updatedSession = await lixa.handleResourceCallback({
|
|
180
|
+
sessionId,
|
|
181
|
+
provider: "github",
|
|
182
|
+
code: req.query.code as string,
|
|
183
|
+
state: req.query.state as string,
|
|
184
|
+
scopes: ["repo", "read:org"],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
res.redirect("/dashboard");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 3. Query Connected Resource Access Token
|
|
191
|
+
app.get("/api/github/repos", async (req, res) => {
|
|
192
|
+
const resource = await lixa.getConnectedResource(req.cookies.session_id, "github");
|
|
193
|
+
|
|
194
|
+
if (!resource) {
|
|
195
|
+
return res.status(403).json({ error: "GitHub resource not connected" });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Call GitHub API with resource access token
|
|
199
|
+
const response = await fetch("https://api.github.com/user/repos", {
|
|
200
|
+
headers: { Authorization: `Bearer ${resource.accessToken}` },
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const repos = await response.json();
|
|
204
|
+
res.json(repos);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 4. Disconnect Resource Provider
|
|
208
|
+
app.delete("/connect/github", async (req, res) => {
|
|
209
|
+
await lixa.disconnectResource(req.cookies.session_id, "github");
|
|
210
|
+
res.json({ success: true });
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
201
215
|
|
|
202
|
-
|
|
216
|
+
## Session Interface Structure
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
export interface Session<TRaw = OAuthTokenResponse> {
|
|
220
|
+
/** Unique session ID generated by Lixa */
|
|
221
|
+
id?: string;
|
|
222
|
+
|
|
223
|
+
/** Primary access token or session token */
|
|
203
224
|
token: string;
|
|
204
|
-
raw: any;
|
|
205
|
-
}
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
/** Unified user ID across linked accounts */
|
|
227
|
+
userId?: string;
|
|
228
|
+
|
|
229
|
+
/** Primary user email */
|
|
230
|
+
email?: string;
|
|
231
|
+
|
|
232
|
+
/** Current active auth provider */
|
|
233
|
+
provider?: string;
|
|
234
|
+
|
|
235
|
+
/** Linked SSO provider accounts (AuthN) */
|
|
236
|
+
accounts?: Record<string, LinkedAccount>;
|
|
237
|
+
|
|
238
|
+
/** Connected third-party resource provider tokens (AuthZ) */
|
|
239
|
+
resources?: Record<string, ConnectedResource>;
|
|
240
|
+
|
|
241
|
+
/** Full raw token response from provider */
|
|
242
|
+
raw: TRaw;
|
|
211
243
|
}
|
|
212
244
|
```
|
|
213
245
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **Google** - OAuth 2.0 and OpenID Connect
|
|
217
|
-
- **GitHub** - OAuth 2.0
|
|
246
|
+
### `LinkedAccount` vs `ConnectedResource`
|
|
218
247
|
|
|
219
|
-
|
|
248
|
+
| Concept | Purpose | Scopes Allowed | Stored Location |
|
|
249
|
+
| :--- | :--- | :--- | :--- |
|
|
250
|
+
| **`LinkedAccount`** | Identity verification & multi-SSO merging (**AuthN**) | Minimal identity scopes (`openid`, `email`, `read:user`) | `session.accounts[provider]` |
|
|
251
|
+
| **`ConnectedResource`** | External API resource access (**AuthZ**) | Resource permissions (`repo`, `drive.readonly`) | `session.resources[provider]` |
|
|
220
252
|
|
|
221
|
-
|
|
222
|
-
# Install dependencies
|
|
223
|
-
npm install
|
|
253
|
+
---
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
npm test
|
|
255
|
+
## User Info Utilities
|
|
227
256
|
|
|
228
|
-
|
|
229
|
-
npm run test:coverage
|
|
257
|
+
Lixa provides utilities to extract user information from OAuth tokens:
|
|
230
258
|
|
|
231
|
-
|
|
232
|
-
|
|
259
|
+
```typescript
|
|
260
|
+
import { extractUserInfo, type UserInfo } from "@vunexa/lixa";
|
|
233
261
|
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
const { userInfo } = await extractUserInfo(tokenData, providerMetadata);
|
|
263
|
+
console.log(userInfo.email, userInfo.name);
|
|
236
264
|
```
|
|
237
265
|
|
|
266
|
+
---
|
|
267
|
+
|
|
238
268
|
## License
|
|
239
269
|
|
|
240
|
-
MIT
|
|
270
|
+
[MIT](LICENSE)
|