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