@mastra/auth-neon 0.3.0 → 0.3.1-alpha.0
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/CHANGELOG.md +11 -0
- package/dist/index.cjs +401 -360
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +399 -357
- package/dist/index.js.map +1 -1
- package/package.json +14 -13
package/dist/index.cjs
CHANGED
|
@@ -1,373 +1,414 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _mastra_core_auth = require("@mastra/core/auth");
|
|
3
|
+
let _mastra_core_server = require("@mastra/core/server");
|
|
4
|
+
let jose = require("jose");
|
|
5
|
+
let _mastra_core_auth_ee = require("@mastra/core/auth/ee");
|
|
6
|
+
//#region src/rbac-provider.ts
|
|
7
|
+
const DEFAULT_CACHE_TTL_MS = 6e4;
|
|
8
|
+
const DEFAULT_CACHE_MAX_SIZE = 1e3;
|
|
9
|
+
/**
|
|
10
|
+
* RBAC provider for Neon Auth that maps organization roles to Mastra permissions.
|
|
11
|
+
*
|
|
12
|
+
* Neon Auth uses Better Auth's Organization plugin which provides `owner`,
|
|
13
|
+
* `admin`, and `member` roles. This provider maps those roles to Mastra
|
|
14
|
+
* permission patterns via a configurable `roleMapping`.
|
|
15
|
+
*
|
|
16
|
+
* @example Static mapping
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { MastraRBACNeon } from '@mastra/auth-neon';
|
|
19
|
+
*
|
|
20
|
+
* const rbac = new MastraRBACNeon({
|
|
21
|
+
* roleMapping: {
|
|
22
|
+
* owner: ['*'],
|
|
23
|
+
* admin: ['*'],
|
|
24
|
+
* member: ['agents:read', 'workflows:*'],
|
|
25
|
+
* _default: [],
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example With custom role extraction from JWT claims
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const rbac = new MastraRBACNeon({
|
|
33
|
+
* roleMapping: {
|
|
34
|
+
* admin: ['*'],
|
|
35
|
+
* member: ['agents:read'],
|
|
36
|
+
* },
|
|
37
|
+
* getUserRoles: (user) => {
|
|
38
|
+
* const role = user.jwt?.role as string;
|
|
39
|
+
* return role ? [role] : [];
|
|
40
|
+
* },
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
10
44
|
var MastraRBACNeon = class {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
}
|
|
45
|
+
options;
|
|
46
|
+
baseUrl;
|
|
47
|
+
rolesCache = /* @__PURE__ */ new Map();
|
|
48
|
+
cacheTtlMs;
|
|
49
|
+
cacheMaxSize;
|
|
50
|
+
get roleMapping() {
|
|
51
|
+
return this.options.roleMapping;
|
|
52
|
+
}
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.options = options;
|
|
55
|
+
const rawUrl = options.baseUrl ?? process.env.NEON_AUTH_BASE_URL ?? "";
|
|
56
|
+
let end = rawUrl.length;
|
|
57
|
+
while (end > 0 && rawUrl[end - 1] === "/") end--;
|
|
58
|
+
this.baseUrl = rawUrl.slice(0, end);
|
|
59
|
+
this.cacheTtlMs = options.cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
60
|
+
this.cacheMaxSize = options.cache?.maxSize ?? DEFAULT_CACHE_MAX_SIZE;
|
|
61
|
+
}
|
|
62
|
+
async getRoles(user) {
|
|
63
|
+
if (this.options.getUserRoles) return this.options.getUserRoles(user);
|
|
64
|
+
if (user.jwt) {
|
|
65
|
+
const role = user.jwt.role;
|
|
66
|
+
if (role) return [role];
|
|
67
|
+
}
|
|
68
|
+
const userId = user.user?.id;
|
|
69
|
+
if (!userId) return [];
|
|
70
|
+
const cached = this.rolesCache.get(userId);
|
|
71
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
72
|
+
this.rolesCache.delete(userId);
|
|
73
|
+
this.rolesCache.set(userId, cached);
|
|
74
|
+
return cached.roles;
|
|
75
|
+
}
|
|
76
|
+
const roles = await this.fetchRolesFromNeonAuth(userId);
|
|
77
|
+
if (this.rolesCache.size >= this.cacheMaxSize) {
|
|
78
|
+
const firstKey = this.rolesCache.keys().next().value;
|
|
79
|
+
if (firstKey) this.rolesCache.delete(firstKey);
|
|
80
|
+
}
|
|
81
|
+
this.rolesCache.set(userId, {
|
|
82
|
+
roles,
|
|
83
|
+
expiresAt: Date.now() + this.cacheTtlMs
|
|
84
|
+
});
|
|
85
|
+
return roles;
|
|
86
|
+
}
|
|
87
|
+
async hasRole(user, role) {
|
|
88
|
+
return (await this.getRoles(user)).includes(role);
|
|
89
|
+
}
|
|
90
|
+
async getPermissions(user) {
|
|
91
|
+
return (0, _mastra_core_auth_ee.resolvePermissionsFromMapping)(await this.getRoles(user), this.options.roleMapping);
|
|
92
|
+
}
|
|
93
|
+
async hasPermission(user, permission) {
|
|
94
|
+
return (await this.getPermissions(user)).some((p) => (0, _mastra_core_auth_ee.matchesPermission)(p, permission));
|
|
95
|
+
}
|
|
96
|
+
async hasAllPermissions(user, permissions) {
|
|
97
|
+
const userPermissions = await this.getPermissions(user);
|
|
98
|
+
return permissions.every((required) => userPermissions.some((p) => (0, _mastra_core_auth_ee.matchesPermission)(p, required)));
|
|
99
|
+
}
|
|
100
|
+
async hasAnyPermission(user, permissions) {
|
|
101
|
+
const userPermissions = await this.getPermissions(user);
|
|
102
|
+
return permissions.some((required) => userPermissions.some((p) => (0, _mastra_core_auth_ee.matchesPermission)(p, required)));
|
|
103
|
+
}
|
|
104
|
+
async getAvailableRoles() {
|
|
105
|
+
return Object.keys(this.options.roleMapping).filter((k) => k !== "_default").map((k) => ({
|
|
106
|
+
id: k,
|
|
107
|
+
name: k.charAt(0).toUpperCase() + k.slice(1)
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
async getRolePermissions(roleId) {
|
|
111
|
+
return (0, _mastra_core_auth_ee.resolvePermissionsFromMapping)([roleId], this.options.roleMapping);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Fetch organization membership roles from Neon Auth.
|
|
115
|
+
*
|
|
116
|
+
* Uses Better Auth's `organization/list-memberships` admin API.
|
|
117
|
+
* Falls back to empty roles on error (default permissions will apply).
|
|
118
|
+
*/
|
|
119
|
+
async fetchRolesFromNeonAuth(userId) {
|
|
120
|
+
if (!this.baseUrl) return [];
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(`${this.baseUrl}/auth/api/organization/list-memberships`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
body: JSON.stringify({ userId })
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) return [];
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
const memberships = Array.isArray(data) ? data : data.data ?? [];
|
|
130
|
+
return (this.options.organizationId ? memberships.filter((m) => m.organizationId === this.options.organizationId) : memberships).map((m) => m.role);
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
103
135
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
function getRequestHeader(request, name) {
|
|
107
|
-
if (request instanceof Request) {
|
|
108
|
-
return request.headers.get(name);
|
|
109
|
-
}
|
|
110
|
-
return request.raw?.headers.get(name) ?? request.headers?.get(name) ?? request.header(name) ?? null;
|
|
111
|
-
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/index.ts
|
|
112
138
|
function stripTrailingSlashes(url) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
return url.slice(0, end);
|
|
139
|
+
let end = url.length;
|
|
140
|
+
while (end > 0 && url[end - 1] === "/") end--;
|
|
141
|
+
return url.slice(0, end);
|
|
118
142
|
}
|
|
119
143
|
function parseCookies(response) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!raw) return [];
|
|
125
|
-
return raw.split(/,(?=\s*\w+=)/);
|
|
144
|
+
if (typeof response.headers.getSetCookie === "function") return response.headers.getSetCookie();
|
|
145
|
+
const raw = response.headers.get("set-cookie");
|
|
146
|
+
if (!raw) return [];
|
|
147
|
+
return raw.split(/,(?=\s*\w+=)/);
|
|
126
148
|
}
|
|
127
149
|
function mapNeonUserToEEUser(user) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
return {
|
|
151
|
+
id: user.id,
|
|
152
|
+
email: user.email,
|
|
153
|
+
name: user.name,
|
|
154
|
+
avatarUrl: user.image ?? void 0,
|
|
155
|
+
metadata: {
|
|
156
|
+
emailVerified: user.emailVerified,
|
|
157
|
+
createdAt: user.createdAt,
|
|
158
|
+
updatedAt: user.updatedAt
|
|
159
|
+
}
|
|
160
|
+
};
|
|
139
161
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Mastra authentication provider for Neon Auth.
|
|
164
|
+
*
|
|
165
|
+
* Neon Auth is a managed authentication service built on Better Auth
|
|
166
|
+
* that stores users, sessions, and auth configuration directly in your
|
|
167
|
+
* Neon Postgres database.
|
|
168
|
+
*
|
|
169
|
+
* This adapter supports:
|
|
170
|
+
* - JWT bearer token verification via JWKS (for API clients)
|
|
171
|
+
* - Session cookie verification via Neon Auth REST API (for Studio)
|
|
172
|
+
* - Email/password sign-in and sign-up (for Studio credentials flow)
|
|
173
|
+
* - Session management (validate, refresh, destroy)
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* import { MastraAuthNeon } from '@mastra/auth-neon';
|
|
178
|
+
*
|
|
179
|
+
* const auth = new MastraAuthNeon({
|
|
180
|
+
* baseUrl: process.env.NEON_AUTH_BASE_URL,
|
|
181
|
+
* });
|
|
182
|
+
*
|
|
183
|
+
* const mastra = new Mastra({
|
|
184
|
+
* server: {
|
|
185
|
+
* auth,
|
|
186
|
+
* },
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @example With RBAC
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { MastraAuthNeon, MastraRBACNeon } from '@mastra/auth-neon';
|
|
193
|
+
*
|
|
194
|
+
* const mastra = new Mastra({
|
|
195
|
+
* server: {
|
|
196
|
+
* auth: new MastraAuthNeon({
|
|
197
|
+
* baseUrl: process.env.NEON_AUTH_BASE_URL,
|
|
198
|
+
* }),
|
|
199
|
+
* rbac: new MastraRBACNeon({
|
|
200
|
+
* roleMapping: {
|
|
201
|
+
* owner: ['*'],
|
|
202
|
+
* admin: ['*'],
|
|
203
|
+
* member: ['agents:read', 'workflows:*'],
|
|
204
|
+
* _default: [],
|
|
205
|
+
* },
|
|
206
|
+
* }),
|
|
207
|
+
* },
|
|
208
|
+
* });
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* @see https://neon.com/docs/auth/overview
|
|
212
|
+
*/
|
|
213
|
+
var MastraAuthNeon = class extends _mastra_core_server.MastraAuthProvider {
|
|
214
|
+
baseUrl;
|
|
215
|
+
jwksUrl;
|
|
216
|
+
sessionCookieName;
|
|
217
|
+
signUpEnabledConfig;
|
|
218
|
+
constructor(options) {
|
|
219
|
+
super({ name: options?.name ?? "neon" });
|
|
220
|
+
const baseUrl = options?.baseUrl ?? process.env.NEON_AUTH_BASE_URL;
|
|
221
|
+
if (!baseUrl) throw new Error("Neon Auth base URL is required, please provide it in the options or set the NEON_AUTH_BASE_URL environment variable");
|
|
222
|
+
this.baseUrl = stripTrailingSlashes(baseUrl);
|
|
223
|
+
this.jwksUrl = options?.jwksUrl ?? process.env.NEON_AUTH_JWKS_URL ?? `${this.baseUrl}/auth/jwks`;
|
|
224
|
+
this.sessionCookieName = options?.sessionCookieName ?? "neonauth.session_token";
|
|
225
|
+
this.signUpEnabledConfig = options?.signUpEnabled ?? true;
|
|
226
|
+
this.registerOptions(options);
|
|
227
|
+
}
|
|
228
|
+
/** Expose the base URL for RBAC or other consumers. */
|
|
229
|
+
getBaseUrl() {
|
|
230
|
+
return this.baseUrl;
|
|
231
|
+
}
|
|
232
|
+
isSignUpEnabled() {
|
|
233
|
+
return this.signUpEnabledConfig;
|
|
234
|
+
}
|
|
235
|
+
async getCurrentUser(request) {
|
|
236
|
+
try {
|
|
237
|
+
const result = await this.fetchSession(request.headers);
|
|
238
|
+
if (!result?.user) return null;
|
|
239
|
+
return mapNeonUserToEEUser(result.user);
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async getUser(_userId) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
getUserProfileUrl(user) {
|
|
248
|
+
return `/profile/${user.id}`;
|
|
249
|
+
}
|
|
250
|
+
async authenticateToken(token, request) {
|
|
251
|
+
if (!token || typeof token !== "string") return null;
|
|
252
|
+
const jwtResult = await this.verifyJwt(token);
|
|
253
|
+
if (jwtResult) return {
|
|
254
|
+
user: {
|
|
255
|
+
id: jwtResult.sub ?? "",
|
|
256
|
+
email: jwtResult.email ?? "",
|
|
257
|
+
name: jwtResult.name ?? "",
|
|
258
|
+
image: jwtResult.picture ?? null,
|
|
259
|
+
emailVerified: jwtResult.email_verified ?? false,
|
|
260
|
+
createdAt: jwtResult.iat ? (/* @__PURE__ */ new Date(jwtResult.iat * 1e3)).toISOString() : "",
|
|
261
|
+
updatedAt: ""
|
|
262
|
+
},
|
|
263
|
+
jwt: jwtResult
|
|
264
|
+
};
|
|
265
|
+
try {
|
|
266
|
+
const headers = new Headers();
|
|
267
|
+
const cookieHeader = (0, _mastra_core_auth.getRequestHeader)(request, "Cookie");
|
|
268
|
+
if (cookieHeader) headers.set("Cookie", cookieHeader);
|
|
269
|
+
const hasSessionCookie = !!cookieHeader?.split(";").some((pair) => pair.trim().split("=")[0]?.trim() === this.sessionCookieName);
|
|
270
|
+
if (token && !hasSessionCookie) {
|
|
271
|
+
const existingCookies = cookieHeader ? `${cookieHeader}; ` : "";
|
|
272
|
+
headers.set("Cookie", `${existingCookies}${this.sessionCookieName}=${token}`);
|
|
273
|
+
}
|
|
274
|
+
const result = await this.fetchSession(headers);
|
|
275
|
+
if (!result?.session || !result?.user) return null;
|
|
276
|
+
return {
|
|
277
|
+
session: result.session,
|
|
278
|
+
user: result.user
|
|
279
|
+
};
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async authorizeUser(user) {
|
|
285
|
+
if (!user?.user?.id) return false;
|
|
286
|
+
if (user.jwt?.exp && user.jwt.exp * 1e3 < Date.now()) return false;
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
async signIn(email, password, request) {
|
|
290
|
+
const response = await fetch(`${this.baseUrl}/auth/sign-in/email`, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: {
|
|
293
|
+
"Content-Type": "application/json",
|
|
294
|
+
...request?.headers ? Object.fromEntries(request.headers.entries()) : {}
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
email,
|
|
298
|
+
password
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
if (!response.ok) {
|
|
302
|
+
const errorData = await response.json().catch(() => ({}));
|
|
303
|
+
throw new Error(errorData.message || "Invalid email or password");
|
|
304
|
+
}
|
|
305
|
+
const result = await response.json();
|
|
306
|
+
if (!result?.user) throw new Error("Invalid email or password");
|
|
307
|
+
const cookies = parseCookies(response);
|
|
308
|
+
return {
|
|
309
|
+
user: mapNeonUserToEEUser(result.user),
|
|
310
|
+
token: result.token ?? void 0,
|
|
311
|
+
cookies
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async signUp(email, password, name, request) {
|
|
315
|
+
const displayName = name ?? email.split("@")[0] ?? "User";
|
|
316
|
+
const response = await fetch(`${this.baseUrl}/auth/sign-up/email`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "application/json",
|
|
320
|
+
...request?.headers ? Object.fromEntries(request.headers.entries()) : {}
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify({
|
|
323
|
+
email,
|
|
324
|
+
password,
|
|
325
|
+
name: displayName
|
|
326
|
+
})
|
|
327
|
+
});
|
|
328
|
+
if (!response.ok) {
|
|
329
|
+
const errorData = await response.json().catch(() => ({}));
|
|
330
|
+
throw new Error(errorData.message || "Failed to create account");
|
|
331
|
+
}
|
|
332
|
+
const result = await response.json();
|
|
333
|
+
if (!result?.user) throw new Error("Failed to create account");
|
|
334
|
+
const cookies = parseCookies(response);
|
|
335
|
+
return {
|
|
336
|
+
user: mapNeonUserToEEUser(result.user),
|
|
337
|
+
token: result.token ?? void 0,
|
|
338
|
+
cookies
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async createSession(_userId, metadata) {
|
|
342
|
+
const now = /* @__PURE__ */ new Date();
|
|
343
|
+
const expiresAt = new Date(now.getTime() + 10080 * 60 * 1e3);
|
|
344
|
+
return {
|
|
345
|
+
id: crypto.randomUUID(),
|
|
346
|
+
userId: _userId,
|
|
347
|
+
createdAt: now,
|
|
348
|
+
expiresAt,
|
|
349
|
+
metadata
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
async validateSession(sessionId) {
|
|
353
|
+
try {
|
|
354
|
+
const headers = new Headers();
|
|
355
|
+
headers.set("Cookie", `${this.sessionCookieName}=${sessionId}`);
|
|
356
|
+
const result = await this.fetchSession(headers);
|
|
357
|
+
if (!result?.session) return null;
|
|
358
|
+
return {
|
|
359
|
+
id: result.session.id,
|
|
360
|
+
userId: result.session.userId,
|
|
361
|
+
expiresAt: new Date(result.session.expiresAt),
|
|
362
|
+
createdAt: new Date(result.session.createdAt)
|
|
363
|
+
};
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async destroySession(_sessionId) {}
|
|
369
|
+
async refreshSession(sessionId) {
|
|
370
|
+
return this.validateSession(sessionId);
|
|
371
|
+
}
|
|
372
|
+
getSessionIdFromRequest(request) {
|
|
373
|
+
const cookieHeader = request.headers.get("Cookie");
|
|
374
|
+
if (!cookieHeader) return null;
|
|
375
|
+
for (const pair of cookieHeader.split(";")) {
|
|
376
|
+
const [key, ...rest] = pair.trim().split("=");
|
|
377
|
+
if (key?.trim() === this.sessionCookieName) return rest.join("=") || null;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
getSessionHeaders(session) {
|
|
382
|
+
const cookie = session._sessionCookie;
|
|
383
|
+
if (typeof cookie === "string") return { "Set-Cookie": cookie };
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
getClearSessionHeaders() {
|
|
387
|
+
return { "Set-Cookie": [`${this.sessionCookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`, `${this.sessionCookieName}_sig=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`].join(", ") };
|
|
388
|
+
}
|
|
389
|
+
async verifyJwt(token) {
|
|
390
|
+
try {
|
|
391
|
+
const { payload } = await (0, jose.jwtVerify)(token, (0, jose.createRemoteJWKSet)(new URL(this.jwksUrl)));
|
|
392
|
+
return payload;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/** Fetch and validate a session from the Neon Auth REST API. */
|
|
398
|
+
async fetchSession(headers) {
|
|
399
|
+
const response = await fetch(`${this.baseUrl}/auth/get-session`, {
|
|
400
|
+
method: "GET",
|
|
401
|
+
headers
|
|
402
|
+
});
|
|
403
|
+
if (!response.ok) return null;
|
|
404
|
+
const data = await response.json();
|
|
405
|
+
if (!data?.session || !data?.user) return null;
|
|
406
|
+
return data;
|
|
407
|
+
}
|
|
367
408
|
};
|
|
368
|
-
|
|
409
|
+
//#endregion
|
|
369
410
|
exports.MastraAuthNeon = MastraAuthNeon;
|
|
370
411
|
exports.MastraRBACNeon = MastraRBACNeon;
|
|
371
412
|
exports.mapNeonUserToEEUser = mapNeonUserToEEUser;
|
|
372
|
-
|
|
413
|
+
|
|
373
414
|
//# sourceMappingURL=index.cjs.map
|