@webtwizz/cloud 0.1.0 → 0.3.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/AI_GUIDE.md +2 -0
- package/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/dist/chunk-7EXXDRMF.js +376 -0
- package/dist/client.js +789 -4
- package/dist/files.d.ts +1 -1
- package/dist/files.js +5 -5
- package/dist/server.d.ts +3 -1
- package/dist/server.js +1488 -4
- package/package.json +9 -5
package/dist/server.js
CHANGED
|
@@ -1,19 +1,1503 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NEON_AUTH_SESSION_VERIFIER_PARAM_NAME,
|
|
3
|
+
normalizeBetterAuthError
|
|
4
|
+
} from "./chunk-7EXXDRMF.js";
|
|
1
5
|
import {
|
|
2
6
|
env
|
|
3
7
|
} from "./chunk-ESAV545E.js";
|
|
4
8
|
|
|
5
9
|
// src/server.ts
|
|
6
10
|
import "server-only";
|
|
7
|
-
|
|
11
|
+
|
|
12
|
+
// node_modules/@neondatabase/auth/dist/server-b0OzGjXl.mjs
|
|
13
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
14
|
+
import { parseCookies, parseSetCookieHeader } from "better-auth/cookies";
|
|
15
|
+
var NEON_AUTH_SERVER_PROXY_HEADER = "x-neon-auth-proxy";
|
|
16
|
+
var API_ENDPOINTS = {
|
|
17
|
+
getSession: {
|
|
18
|
+
path: "get-session",
|
|
19
|
+
method: "GET"
|
|
20
|
+
},
|
|
21
|
+
getAccessToken: {
|
|
22
|
+
path: "get-access-token",
|
|
23
|
+
method: "GET"
|
|
24
|
+
},
|
|
25
|
+
listSessions: {
|
|
26
|
+
path: "list-sessions",
|
|
27
|
+
method: "GET"
|
|
28
|
+
},
|
|
29
|
+
revokeSession: {
|
|
30
|
+
path: "revoke-session",
|
|
31
|
+
method: "POST"
|
|
32
|
+
},
|
|
33
|
+
revokeSessions: {
|
|
34
|
+
path: "revoke-sessions",
|
|
35
|
+
method: "POST"
|
|
36
|
+
},
|
|
37
|
+
revokeOtherSessions: {
|
|
38
|
+
path: "revoke-all-sessions",
|
|
39
|
+
method: "POST"
|
|
40
|
+
},
|
|
41
|
+
refreshToken: {
|
|
42
|
+
path: "refresh-token",
|
|
43
|
+
method: "POST"
|
|
44
|
+
},
|
|
45
|
+
signIn: {
|
|
46
|
+
email: {
|
|
47
|
+
path: "sign-in/email",
|
|
48
|
+
method: "POST"
|
|
49
|
+
},
|
|
50
|
+
social: {
|
|
51
|
+
path: "sign-in/social",
|
|
52
|
+
method: "POST"
|
|
53
|
+
},
|
|
54
|
+
emailOtp: {
|
|
55
|
+
path: "sign-in/email-otp",
|
|
56
|
+
method: "POST"
|
|
57
|
+
},
|
|
58
|
+
magicLink: {
|
|
59
|
+
path: "sign-in/magic-link",
|
|
60
|
+
method: "POST"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
signUp: { email: {
|
|
64
|
+
path: "sign-up/email",
|
|
65
|
+
method: "POST"
|
|
66
|
+
} },
|
|
67
|
+
signOut: {
|
|
68
|
+
path: "sign-out",
|
|
69
|
+
method: "POST"
|
|
70
|
+
},
|
|
71
|
+
listAccounts: {
|
|
72
|
+
path: "list-accounts",
|
|
73
|
+
method: "GET"
|
|
74
|
+
},
|
|
75
|
+
accountInfo: {
|
|
76
|
+
path: "account-info",
|
|
77
|
+
method: "GET"
|
|
78
|
+
},
|
|
79
|
+
updateUser: {
|
|
80
|
+
path: "update-user",
|
|
81
|
+
method: "POST"
|
|
82
|
+
},
|
|
83
|
+
deleteUser: {
|
|
84
|
+
path: "delete-user",
|
|
85
|
+
method: "POST"
|
|
86
|
+
},
|
|
87
|
+
changePassword: {
|
|
88
|
+
path: "change-password",
|
|
89
|
+
method: "POST"
|
|
90
|
+
},
|
|
91
|
+
sendVerificationEmail: {
|
|
92
|
+
path: "send-verification-email",
|
|
93
|
+
method: "POST"
|
|
94
|
+
},
|
|
95
|
+
verifyEmail: {
|
|
96
|
+
path: "verify-email",
|
|
97
|
+
method: "POST"
|
|
98
|
+
},
|
|
99
|
+
resetPassword: {
|
|
100
|
+
path: "reset-password",
|
|
101
|
+
method: "POST"
|
|
102
|
+
},
|
|
103
|
+
requestPasswordReset: {
|
|
104
|
+
path: "request-password-reset",
|
|
105
|
+
method: "POST"
|
|
106
|
+
},
|
|
107
|
+
token: {
|
|
108
|
+
path: "token",
|
|
109
|
+
method: "GET"
|
|
110
|
+
},
|
|
111
|
+
jwks: {
|
|
112
|
+
path: "jwt",
|
|
113
|
+
method: "GET"
|
|
114
|
+
},
|
|
115
|
+
getAnonymousToken: {
|
|
116
|
+
path: "token/anonymous",
|
|
117
|
+
method: "GET"
|
|
118
|
+
},
|
|
119
|
+
admin: {
|
|
120
|
+
createUser: {
|
|
121
|
+
path: "admin/create-user",
|
|
122
|
+
method: "POST"
|
|
123
|
+
},
|
|
124
|
+
listUsers: {
|
|
125
|
+
path: "admin/list-users",
|
|
126
|
+
method: "GET"
|
|
127
|
+
},
|
|
128
|
+
setRole: {
|
|
129
|
+
path: "admin/set-role",
|
|
130
|
+
method: "POST"
|
|
131
|
+
},
|
|
132
|
+
setUserPassword: {
|
|
133
|
+
path: "admin/set-user-password",
|
|
134
|
+
method: "POST"
|
|
135
|
+
},
|
|
136
|
+
updateUser: {
|
|
137
|
+
path: "admin/update-user",
|
|
138
|
+
method: "POST"
|
|
139
|
+
},
|
|
140
|
+
banUser: {
|
|
141
|
+
path: "admin/ban-user",
|
|
142
|
+
method: "POST"
|
|
143
|
+
},
|
|
144
|
+
unbanUser: {
|
|
145
|
+
path: "admin/unban-user",
|
|
146
|
+
method: "POST"
|
|
147
|
+
},
|
|
148
|
+
listUserSessions: {
|
|
149
|
+
path: "admin/list-user-sessions",
|
|
150
|
+
method: "GET"
|
|
151
|
+
},
|
|
152
|
+
revokeUserSession: {
|
|
153
|
+
path: "admin/revoke-user-session",
|
|
154
|
+
method: "POST"
|
|
155
|
+
},
|
|
156
|
+
revokeUserSessions: {
|
|
157
|
+
path: "admin/revoke-user-sessions",
|
|
158
|
+
method: "POST"
|
|
159
|
+
},
|
|
160
|
+
impersonateUser: {
|
|
161
|
+
path: "admin/impersonate-user",
|
|
162
|
+
method: "POST"
|
|
163
|
+
},
|
|
164
|
+
stopImpersonating: {
|
|
165
|
+
path: "admin/stop-impersonating",
|
|
166
|
+
method: "POST"
|
|
167
|
+
},
|
|
168
|
+
removeUser: {
|
|
169
|
+
path: "admin/remove-user",
|
|
170
|
+
method: "POST"
|
|
171
|
+
},
|
|
172
|
+
hasPermission: {
|
|
173
|
+
path: "admin/has-permission",
|
|
174
|
+
method: "POST"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
organization: {
|
|
178
|
+
create: {
|
|
179
|
+
path: "organization/create",
|
|
180
|
+
method: "POST"
|
|
181
|
+
},
|
|
182
|
+
update: {
|
|
183
|
+
path: "organization/update",
|
|
184
|
+
method: "POST"
|
|
185
|
+
},
|
|
186
|
+
delete: {
|
|
187
|
+
path: "organization/delete",
|
|
188
|
+
method: "POST"
|
|
189
|
+
},
|
|
190
|
+
list: {
|
|
191
|
+
path: "organization/list",
|
|
192
|
+
method: "GET"
|
|
193
|
+
},
|
|
194
|
+
getFullOrganization: {
|
|
195
|
+
path: "organization/get-full-organization",
|
|
196
|
+
method: "GET"
|
|
197
|
+
},
|
|
198
|
+
setActive: {
|
|
199
|
+
path: "organization/set-active",
|
|
200
|
+
method: "POST"
|
|
201
|
+
},
|
|
202
|
+
checkSlug: {
|
|
203
|
+
path: "organization/check-slug",
|
|
204
|
+
method: "GET"
|
|
205
|
+
},
|
|
206
|
+
listMembers: {
|
|
207
|
+
path: "organization/list-members",
|
|
208
|
+
method: "GET"
|
|
209
|
+
},
|
|
210
|
+
removeMember: {
|
|
211
|
+
path: "organization/remove-member",
|
|
212
|
+
method: "POST"
|
|
213
|
+
},
|
|
214
|
+
updateMemberRole: {
|
|
215
|
+
path: "organization/update-member-role",
|
|
216
|
+
method: "POST"
|
|
217
|
+
},
|
|
218
|
+
leave: {
|
|
219
|
+
path: "organization/leave",
|
|
220
|
+
method: "POST"
|
|
221
|
+
},
|
|
222
|
+
getActiveMember: {
|
|
223
|
+
path: "organization/get-active-member",
|
|
224
|
+
method: "GET"
|
|
225
|
+
},
|
|
226
|
+
getActiveMemberRole: {
|
|
227
|
+
path: "organization/get-active-member-role",
|
|
228
|
+
method: "GET"
|
|
229
|
+
},
|
|
230
|
+
inviteMember: {
|
|
231
|
+
path: "organization/invite-member",
|
|
232
|
+
method: "POST"
|
|
233
|
+
},
|
|
234
|
+
acceptInvitation: {
|
|
235
|
+
path: "organization/accept-invitation",
|
|
236
|
+
method: "POST"
|
|
237
|
+
},
|
|
238
|
+
rejectInvitation: {
|
|
239
|
+
path: "organization/reject-invitation",
|
|
240
|
+
method: "POST"
|
|
241
|
+
},
|
|
242
|
+
cancelInvitation: {
|
|
243
|
+
path: "organization/cancel-invitation",
|
|
244
|
+
method: "POST"
|
|
245
|
+
},
|
|
246
|
+
getInvitation: {
|
|
247
|
+
path: "organization/get-invitation",
|
|
248
|
+
method: "GET"
|
|
249
|
+
},
|
|
250
|
+
listInvitations: {
|
|
251
|
+
path: "organization/list-invitations",
|
|
252
|
+
method: "GET"
|
|
253
|
+
},
|
|
254
|
+
listUserInvitations: {
|
|
255
|
+
path: "organization/list-user-invitations",
|
|
256
|
+
method: "GET"
|
|
257
|
+
},
|
|
258
|
+
hasPermission: {
|
|
259
|
+
path: "organization/has-permission",
|
|
260
|
+
method: "POST"
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
emailOtp: {
|
|
264
|
+
sendVerificationOtp: {
|
|
265
|
+
path: "email-otp/send-verification-otp",
|
|
266
|
+
method: "POST"
|
|
267
|
+
},
|
|
268
|
+
verifyEmail: {
|
|
269
|
+
path: "email-otp/verify-email",
|
|
270
|
+
method: "POST"
|
|
271
|
+
},
|
|
272
|
+
checkVerificationOtp: {
|
|
273
|
+
path: "email-otp/check-verification-otp",
|
|
274
|
+
method: "POST"
|
|
275
|
+
},
|
|
276
|
+
resetPassword: {
|
|
277
|
+
path: "email-otp/passcode",
|
|
278
|
+
method: "POST"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
magicLink: { verify: {
|
|
282
|
+
path: "magic-link/verify",
|
|
283
|
+
method: "GET"
|
|
284
|
+
} }
|
|
285
|
+
};
|
|
286
|
+
var NEON_AUTH_COOKIE_PREFIX = "__Secure-neon-auth";
|
|
287
|
+
var NEON_AUTH_SESSION_DATA_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.local.session_data`;
|
|
288
|
+
var NEON_AUTH_LEGACY_SESSION_CHALLENGE_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.session_challange`;
|
|
289
|
+
var NEON_AUTH_SESSION_CHALLENGE_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.session_challenge`;
|
|
290
|
+
var NEON_AUTH_SESSION_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.session_token`;
|
|
291
|
+
var extractNeonAuthCookies = (headers2) => {
|
|
292
|
+
const cookieHeader = typeof headers2 === "string" ? headers2 : headers2.get("cookie");
|
|
293
|
+
if (!cookieHeader) return "";
|
|
294
|
+
const parsedCookies = parseCookies(cookieHeader);
|
|
295
|
+
const result = [];
|
|
296
|
+
for (const [name, value] of parsedCookies.entries()) if (name.startsWith(NEON_AUTH_COOKIE_PREFIX)) result.push(`${name}=${value}`);
|
|
297
|
+
return result.join("; ");
|
|
298
|
+
};
|
|
299
|
+
var parseSetCookies = (setCookieHeader) => {
|
|
300
|
+
const parsedCookies = parseSetCookieHeader(setCookieHeader);
|
|
301
|
+
const cookies2 = [];
|
|
302
|
+
for (const entry of parsedCookies.entries()) {
|
|
303
|
+
const [name, parsedCookie] = entry;
|
|
304
|
+
cookies2.push({
|
|
305
|
+
name,
|
|
306
|
+
value: decodeURIComponent(parsedCookie.value),
|
|
307
|
+
path: parsedCookie.path,
|
|
308
|
+
domain: parsedCookie.domain,
|
|
309
|
+
maxAge: parsedCookie["max-age"] ?? parsedCookie.maxAge,
|
|
310
|
+
expires: parsedCookie.expires,
|
|
311
|
+
httpOnly: parsedCookie.httponly ?? true,
|
|
312
|
+
secure: parsedCookie.secure ?? true,
|
|
313
|
+
sameSite: parsedCookie.samesite ?? "lax",
|
|
314
|
+
partitioned: parsedCookie.partitioned
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return cookies2;
|
|
318
|
+
};
|
|
319
|
+
var serializeSetCookie = (cookie) => {
|
|
320
|
+
let result = `${cookie.name}=${encodeURIComponent(cookie.value)}`;
|
|
321
|
+
if (cookie.path) result += `; Path=${cookie.path}`;
|
|
322
|
+
if (cookie.domain) result += `; Domain=${cookie.domain}`;
|
|
323
|
+
if (cookie.maxAge !== void 0) result += `; Max-Age=${cookie.maxAge}`;
|
|
324
|
+
if (cookie.expires) result += `; Expires=${cookie.expires.toUTCString()}`;
|
|
325
|
+
if (cookie.httpOnly) result += "; HttpOnly";
|
|
326
|
+
if (cookie.secure) result += "; Secure";
|
|
327
|
+
if (cookie.sameSite) {
|
|
328
|
+
const sameSite = cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1);
|
|
329
|
+
result += `; SameSite=${sameSite}`;
|
|
330
|
+
}
|
|
331
|
+
if (cookie.partitioned) result += "; Partitioned";
|
|
332
|
+
return result;
|
|
333
|
+
};
|
|
334
|
+
var parseCookieValue = (cookieString, name) => {
|
|
335
|
+
if (!cookieString) return null;
|
|
336
|
+
return parseCookies(cookieString).get(name) ?? null;
|
|
337
|
+
};
|
|
338
|
+
var DEFAULT_SESSION_CACHE_TTL_SECONDS = 300;
|
|
339
|
+
var JWS_ALGO = "HS256";
|
|
340
|
+
function emit$1(log, level, message, meta) {
|
|
341
|
+
if (log) {
|
|
342
|
+
log[level](message, meta);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (meta && Object.keys(meta).length > 0) console[level](message, meta);
|
|
346
|
+
else console[level](message);
|
|
347
|
+
}
|
|
348
|
+
function parseDate(dateValue, fieldName) {
|
|
349
|
+
const date = new Date(dateValue);
|
|
350
|
+
if (Number.isNaN(date.getTime())) throw new TypeError(`Invalid date value for ${fieldName}: ${JSON.stringify(dateValue)}`);
|
|
351
|
+
return date;
|
|
352
|
+
}
|
|
353
|
+
async function signSessionDataCookie(sessionData, secret, ttlSeconds = DEFAULT_SESSION_CACHE_TTL_SECONDS) {
|
|
354
|
+
const ttlMs = ttlSeconds * 1e3;
|
|
355
|
+
const expiresAt = Math.min(sessionData.session.expiresAt.getTime(), Date.now() + ttlMs);
|
|
356
|
+
return {
|
|
357
|
+
value: await signPayload(sessionData, expiresAt, secret),
|
|
358
|
+
expiresAt: new Date(expiresAt)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function signPayload(sessionData, expiresAt, secret) {
|
|
362
|
+
const encodedSecret = new TextEncoder().encode(secret);
|
|
363
|
+
const expSeconds = Math.floor(expiresAt / 1e3);
|
|
364
|
+
return new SignJWT(sessionData).setProtectedHeader({
|
|
365
|
+
alg: JWS_ALGO,
|
|
366
|
+
typ: "JWT"
|
|
367
|
+
}).setIssuedAt().setExpirationTime(expSeconds).setSubject(sessionData.user?.id ?? "anonymous").sign(encodedSecret);
|
|
368
|
+
}
|
|
369
|
+
function parseSessionData(json, log) {
|
|
370
|
+
if (!json || typeof json !== "object") return {
|
|
371
|
+
session: null,
|
|
372
|
+
user: null
|
|
373
|
+
};
|
|
374
|
+
const data = json;
|
|
375
|
+
if (!data.session || !data.user) return {
|
|
376
|
+
session: null,
|
|
377
|
+
user: null
|
|
378
|
+
};
|
|
379
|
+
try {
|
|
380
|
+
return {
|
|
381
|
+
session: {
|
|
382
|
+
...data.session,
|
|
383
|
+
expiresAt: parseDate(data.session.expiresAt, "session.expiresAt"),
|
|
384
|
+
createdAt: parseDate(data.session.createdAt, "session.createdAt"),
|
|
385
|
+
updatedAt: parseDate(data.session.updatedAt, "session.updatedAt")
|
|
386
|
+
},
|
|
387
|
+
user: {
|
|
388
|
+
...data.user,
|
|
389
|
+
createdAt: parseDate(data.user.createdAt, "user.createdAt"),
|
|
390
|
+
updatedAt: parseDate(data.user.updatedAt, "user.updatedAt")
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
emit$1(log, "error", "[parseSessionData] Failed to parse session dates:", {
|
|
395
|
+
error: error instanceof Error ? error.message : String(error),
|
|
396
|
+
hasSession: !!data.session,
|
|
397
|
+
hasUser: !!data.user
|
|
398
|
+
});
|
|
399
|
+
return {
|
|
400
|
+
session: null,
|
|
401
|
+
user: null
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function getSessionDataFromCookie(request, cookieName, cookieSecret, log) {
|
|
406
|
+
try {
|
|
407
|
+
const cookieHeader = request.headers.get("cookie");
|
|
408
|
+
if (!cookieHeader) return null;
|
|
409
|
+
const sessionDataCookie = parseCookies(cookieHeader).get(cookieName);
|
|
410
|
+
if (!sessionDataCookie) return null;
|
|
411
|
+
const result = await validateSessionData(sessionDataCookie, cookieSecret);
|
|
412
|
+
if (result.valid && result.payload) return result.payload;
|
|
413
|
+
emit$1(log, "warn", "[getSessionDataFromCookie] Invalid session cookie:", {
|
|
414
|
+
error: result.error,
|
|
415
|
+
cookieName
|
|
416
|
+
});
|
|
417
|
+
return null;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
emit$1(log, "error", "[getSessionDataFromCookie] Unexpected validation error:", {
|
|
420
|
+
error: error instanceof Error ? error.message : String(error),
|
|
421
|
+
cookieName,
|
|
422
|
+
...process.env.NODE_ENV !== "production" && { stack: error instanceof Error ? error.stack : void 0 }
|
|
423
|
+
});
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function fetchSessionWithCookie(sessionTokenCookie, baseUrl, log) {
|
|
428
|
+
let cookieName;
|
|
429
|
+
let cookieValue;
|
|
430
|
+
if (sessionTokenCookie.includes("=")) {
|
|
431
|
+
const [name, ...valueParts] = sessionTokenCookie.split(";")[0].trim().split("=");
|
|
432
|
+
cookieName = name.trim();
|
|
433
|
+
cookieValue = valueParts.join("=").trim();
|
|
434
|
+
} else throw new Error("Invalid session token cookie format");
|
|
435
|
+
if (!cookieName.includes("session_token")) throw new Error("session_token not found in cookie");
|
|
436
|
+
const response = await fetch(`${baseUrl}/get-session`, {
|
|
437
|
+
headers: { Cookie: `${cookieName}=${cookieValue}` },
|
|
438
|
+
signal: AbortSignal.timeout(3e3)
|
|
439
|
+
});
|
|
440
|
+
if (!response.ok) throw new Error(`Failed to fetch session data: ${response.status} ${response.statusText}`);
|
|
441
|
+
let body;
|
|
442
|
+
try {
|
|
443
|
+
body = await response.json();
|
|
444
|
+
} catch (error) {
|
|
445
|
+
throw new Error(`Failed to parse /get-session response as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
}
|
|
447
|
+
return parseSessionData(body, log);
|
|
448
|
+
}
|
|
449
|
+
var ERRORS = {
|
|
450
|
+
MISSING_AUTH_BASE_URL: "Missing required config: baseUrl. You must provide the auth URL of your Neon Auth instance in the config object.",
|
|
451
|
+
MISSING_COOKIE_SECRET: "Missing required config: cookies.secret. You must provide the cookie secret in the config object.",
|
|
452
|
+
COOKIE_SECRET_TOO_SHORT: "cookies.secret must be at least 32 characters long for security. Generate a secure secret with: openssl rand -base64 32",
|
|
453
|
+
INVALID_SESSION_DATA_TTL: "cookies.sessionDataTtl must be a positive number (in seconds)"
|
|
454
|
+
};
|
|
455
|
+
async function validateSessionData(sessionDataString, cookieSecret) {
|
|
456
|
+
try {
|
|
457
|
+
const { payload } = await jwtVerify(sessionDataString, new TextEncoder().encode(cookieSecret), { algorithms: ["HS256"] });
|
|
458
|
+
return {
|
|
459
|
+
valid: true,
|
|
460
|
+
payload: parseSessionData(payload)
|
|
461
|
+
};
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return {
|
|
464
|
+
valid: false,
|
|
465
|
+
error: error instanceof Error ? error.message : "Invalid session data"
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async function mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig, log) {
|
|
470
|
+
try {
|
|
471
|
+
const sessionData = await fetchSessionWithCookie(sessionTokenCookie, baseUrl, log);
|
|
472
|
+
if (!sessionData.session) return null;
|
|
473
|
+
const { value: signedData, expiresAt } = await signSessionDataCookie(sessionData, cookieConfig.secret, cookieConfig.sessionDataTtl);
|
|
474
|
+
const maxAge = Math.floor((expiresAt.getTime() - Date.now()) / 1e3);
|
|
475
|
+
return serializeSetCookie({
|
|
476
|
+
name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
477
|
+
value: signedData,
|
|
478
|
+
path: "/",
|
|
479
|
+
domain: cookieConfig.domain,
|
|
480
|
+
httpOnly: true,
|
|
481
|
+
secure: true,
|
|
482
|
+
sameSite: cookieConfig.sameSite ?? "lax",
|
|
483
|
+
maxAge
|
|
484
|
+
});
|
|
485
|
+
} catch (error) {
|
|
486
|
+
const meta = {
|
|
487
|
+
error: error instanceof Error ? error.message : String(error),
|
|
488
|
+
...process.env.NODE_ENV !== "production" && { stack: error instanceof Error ? error.stack : void 0 }
|
|
489
|
+
};
|
|
490
|
+
if (log) log.error("[mintSessionDataCookie] Failed to mint session_data cookie:", meta);
|
|
491
|
+
else console.error("[mintSessionDataCookie] Failed to mint session_data cookie:", meta);
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function mintSessionDataFromResponse(responseHeaders, baseUrl, cookieConfig, log) {
|
|
496
|
+
const setCookieHeaders = responseHeaders.getSetCookie();
|
|
497
|
+
let sessionTokenCookie;
|
|
498
|
+
let sessionTokenIsDeletion = false;
|
|
499
|
+
for (const cookieHeader of setCookieHeaders) {
|
|
500
|
+
const parsed = parseSetCookies(cookieHeader).find((cookie) => cookie.name === NEON_AUTH_SESSION_COOKIE_NAME);
|
|
501
|
+
if (parsed) {
|
|
502
|
+
sessionTokenCookie = cookieHeader;
|
|
503
|
+
sessionTokenIsDeletion = parsed.maxAge === 0 || parsed.expires !== void 0 && parsed.expires.getTime() <= Date.now();
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (!sessionTokenCookie) return null;
|
|
508
|
+
if (sessionTokenIsDeletion) return serializeSetCookie({
|
|
509
|
+
name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
510
|
+
value: "",
|
|
511
|
+
path: "/",
|
|
512
|
+
domain: cookieConfig.domain,
|
|
513
|
+
httpOnly: true,
|
|
514
|
+
secure: true,
|
|
515
|
+
sameSite: cookieConfig.sameSite ?? "lax",
|
|
516
|
+
maxAge: 0
|
|
517
|
+
});
|
|
518
|
+
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig, log);
|
|
519
|
+
}
|
|
520
|
+
async function mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfig, log) {
|
|
521
|
+
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig, log);
|
|
522
|
+
}
|
|
523
|
+
var LEVEL_RANK = {
|
|
524
|
+
error: 0,
|
|
525
|
+
warn: 1,
|
|
526
|
+
info: 2,
|
|
527
|
+
debug: 3
|
|
528
|
+
};
|
|
529
|
+
var consoleSink = {
|
|
530
|
+
error: (message, meta) => {
|
|
531
|
+
if (meta && Object.keys(meta).length > 0) console.error(message, meta);
|
|
532
|
+
else console.error(message);
|
|
533
|
+
},
|
|
534
|
+
warn: (message, meta) => {
|
|
535
|
+
if (meta && Object.keys(meta).length > 0) console.warn(message, meta);
|
|
536
|
+
else console.warn(message);
|
|
537
|
+
},
|
|
538
|
+
info: (message, meta) => {
|
|
539
|
+
if (meta && Object.keys(meta).length > 0) console.info(message, meta);
|
|
540
|
+
else console.info(message);
|
|
541
|
+
},
|
|
542
|
+
debug: (message, meta) => {
|
|
543
|
+
if (meta && Object.keys(meta).length > 0) console.debug(message, meta);
|
|
544
|
+
else console.debug(message);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
function wrapWithLevel(level, logger) {
|
|
548
|
+
const minRank = LEVEL_RANK[level];
|
|
549
|
+
const gate = (messageLevel, fn) => {
|
|
550
|
+
return (message, meta) => {
|
|
551
|
+
if (LEVEL_RANK[messageLevel] <= minRank) fn(message, meta);
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
return {
|
|
555
|
+
error: gate("error", logger.error),
|
|
556
|
+
warn: gate("warn", logger.warn),
|
|
557
|
+
info: gate("info", logger.info),
|
|
558
|
+
debug: gate("debug", logger.debug)
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
var noopResolved = {
|
|
562
|
+
error: () => {
|
|
563
|
+
},
|
|
564
|
+
warn: () => {
|
|
565
|
+
},
|
|
566
|
+
info: () => {
|
|
567
|
+
},
|
|
568
|
+
debug: () => {
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
function resolveNeonAuthLogging(input) {
|
|
572
|
+
if (input?.logLevel === "silent") return noopResolved;
|
|
573
|
+
const level = input?.logLevel ?? "warn";
|
|
574
|
+
const raw = input?.logger ?? {};
|
|
575
|
+
return wrapWithLevel(level, {
|
|
576
|
+
error: raw.error ?? consoleSink.error,
|
|
577
|
+
warn: raw.warn ?? consoleSink.warn,
|
|
578
|
+
info: raw.info ?? consoleSink.info,
|
|
579
|
+
debug: raw.debug ?? consoleSink.debug
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function resolveLog(log) {
|
|
583
|
+
if (!log) return void 0;
|
|
584
|
+
if (log.error && log.warn && log.info && log.debug) return log;
|
|
585
|
+
return resolveNeonAuthLogging({ logger: log });
|
|
586
|
+
}
|
|
587
|
+
function readErrnoCode(node) {
|
|
588
|
+
if (!node || typeof node !== "object") return void 0;
|
|
589
|
+
const code = node.code;
|
|
590
|
+
return typeof code === "string" ? code : void 0;
|
|
591
|
+
}
|
|
592
|
+
function isAbortError(node) {
|
|
593
|
+
if (node instanceof Error && node.name === "AbortError") return true;
|
|
594
|
+
if (typeof node === "object" && node !== null && "name" in node && node.name === "AbortError") return true;
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
function isTlsRelated(code, message) {
|
|
598
|
+
if (!code && !message) return false;
|
|
599
|
+
const c = code ?? "";
|
|
600
|
+
if (c.startsWith("ERR_TLS") || c.startsWith("CERT_") || c === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return true;
|
|
601
|
+
const m = message.toLowerCase();
|
|
602
|
+
return m.includes("certificate") || m.includes("ssl") || m.includes("tls") || m.includes("wrong version number");
|
|
603
|
+
}
|
|
604
|
+
function isLegacyFetchTypeError(node) {
|
|
605
|
+
return node instanceof TypeError && typeof node.message === "string" && node.message.includes("fetch");
|
|
606
|
+
}
|
|
607
|
+
function collectRelatedErrors(error) {
|
|
608
|
+
const seen = /* @__PURE__ */ new Set();
|
|
609
|
+
const list = [];
|
|
610
|
+
function add(e) {
|
|
611
|
+
if (e === void 0 || e === null) return;
|
|
612
|
+
if (typeof e === "object") {
|
|
613
|
+
if (seen.has(e)) return;
|
|
614
|
+
seen.add(e);
|
|
615
|
+
}
|
|
616
|
+
list.push(e);
|
|
617
|
+
if (e instanceof Error && e.cause !== void 0) add(e.cause);
|
|
618
|
+
if (e instanceof AggregateError && Array.isArray(e.errors)) for (const sub of e.errors) add(sub);
|
|
619
|
+
}
|
|
620
|
+
add(error);
|
|
621
|
+
return list;
|
|
622
|
+
}
|
|
623
|
+
function clientMessageFor(code) {
|
|
624
|
+
switch (code) {
|
|
625
|
+
case "NETWORK_DNS":
|
|
626
|
+
return "Could not resolve authentication server hostname";
|
|
627
|
+
case "NETWORK_REFUSED":
|
|
628
|
+
return "Connection refused by authentication server";
|
|
629
|
+
case "NETWORK_TIMEOUT":
|
|
630
|
+
return "Authentication server connection timed out";
|
|
631
|
+
case "NETWORK_TLS":
|
|
632
|
+
return "TLS error connecting to authentication server";
|
|
633
|
+
case "NETWORK_RESET":
|
|
634
|
+
return "Connection to authentication server was reset";
|
|
635
|
+
case "NETWORK_ABORT":
|
|
636
|
+
return "Authentication request was aborted";
|
|
637
|
+
default:
|
|
638
|
+
return "Unable to connect to authentication server";
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
var INTERNAL_PUBLIC_MESSAGE = "Internal Server Error";
|
|
642
|
+
function classifyFetchFailure(error) {
|
|
643
|
+
const nodes = collectRelatedErrors(error);
|
|
644
|
+
for (const node of nodes) if (isAbortError(node)) return {
|
|
645
|
+
kind: "transport",
|
|
646
|
+
code: "NETWORK_ABORT",
|
|
647
|
+
detail: "AbortError",
|
|
648
|
+
clientMessage: clientMessageFor("NETWORK_ABORT")
|
|
649
|
+
};
|
|
650
|
+
for (const node of nodes) {
|
|
651
|
+
const code = readErrnoCode(node);
|
|
652
|
+
const message = node instanceof Error ? node.message : "";
|
|
653
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return {
|
|
654
|
+
kind: "transport",
|
|
655
|
+
code: "NETWORK_DNS",
|
|
656
|
+
detail: code,
|
|
657
|
+
clientMessage: clientMessageFor("NETWORK_DNS")
|
|
658
|
+
};
|
|
659
|
+
if (code === "ECONNREFUSED") return {
|
|
660
|
+
kind: "transport",
|
|
661
|
+
code: "NETWORK_REFUSED",
|
|
662
|
+
detail: code,
|
|
663
|
+
clientMessage: clientMessageFor("NETWORK_REFUSED")
|
|
664
|
+
};
|
|
665
|
+
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") return {
|
|
666
|
+
kind: "transport",
|
|
667
|
+
code: "NETWORK_TIMEOUT",
|
|
668
|
+
detail: code ?? "timeout",
|
|
669
|
+
clientMessage: clientMessageFor("NETWORK_TIMEOUT")
|
|
670
|
+
};
|
|
671
|
+
if (code === "ECONNRESET" || code === "EPIPE" || code === "ECONNABORTED") return {
|
|
672
|
+
kind: "transport",
|
|
673
|
+
code: "NETWORK_RESET",
|
|
674
|
+
detail: code,
|
|
675
|
+
clientMessage: clientMessageFor("NETWORK_RESET")
|
|
676
|
+
};
|
|
677
|
+
if (isTlsRelated(code, message)) return {
|
|
678
|
+
kind: "transport",
|
|
679
|
+
code: "NETWORK_TLS",
|
|
680
|
+
detail: code ?? "tls",
|
|
681
|
+
clientMessage: clientMessageFor("NETWORK_TLS")
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
for (const node of nodes) if (isLegacyFetchTypeError(node)) return {
|
|
685
|
+
kind: "transport",
|
|
686
|
+
code: "NETWORK_ERROR",
|
|
687
|
+
detail: "fetch TypeError",
|
|
688
|
+
clientMessage: clientMessageFor("NETWORK_ERROR")
|
|
689
|
+
};
|
|
690
|
+
const head = nodes[0];
|
|
691
|
+
return {
|
|
692
|
+
kind: "internal",
|
|
693
|
+
detail: (head instanceof Error ? head.message : INTERNAL_PUBLIC_MESSAGE).slice(0, 200),
|
|
694
|
+
clientMessage: INTERNAL_PUBLIC_MESSAGE
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function validateCookieConfig(cookies2) {
|
|
698
|
+
if (!cookies2.secret) throw new Error(ERRORS.MISSING_COOKIE_SECRET);
|
|
699
|
+
if (cookies2.secret.length < 32) throw new Error(ERRORS.COOKIE_SECRET_TOO_SHORT);
|
|
700
|
+
if (cookies2.sessionDataTtl !== void 0 && cookies2.sessionDataTtl <= 0) throw new Error(ERRORS.INVALID_SESSION_DATA_TTL);
|
|
701
|
+
}
|
|
702
|
+
function createAuthServer(config) {
|
|
703
|
+
const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain, sameSite } = config;
|
|
704
|
+
const log = resolveLog(config.log);
|
|
705
|
+
validateCookieConfig({
|
|
706
|
+
secret: cookieSecret,
|
|
707
|
+
sessionDataTtl
|
|
708
|
+
});
|
|
709
|
+
const effectiveSameSite = sameSite ?? "lax";
|
|
710
|
+
const fetchWithAuth = async (path, method, args) => {
|
|
711
|
+
const ctx = await getContext();
|
|
712
|
+
const cookies2 = await ctx.getCookies();
|
|
713
|
+
const origin = await ctx.getOrigin();
|
|
714
|
+
const framework = ctx.getFramework();
|
|
715
|
+
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
716
|
+
const { query, fetchOptions: _fetchOptions, ...body } = args || {};
|
|
717
|
+
if (query && typeof query === "object") {
|
|
718
|
+
const queryParams = query;
|
|
719
|
+
for (const [key, value] of Object.entries(queryParams)) if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
720
|
+
}
|
|
721
|
+
const headers2 = {
|
|
722
|
+
Cookie: cookies2,
|
|
723
|
+
Origin: origin,
|
|
724
|
+
[NEON_AUTH_SERVER_PROXY_HEADER]: framework
|
|
725
|
+
};
|
|
726
|
+
let requestBody;
|
|
727
|
+
if (method === "POST") {
|
|
728
|
+
headers2["Content-Type"] = "application/json";
|
|
729
|
+
requestBody = JSON.stringify(Object.keys(body).length > 0 ? body : {});
|
|
730
|
+
}
|
|
731
|
+
let response;
|
|
732
|
+
try {
|
|
733
|
+
response = await fetch(url.toString(), {
|
|
734
|
+
method,
|
|
735
|
+
headers: headers2,
|
|
736
|
+
body: requestBody
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
const classified = classifyFetchFailure(error);
|
|
740
|
+
if (classified.kind === "transport") {
|
|
741
|
+
log?.warn("[webtwizz-auth] Server API upstream fetch failed", {
|
|
742
|
+
component: "server-api",
|
|
743
|
+
path: url.pathname,
|
|
744
|
+
code: classified.code,
|
|
745
|
+
detail: classified.detail,
|
|
746
|
+
host: url.host
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
data: null,
|
|
750
|
+
error: {
|
|
751
|
+
message: classified.clientMessage,
|
|
752
|
+
status: 502,
|
|
753
|
+
statusText: "Bad Gateway",
|
|
754
|
+
code: classified.code
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
log?.error("[webtwizz-auth] Server API unexpected fetch error", {
|
|
759
|
+
component: "server-api",
|
|
760
|
+
path: url.pathname,
|
|
761
|
+
detail: classified.detail,
|
|
762
|
+
host: url.host,
|
|
763
|
+
err: error
|
|
764
|
+
});
|
|
765
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
766
|
+
}
|
|
767
|
+
if (response.ok) log?.debug("[webtwizz-auth] Server API upstream fetch completed", {
|
|
768
|
+
component: "server-api",
|
|
769
|
+
path: url.pathname,
|
|
770
|
+
status: response.status,
|
|
771
|
+
host: url.host
|
|
772
|
+
});
|
|
773
|
+
else if (response.status >= 500) log?.warn("[webtwizz-auth] Server API upstream HTTP error", {
|
|
774
|
+
component: "server-api",
|
|
775
|
+
path: url.pathname,
|
|
776
|
+
status: response.status,
|
|
777
|
+
statusText: response.statusText,
|
|
778
|
+
host: url.host
|
|
779
|
+
});
|
|
780
|
+
else log?.info("[webtwizz-auth] Server API upstream HTTP non-2xx", {
|
|
781
|
+
component: "server-api",
|
|
782
|
+
path: url.pathname,
|
|
783
|
+
status: response.status,
|
|
784
|
+
statusText: response.statusText,
|
|
785
|
+
host: url.host
|
|
786
|
+
});
|
|
787
|
+
const setCookieHeaders = response.headers.getSetCookie();
|
|
788
|
+
if (setCookieHeaders.length > 0) {
|
|
789
|
+
for (const setCookieHeader of setCookieHeaders) {
|
|
790
|
+
const parsedCookies = parseSetCookies(setCookieHeader);
|
|
791
|
+
for (const cookie of parsedCookies) {
|
|
792
|
+
const cookieOptions = {
|
|
793
|
+
...cookie,
|
|
794
|
+
domain,
|
|
795
|
+
partitioned: void 0,
|
|
796
|
+
sameSite: effectiveSameSite,
|
|
797
|
+
secure: true
|
|
798
|
+
};
|
|
799
|
+
await ctx.setCookie(cookie.name, cookie.value, cookieOptions);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, {
|
|
804
|
+
secret: cookieSecret,
|
|
805
|
+
sessionDataTtl,
|
|
806
|
+
domain,
|
|
807
|
+
sameSite
|
|
808
|
+
}, log);
|
|
809
|
+
if (sessionDataCookie) {
|
|
810
|
+
const [parsedSessionData] = parseSetCookies(sessionDataCookie);
|
|
811
|
+
if (parsedSessionData) await ctx.setCookie(parsedSessionData.name, parsedSessionData.value, parsedSessionData);
|
|
812
|
+
}
|
|
813
|
+
} catch (error) {
|
|
814
|
+
log?.warn("[webtwizz-auth] Failed to mint session data cookie", {
|
|
815
|
+
component: "server-api",
|
|
816
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
817
|
+
err: error
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const responseData = await response.json().catch(() => null);
|
|
822
|
+
if (!response.ok) {
|
|
823
|
+
const normalized = normalizeBetterAuthError({
|
|
824
|
+
status: response.status,
|
|
825
|
+
statusText: response.statusText,
|
|
826
|
+
message: responseData?.message || response.statusText,
|
|
827
|
+
code: responseData?.code,
|
|
828
|
+
body: responseData
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
data: null,
|
|
832
|
+
error: {
|
|
833
|
+
message: normalized.message,
|
|
834
|
+
status: normalized.status ?? response.status,
|
|
835
|
+
statusText: response.statusText,
|
|
836
|
+
code: normalized.code
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
data: responseData,
|
|
842
|
+
error: null
|
|
843
|
+
};
|
|
844
|
+
};
|
|
845
|
+
const baseServer = createApiProxy(API_ENDPOINTS, fetchWithAuth);
|
|
846
|
+
const originalGetSession = baseServer.getSession;
|
|
847
|
+
baseServer.getSession = async (...args) => {
|
|
848
|
+
const [data] = args;
|
|
849
|
+
if (!(data?.query?.disableCookieCache === "true")) try {
|
|
850
|
+
const cookiesString = await (await getContext()).getCookies();
|
|
851
|
+
const hasSessionToken = cookiesString.includes(NEON_AUTH_SESSION_COOKIE_NAME);
|
|
852
|
+
const sessionDataCookie = parseCookieValue(cookiesString, NEON_AUTH_SESSION_DATA_COOKIE_NAME);
|
|
853
|
+
if (sessionDataCookie && hasSessionToken) {
|
|
854
|
+
const result = await validateSessionData(sessionDataCookie, cookieSecret);
|
|
855
|
+
if (result.valid && result.payload) return {
|
|
856
|
+
data: result.payload,
|
|
857
|
+
error: null
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
} catch (error) {
|
|
861
|
+
log?.warn("[webtwizz-auth] Cookie validation error before getSession upstream call", {
|
|
862
|
+
component: "server-api",
|
|
863
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
864
|
+
err: error
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
return originalGetSession(...args);
|
|
868
|
+
};
|
|
869
|
+
return baseServer;
|
|
870
|
+
}
|
|
871
|
+
function isEndpointConfig(value) {
|
|
872
|
+
return typeof value === "object" && value !== null && "path" in value && "method" in value;
|
|
873
|
+
}
|
|
874
|
+
function createApiProxy(endpoints, fetchFn) {
|
|
875
|
+
return new Proxy({}, {
|
|
876
|
+
get(target, prop) {
|
|
877
|
+
if (prop in target) return target[prop];
|
|
878
|
+
const endpoint = endpoints[prop];
|
|
879
|
+
if (!endpoint) return;
|
|
880
|
+
if (isEndpointConfig(endpoint)) return (args) => fetchFn(endpoint.path, endpoint.method, args);
|
|
881
|
+
return createApiProxy(endpoint, fetchFn);
|
|
882
|
+
},
|
|
883
|
+
set(target, prop, value) {
|
|
884
|
+
target[prop] = value;
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
var PROXY_HEADERS = [
|
|
890
|
+
"user-agent",
|
|
891
|
+
"authorization",
|
|
892
|
+
"referer",
|
|
893
|
+
"content-type"
|
|
894
|
+
];
|
|
895
|
+
var NEON_AUTH_HEADER_MIDDLEWARE_NAME = "x-neon-auth-middleware";
|
|
896
|
+
function safeAuthHost(baseUrl) {
|
|
897
|
+
try {
|
|
898
|
+
return new URL(baseUrl).host;
|
|
899
|
+
} catch {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
var handleAuthRequest = async (baseUrl, request, path, log) => {
|
|
904
|
+
const headers2 = prepareRequestHeaders(request);
|
|
905
|
+
const body = await parseRequestBody(request);
|
|
906
|
+
try {
|
|
907
|
+
const upstreamURL = getUpstreamURL(baseUrl, path, { originalUrl: new URL(request.url) });
|
|
908
|
+
const response = await fetch(upstreamURL.toString(), {
|
|
909
|
+
method: request.method,
|
|
910
|
+
headers: headers2,
|
|
911
|
+
body
|
|
912
|
+
});
|
|
913
|
+
const host = safeAuthHost(baseUrl);
|
|
914
|
+
if (response.ok) log?.debug("[webtwizz-auth] Upstream fetch completed", {
|
|
915
|
+
component: "proxy",
|
|
916
|
+
proxyPath: path,
|
|
917
|
+
status: response.status,
|
|
918
|
+
host
|
|
919
|
+
});
|
|
920
|
+
else if (response.status >= 500) log?.warn("[webtwizz-auth] Upstream HTTP error", {
|
|
921
|
+
component: "proxy",
|
|
922
|
+
proxyPath: path,
|
|
923
|
+
status: response.status,
|
|
924
|
+
statusText: response.statusText,
|
|
925
|
+
host
|
|
926
|
+
});
|
|
927
|
+
else log?.info("[webtwizz-auth] Upstream HTTP non-2xx", {
|
|
928
|
+
component: "proxy",
|
|
929
|
+
proxyPath: path,
|
|
930
|
+
status: response.status,
|
|
931
|
+
statusText: response.statusText,
|
|
932
|
+
host
|
|
933
|
+
});
|
|
934
|
+
return response;
|
|
935
|
+
} catch (error) {
|
|
936
|
+
const classified = classifyFetchFailure(error);
|
|
937
|
+
if (classified.kind === "transport") {
|
|
938
|
+
log?.warn("[webtwizz-auth] Upstream fetch failed", {
|
|
939
|
+
component: "proxy",
|
|
940
|
+
proxyPath: path,
|
|
941
|
+
code: classified.code,
|
|
942
|
+
detail: classified.detail,
|
|
943
|
+
host: safeAuthHost(baseUrl)
|
|
944
|
+
});
|
|
945
|
+
return Response.json({
|
|
946
|
+
error: classified.clientMessage,
|
|
947
|
+
code: classified.code
|
|
948
|
+
}, {
|
|
949
|
+
status: 502,
|
|
950
|
+
headers: { "Content-Type": "application/json" }
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
log?.error("[webtwizz-auth] Unexpected proxy error", {
|
|
954
|
+
component: "proxy",
|
|
955
|
+
proxyPath: path,
|
|
956
|
+
detail: classified.detail,
|
|
957
|
+
host: safeAuthHost(baseUrl)
|
|
958
|
+
});
|
|
959
|
+
return Response.json({
|
|
960
|
+
error: classified.clientMessage,
|
|
961
|
+
code: "INTERNAL_ERROR"
|
|
962
|
+
}, {
|
|
963
|
+
status: 500,
|
|
964
|
+
headers: { "Content-Type": "application/json" }
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
var getUpstreamURL = (baseUrl, path, { originalUrl }) => {
|
|
969
|
+
const url = new URL(`${baseUrl}/${path}`);
|
|
970
|
+
if (originalUrl) {
|
|
971
|
+
url.search = originalUrl.search;
|
|
972
|
+
return url;
|
|
973
|
+
}
|
|
974
|
+
return url;
|
|
975
|
+
};
|
|
976
|
+
var prepareRequestHeaders = (request) => {
|
|
977
|
+
const headers2 = new Headers();
|
|
978
|
+
for (const header of PROXY_HEADERS) if (request.headers.get(header)) headers2.set(header, request.headers.get(header));
|
|
979
|
+
headers2.set("Origin", getOrigin(request));
|
|
980
|
+
headers2.set("Cookie", extractNeonAuthCookies(request.headers));
|
|
981
|
+
headers2.set(NEON_AUTH_HEADER_MIDDLEWARE_NAME, "true");
|
|
982
|
+
return headers2;
|
|
983
|
+
};
|
|
984
|
+
var getOrigin = (request) => {
|
|
985
|
+
return request.headers.get("origin") || request.headers.get("referer")?.split("/").slice(0, 3).join("/") || new URL(request.url).origin;
|
|
986
|
+
};
|
|
987
|
+
var parseRequestBody = async (request) => {
|
|
988
|
+
if (request.body) return request.text();
|
|
989
|
+
};
|
|
990
|
+
var RESPONSE_HEADERS_ALLOWLIST = [
|
|
991
|
+
"content-type",
|
|
992
|
+
"content-encoding",
|
|
993
|
+
"date",
|
|
994
|
+
"set-cookie",
|
|
995
|
+
"set-auth-jwt",
|
|
996
|
+
"set-auth-token",
|
|
997
|
+
"x-neon-ret-request-id"
|
|
998
|
+
];
|
|
999
|
+
var handleAuthResponse = async (response, baseUrl, cookieConfig, log) => {
|
|
1000
|
+
const responseHeaders = prepareResponseHeaders(response, cookieConfig);
|
|
1001
|
+
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, cookieConfig, log);
|
|
1002
|
+
if (sessionDataCookie) responseHeaders.append("Set-Cookie", sessionDataCookie);
|
|
1003
|
+
return new Response(response.body, {
|
|
1004
|
+
status: response.status,
|
|
1005
|
+
statusText: response.statusText,
|
|
1006
|
+
headers: responseHeaders
|
|
1007
|
+
});
|
|
1008
|
+
};
|
|
1009
|
+
var prepareResponseHeaders = (response, cookieConfig) => {
|
|
1010
|
+
const headers2 = new Headers();
|
|
1011
|
+
const effectiveSameSite = cookieConfig.sameSite ?? "lax";
|
|
1012
|
+
const { domain } = cookieConfig;
|
|
1013
|
+
for (const header of RESPONSE_HEADERS_ALLOWLIST) if (header === "set-cookie") {
|
|
1014
|
+
const cookies2 = response.headers.getSetCookie();
|
|
1015
|
+
for (const cookieHeader of cookies2) {
|
|
1016
|
+
const parsedCookies = parseSetCookies(cookieHeader);
|
|
1017
|
+
for (const parsedCookie of parsedCookies) {
|
|
1018
|
+
parsedCookie.partitioned = void 0;
|
|
1019
|
+
parsedCookie.sameSite = effectiveSameSite;
|
|
1020
|
+
parsedCookie.secure = true;
|
|
1021
|
+
if (domain) parsedCookie.domain = domain;
|
|
1022
|
+
headers2.append("Set-Cookie", serializeSetCookie(parsedCookie));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
const value = response.headers.get(header);
|
|
1027
|
+
if (value) headers2.set(header, value);
|
|
1028
|
+
}
|
|
1029
|
+
return headers2;
|
|
1030
|
+
};
|
|
1031
|
+
function emit(log, level, message, meta) {
|
|
1032
|
+
if (log) {
|
|
1033
|
+
log[level](message, meta);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (meta && Object.keys(meta).length > 0) console[level](message, meta);
|
|
1037
|
+
else console[level](message);
|
|
1038
|
+
}
|
|
1039
|
+
async function trySessionCache(request, baseUrl, cookieConfig, log) {
|
|
1040
|
+
if (new URL(request.url).searchParams.get("disableCookieCache") === "true") return null;
|
|
1041
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
1042
|
+
if (!cookieHeader.includes(NEON_AUTH_SESSION_COOKIE_NAME)) return null;
|
|
1043
|
+
const hasSessionData = parseCookies(cookieHeader).has(NEON_AUTH_SESSION_DATA_COOKIE_NAME);
|
|
1044
|
+
const mintAndReturn = async () => {
|
|
1045
|
+
const sessionTokenCookie = extractSessionTokenCookie(cookieHeader);
|
|
1046
|
+
if (!sessionTokenCookie) return null;
|
|
1047
|
+
const sessionDataCookieString = await mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfig, log);
|
|
1048
|
+
if (!sessionDataCookieString) return null;
|
|
1049
|
+
try {
|
|
1050
|
+
const sessionData = await fetchSessionWithCookie(sessionTokenCookie, baseUrl, log);
|
|
1051
|
+
if (!sessionData.session) return null;
|
|
1052
|
+
const response = Response.json(sessionData);
|
|
1053
|
+
response.headers.set("Set-Cookie", sessionDataCookieString);
|
|
1054
|
+
return response;
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
emit(log, "error", "[trySessionCache] Failed to fetch session after minting cookie:", { error: error instanceof Error ? error.message : String(error) });
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
if (!hasSessionData) return await mintAndReturn();
|
|
1061
|
+
try {
|
|
1062
|
+
const sessionData = await getSessionDataFromCookie(request, NEON_AUTH_SESSION_DATA_COOKIE_NAME, cookieConfig.secret, log);
|
|
1063
|
+
if (sessionData && sessionData.session) return Response.json(sessionData);
|
|
1064
|
+
return await mintAndReturn();
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1067
|
+
const errorName = error instanceof Error ? error.name : "Unknown";
|
|
1068
|
+
if (errorName === "JWTExpired") emit(log, "debug", "[trySessionCache] Session cookie expired, minting new one:", {
|
|
1069
|
+
error: errorMessage,
|
|
1070
|
+
url: request.url
|
|
1071
|
+
});
|
|
1072
|
+
else if (errorName === "JWTInvalid" || errorName === "JWTClaimValidationFailed") emit(log, "warn", "[trySessionCache] Invalid session cookie, minting new one:", {
|
|
1073
|
+
error: errorMessage,
|
|
1074
|
+
url: request.url
|
|
1075
|
+
});
|
|
1076
|
+
else emit(log, "error", "[trySessionCache] Unexpected validation error:", {
|
|
1077
|
+
error: errorMessage,
|
|
1078
|
+
url: request.url
|
|
1079
|
+
});
|
|
1080
|
+
return await mintAndReturn();
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function extractSessionTokenCookie(cookieHeader) {
|
|
1084
|
+
const sessionToken = parseCookies(cookieHeader).get(NEON_AUTH_SESSION_COOKIE_NAME);
|
|
1085
|
+
if (!sessionToken) return null;
|
|
1086
|
+
return `${NEON_AUTH_SESSION_COOKIE_NAME}=${sessionToken}`;
|
|
1087
|
+
}
|
|
1088
|
+
async function handleAuthProxyRequest(config) {
|
|
1089
|
+
const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite } = config;
|
|
1090
|
+
const log = resolveLog(config.log);
|
|
1091
|
+
if (path === API_ENDPOINTS.getSession.path && request.method === API_ENDPOINTS.getSession.method) {
|
|
1092
|
+
const cachedResponse = await trySessionCache(request, baseUrl, {
|
|
1093
|
+
secret: cookieSecret,
|
|
1094
|
+
sessionDataTtl,
|
|
1095
|
+
domain,
|
|
1096
|
+
sameSite
|
|
1097
|
+
}, log);
|
|
1098
|
+
if (cachedResponse) return cachedResponse;
|
|
1099
|
+
}
|
|
1100
|
+
return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path, log), baseUrl, {
|
|
1101
|
+
secret: cookieSecret,
|
|
1102
|
+
sessionDataTtl,
|
|
1103
|
+
domain,
|
|
1104
|
+
sameSite
|
|
1105
|
+
}, log);
|
|
1106
|
+
}
|
|
1107
|
+
function hasSessionChallengeCookie(cookies2) {
|
|
1108
|
+
return cookies2.has(NEON_AUTH_SESSION_CHALLENGE_COOKIE_NAME) || cookies2.has(NEON_AUTH_LEGACY_SESSION_CHALLENGE_COOKIE_NAME);
|
|
1109
|
+
}
|
|
1110
|
+
function needsSessionVerification(request) {
|
|
1111
|
+
const hasVerifier = new URL(request.url).searchParams.has(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
1112
|
+
const cookieHeader = request.headers.get("cookie");
|
|
1113
|
+
if (!cookieHeader) return false;
|
|
1114
|
+
const cookies2 = parseCookies(cookieHeader);
|
|
1115
|
+
return hasVerifier && hasSessionChallengeCookie(cookies2);
|
|
1116
|
+
}
|
|
1117
|
+
async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log) {
|
|
1118
|
+
const url = new URL(request.url);
|
|
1119
|
+
const verifier = url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
1120
|
+
const cookieHeader = request.headers.get("cookie");
|
|
1121
|
+
if (!cookieHeader) return null;
|
|
1122
|
+
const cookies2 = parseCookies(cookieHeader);
|
|
1123
|
+
if (!verifier || !hasSessionChallengeCookie(cookies2)) return null;
|
|
1124
|
+
const response = await handleAuthResponse(await handleAuthRequest(baseUrl, new Request(request.url, {
|
|
1125
|
+
method: "GET",
|
|
1126
|
+
headers: request.headers
|
|
1127
|
+
}), "get-session", log), baseUrl, {
|
|
1128
|
+
secret: cookieSecret,
|
|
1129
|
+
sessionDataTtl,
|
|
1130
|
+
domain,
|
|
1131
|
+
sameSite
|
|
1132
|
+
});
|
|
1133
|
+
if (response.ok) {
|
|
1134
|
+
const setCookieHeaders = response.headers.getSetCookie();
|
|
1135
|
+
url.searchParams.delete(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
1136
|
+
return {
|
|
1137
|
+
redirectUrl: url,
|
|
1138
|
+
cookies: setCookieHeaders,
|
|
1139
|
+
success: true
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
var DEFAULT_AUTH_SKIP_ROUTES = [
|
|
1145
|
+
"/api/auth",
|
|
1146
|
+
"/auth/callback",
|
|
1147
|
+
"/auth/sign-in",
|
|
1148
|
+
"/auth/sign-up",
|
|
1149
|
+
"/auth/magic-link",
|
|
1150
|
+
"/auth/email-otp",
|
|
1151
|
+
"/auth/forgot-password"
|
|
1152
|
+
];
|
|
1153
|
+
function shouldProtectRoute(pathname, skipRoutes) {
|
|
1154
|
+
return !skipRoutes.some((route) => isSamePathOrSubpath(pathname, route));
|
|
1155
|
+
}
|
|
1156
|
+
function isSamePathOrSubpath(pathname, routePathname) {
|
|
1157
|
+
const normalizedRoutePathname = routePathname === "/" ? routePathname : routePathname.replace(/\/+$/, "");
|
|
1158
|
+
if (pathname === normalizedRoutePathname) return true;
|
|
1159
|
+
return normalizedRoutePathname !== "/" && pathname.startsWith(`${normalizedRoutePathname}/`);
|
|
1160
|
+
}
|
|
1161
|
+
function checkSessionRequired(pathname, skipRoutes, loginPathname, session) {
|
|
1162
|
+
if (loginPathname !== null && isSamePathOrSubpath(pathname, loginPathname)) return {
|
|
1163
|
+
allowed: true,
|
|
1164
|
+
requiresRedirect: false
|
|
1165
|
+
};
|
|
1166
|
+
if (!shouldProtectRoute(pathname, skipRoutes)) return {
|
|
1167
|
+
allowed: true,
|
|
1168
|
+
requiresRedirect: false
|
|
1169
|
+
};
|
|
1170
|
+
if (!session || session.session === null) return {
|
|
1171
|
+
allowed: false,
|
|
1172
|
+
requiresRedirect: true
|
|
1173
|
+
};
|
|
1174
|
+
return {
|
|
1175
|
+
allowed: true,
|
|
1176
|
+
session,
|
|
1177
|
+
requiresRedirect: false
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
function stripBodyHeaders(headers2) {
|
|
1181
|
+
const next = new Headers(headers2);
|
|
1182
|
+
next.delete("content-type");
|
|
1183
|
+
next.delete("content-length");
|
|
1184
|
+
next.delete("transfer-encoding");
|
|
1185
|
+
return next;
|
|
1186
|
+
}
|
|
1187
|
+
async function processAuthMiddleware(config) {
|
|
1188
|
+
const log = config.log ?? resolveNeonAuthLogging({
|
|
1189
|
+
logger: config.logger,
|
|
1190
|
+
logLevel: config.logLevel
|
|
1191
|
+
});
|
|
1192
|
+
const { request, pathname, skipRoutes, loginUrl, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite } = config;
|
|
1193
|
+
const effectiveSameSite = sameSite ?? "lax";
|
|
1194
|
+
const requestUrl = new URL(request.url);
|
|
1195
|
+
const resolvedLoginUrl = new URL(loginUrl, requestUrl);
|
|
1196
|
+
const sameOriginLoginPathname = resolvedLoginUrl.origin === requestUrl.origin ? resolvedLoginUrl.pathname : null;
|
|
1197
|
+
if (sameOriginLoginPathname !== null && isSamePathOrSubpath(pathname, sameOriginLoginPathname)) return { action: "allow" };
|
|
1198
|
+
if (needsSessionVerification(request)) {
|
|
1199
|
+
const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log);
|
|
1200
|
+
if (exchangeResult !== null) return {
|
|
1201
|
+
action: "redirect_oauth",
|
|
1202
|
+
redirectUrl: exchangeResult.redirectUrl,
|
|
1203
|
+
cookies: exchangeResult.cookies
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
1207
|
+
const hasSessionToken = cookieHeader.includes(NEON_AUTH_SESSION_COOKIE_NAME);
|
|
1208
|
+
const hasStaleSessionData = parseCookies(cookieHeader).has(NEON_AUTH_SESSION_DATA_COOKIE_NAME) && !hasSessionToken;
|
|
1209
|
+
let sessionData = {
|
|
1210
|
+
session: null,
|
|
1211
|
+
user: null
|
|
1212
|
+
};
|
|
1213
|
+
let sessionCookies = [];
|
|
1214
|
+
if (hasSessionToken) {
|
|
1215
|
+
const sessionResponse = await handleAuthProxyRequest({
|
|
1216
|
+
request: new Request(request.url, {
|
|
1217
|
+
method: "GET",
|
|
1218
|
+
headers: stripBodyHeaders(request.headers)
|
|
1219
|
+
}),
|
|
1220
|
+
path: "get-session",
|
|
1221
|
+
baseUrl,
|
|
1222
|
+
cookieSecret,
|
|
1223
|
+
sessionDataTtl,
|
|
1224
|
+
domain,
|
|
1225
|
+
sameSite,
|
|
1226
|
+
log
|
|
1227
|
+
});
|
|
1228
|
+
if (sessionResponse.ok) {
|
|
1229
|
+
const data = await sessionResponse.json().catch((error) => {
|
|
1230
|
+
log.debug("[webtwizz-auth] Failed to parse session response JSON", {
|
|
1231
|
+
component: "middleware",
|
|
1232
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1233
|
+
});
|
|
1234
|
+
return null;
|
|
1235
|
+
});
|
|
1236
|
+
if (data) sessionData = data;
|
|
1237
|
+
}
|
|
1238
|
+
sessionCookies = sessionResponse.headers.getSetCookie();
|
|
1239
|
+
}
|
|
1240
|
+
if (checkSessionRequired(pathname, skipRoutes, sameOriginLoginPathname, sessionData).allowed) return {
|
|
1241
|
+
action: "allow",
|
|
1242
|
+
headers: { [NEON_AUTH_HEADER_MIDDLEWARE_NAME]: "true" },
|
|
1243
|
+
cookies: sessionCookies
|
|
1244
|
+
};
|
|
1245
|
+
const cookies2 = [];
|
|
1246
|
+
if (hasStaleSessionData) cookies2.push(serializeSetCookie({
|
|
1247
|
+
name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
1248
|
+
value: "",
|
|
1249
|
+
path: "/",
|
|
1250
|
+
domain,
|
|
1251
|
+
httpOnly: true,
|
|
1252
|
+
secure: true,
|
|
1253
|
+
sameSite: effectiveSameSite,
|
|
1254
|
+
maxAge: 0
|
|
1255
|
+
}));
|
|
1256
|
+
if (resolvedLoginUrl.origin === requestUrl.origin) {
|
|
1257
|
+
const configuredLoginParamNames = new Set(resolvedLoginUrl.searchParams.keys());
|
|
1258
|
+
for (const [name, value] of requestUrl.searchParams) if (!configuredLoginParamNames.has(name)) resolvedLoginUrl.searchParams.append(name, value);
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
action: "redirect_login",
|
|
1262
|
+
redirectUrl: resolvedLoginUrl,
|
|
1263
|
+
cookies: cookies2.length > 0 ? cookies2 : void 0
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/next-headers.ts
|
|
1268
|
+
import { cookies as nextCookies, headers as nextHeaders } from "next/headers";
|
|
1269
|
+
|
|
1270
|
+
// src/cookies.ts
|
|
1271
|
+
var OURS = "__Secure-webtwizz-auth.";
|
|
1272
|
+
var THEIRS = "__Secure-neon-auth.";
|
|
1273
|
+
var toTheirs = (name) => name.startsWith(OURS) ? THEIRS + name.slice(OURS.length) : name;
|
|
1274
|
+
var toOurs = (name) => name.startsWith(THEIRS) ? OURS + name.slice(THEIRS.length) : name;
|
|
1275
|
+
function cookieHeaderToTheirs(header) {
|
|
1276
|
+
if (!header || !header.includes(OURS)) return header;
|
|
1277
|
+
return header.split(/;\s*/).map((pair) => {
|
|
1278
|
+
const at = pair.indexOf("=");
|
|
1279
|
+
return at < 0 ? pair : toTheirs(pair.slice(0, at)) + pair.slice(at);
|
|
1280
|
+
}).join("; ");
|
|
1281
|
+
}
|
|
1282
|
+
function setCookieToOurs(value) {
|
|
1283
|
+
const at = value.indexOf("=");
|
|
1284
|
+
return at < 0 ? value : toOurs(value.slice(0, at)) + value.slice(at);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/next-headers.ts
|
|
1288
|
+
function delegate(target, overrides) {
|
|
1289
|
+
return new Proxy(target, {
|
|
1290
|
+
get(store, key) {
|
|
1291
|
+
if (typeof key === "string" && key in overrides) return overrides[key];
|
|
1292
|
+
const value = Reflect.get(store, key, store);
|
|
1293
|
+
return typeof value === "function" ? value.bind(store) : value;
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
async function headers() {
|
|
1298
|
+
const store = await nextHeaders();
|
|
1299
|
+
return delegate(store, {
|
|
1300
|
+
get: (name) => name.toLowerCase() === "cookie" ? cookieHeaderToTheirs(store.get(name)) : store.get(name)
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
async function cookies() {
|
|
1304
|
+
const store = await nextCookies();
|
|
1305
|
+
const rename = (args) => {
|
|
1306
|
+
const [first, ...rest] = args;
|
|
1307
|
+
if (typeof first === "string") return [toOurs(first), ...rest];
|
|
1308
|
+
if (first && typeof first === "object" && "name" in first) return [{ ...first, name: toOurs(String(first.name)) }, ...rest];
|
|
1309
|
+
return args;
|
|
1310
|
+
};
|
|
1311
|
+
return delegate(store, {
|
|
1312
|
+
set: (...args) => store.set(...rename(args)),
|
|
1313
|
+
delete: (...args) => store.delete(...rename(args))
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// node_modules/@neondatabase/auth/dist/next/server/index.mjs
|
|
1318
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1319
|
+
async function createNextRequestContext() {
|
|
1320
|
+
const cookieStore = await cookies();
|
|
1321
|
+
const headerStore = await headers();
|
|
1322
|
+
return {
|
|
1323
|
+
getCookies() {
|
|
1324
|
+
return extractNeonAuthCookies(headerStore);
|
|
1325
|
+
},
|
|
1326
|
+
setCookie(name, value, options) {
|
|
1327
|
+
cookieStore.set(name, value, options);
|
|
1328
|
+
},
|
|
1329
|
+
getHeader(name) {
|
|
1330
|
+
return headerStore.get(name) ?? null;
|
|
1331
|
+
},
|
|
1332
|
+
getOrigin() {
|
|
1333
|
+
return headerStore.get("origin") || headerStore.get("referer")?.split("/").slice(0, 3).join("/") || "";
|
|
1334
|
+
},
|
|
1335
|
+
getFramework() {
|
|
1336
|
+
return "nextjs";
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function authApiHandler(config) {
|
|
1341
|
+
const { baseUrl, cookies: cookies$1 } = config;
|
|
1342
|
+
validateCookieConfig(cookies$1);
|
|
1343
|
+
const log = resolveNeonAuthLogging(config);
|
|
1344
|
+
const handler = async (request, { params }) => {
|
|
1345
|
+
return handleAuthProxyRequest({
|
|
1346
|
+
request,
|
|
1347
|
+
path: (await params).path.join("/"),
|
|
1348
|
+
baseUrl,
|
|
1349
|
+
cookieSecret: cookies$1.secret,
|
|
1350
|
+
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1351
|
+
domain: cookies$1.domain,
|
|
1352
|
+
sameSite: cookies$1.sameSite,
|
|
1353
|
+
log
|
|
1354
|
+
});
|
|
1355
|
+
};
|
|
1356
|
+
return {
|
|
1357
|
+
GET: handler,
|
|
1358
|
+
POST: handler,
|
|
1359
|
+
PUT: handler,
|
|
1360
|
+
DELETE: handler,
|
|
1361
|
+
PATCH: handler
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
var SKIP_ROUTES = DEFAULT_AUTH_SKIP_ROUTES;
|
|
1365
|
+
function neonAuthMiddleware(config) {
|
|
1366
|
+
const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in", log, logger, logLevel } = config;
|
|
1367
|
+
validateCookieConfig(cookies$1);
|
|
1368
|
+
return async (request) => {
|
|
1369
|
+
const pathname = request.nextUrl.pathname;
|
|
1370
|
+
const result = await processAuthMiddleware({
|
|
1371
|
+
request,
|
|
1372
|
+
pathname,
|
|
1373
|
+
skipRoutes: SKIP_ROUTES,
|
|
1374
|
+
loginUrl,
|
|
1375
|
+
baseUrl,
|
|
1376
|
+
cookieSecret: cookies$1.secret,
|
|
1377
|
+
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1378
|
+
domain: cookies$1.domain,
|
|
1379
|
+
sameSite: cookies$1.sameSite,
|
|
1380
|
+
log,
|
|
1381
|
+
logger,
|
|
1382
|
+
logLevel
|
|
1383
|
+
});
|
|
1384
|
+
switch (result.action) {
|
|
1385
|
+
case "allow": {
|
|
1386
|
+
const headers$1 = new Headers(request.headers);
|
|
1387
|
+
if (result.headers) for (const [key, value] of Object.entries(result.headers)) headers$1.set(key, value);
|
|
1388
|
+
const response = NextResponse.next({ request: { headers: headers$1 } });
|
|
1389
|
+
if (result.cookies) for (const cookie of result.cookies) response.headers.append("Set-Cookie", cookie);
|
|
1390
|
+
return response;
|
|
1391
|
+
}
|
|
1392
|
+
case "redirect_oauth": {
|
|
1393
|
+
const oauthHeaders = new Headers();
|
|
1394
|
+
for (const cookie of result.cookies) oauthHeaders.append("Set-Cookie", cookie);
|
|
1395
|
+
return NextResponse.redirect(result.redirectUrl, { headers: oauthHeaders });
|
|
1396
|
+
}
|
|
1397
|
+
case "redirect_login":
|
|
1398
|
+
if (result.cookies && result.cookies.length > 0) {
|
|
1399
|
+
const loginHeaders = new Headers();
|
|
1400
|
+
for (const cookie of result.cookies) loginHeaders.append("Set-Cookie", cookie);
|
|
1401
|
+
return NextResponse.redirect(result.redirectUrl, { headers: loginHeaders });
|
|
1402
|
+
}
|
|
1403
|
+
return NextResponse.redirect(result.redirectUrl);
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
function createNeonAuth(config) {
|
|
1408
|
+
const { baseUrl, cookies: cookies$1 } = config;
|
|
1409
|
+
validateCookieConfig(cookies$1);
|
|
1410
|
+
const log = resolveNeonAuthLogging(config);
|
|
1411
|
+
const server = createAuthServer({
|
|
1412
|
+
baseUrl,
|
|
1413
|
+
context: createNextRequestContext,
|
|
1414
|
+
cookieSecret: cookies$1.secret,
|
|
1415
|
+
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1416
|
+
domain: cookies$1.domain,
|
|
1417
|
+
sameSite: cookies$1.sameSite,
|
|
1418
|
+
log
|
|
1419
|
+
});
|
|
1420
|
+
server.handler = () => authApiHandler(config);
|
|
1421
|
+
server.middleware = (middlewareConfig) => neonAuthMiddleware({
|
|
1422
|
+
...config,
|
|
1423
|
+
...middlewareConfig,
|
|
1424
|
+
log
|
|
1425
|
+
});
|
|
1426
|
+
return server;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/server.ts
|
|
1430
|
+
import { NextRequest as NextRequest2 } from "next/server";
|
|
1431
|
+
var PROVIDER_HEADER = /^x-neon-/i;
|
|
1432
|
+
function forLibrary(request, make) {
|
|
1433
|
+
const cookie = request.headers.get("cookie");
|
|
1434
|
+
const translated = cookieHeaderToTheirs(cookie);
|
|
1435
|
+
if (translated === cookie) return request;
|
|
1436
|
+
const headers2 = new Headers(request.headers);
|
|
1437
|
+
headers2.set("cookie", translated);
|
|
1438
|
+
return make(request, headers2);
|
|
1439
|
+
}
|
|
1440
|
+
function forBrowser(response) {
|
|
1441
|
+
const provider = [...response.headers.keys()].filter((name) => PROVIDER_HEADER.test(name));
|
|
1442
|
+
const cookies2 = response.headers.getSetCookie();
|
|
1443
|
+
const rename = cookies2.some((cookie) => cookie.startsWith(THEIRS));
|
|
1444
|
+
if (!provider.length && !rename) return response;
|
|
1445
|
+
const apply = (headers2) => {
|
|
1446
|
+
for (const name of provider) headers2.delete(name);
|
|
1447
|
+
if (rename) {
|
|
1448
|
+
headers2.delete("set-cookie");
|
|
1449
|
+
for (const cookie of cookies2) headers2.append("set-cookie", setCookieToOurs(cookie));
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
try {
|
|
1453
|
+
apply(response.headers);
|
|
1454
|
+
return response;
|
|
1455
|
+
} catch {
|
|
1456
|
+
const headers2 = new Headers(response.headers);
|
|
1457
|
+
apply(headers2);
|
|
1458
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers: headers2 });
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
async function sendSignUpCode(baseUrl, request, response) {
|
|
1462
|
+
const data = await response.clone().json().catch(() => null);
|
|
1463
|
+
const email = data?.user?.email;
|
|
1464
|
+
if (!email || data.user.emailVerified !== false || data.token) return;
|
|
1465
|
+
await fetch(`${baseUrl.replace(/\/+$/, "")}/email-otp/send-verification-otp`, {
|
|
1466
|
+
method: "POST",
|
|
1467
|
+
headers: { "content-type": "application/json", origin: request.headers.get("origin") || new URL(request.url).origin },
|
|
1468
|
+
body: JSON.stringify({ email, type: "email-verification" })
|
|
1469
|
+
}).catch((error) => console.error("[webtwizz-auth] Could not send the sign-up code", error));
|
|
1470
|
+
}
|
|
8
1471
|
function createAuth(options = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
1472
|
+
const baseUrl = options.url ?? env("WEBTWIZZ_AUTH_URL");
|
|
1473
|
+
const auth = createNeonAuth({
|
|
1474
|
+
baseUrl,
|
|
11
1475
|
cookies: {
|
|
12
1476
|
secret: options.secret ?? env("WEBTWIZZ_AUTH_SECRET"),
|
|
13
1477
|
sessionDataTtl: options.sessionDataTtl
|
|
14
1478
|
}
|
|
15
1479
|
});
|
|
1480
|
+
const handler = auth.handler.bind(auth);
|
|
1481
|
+
const middleware = auth.middleware.bind(auth);
|
|
1482
|
+
auth.handler = () => {
|
|
1483
|
+
const methods = handler();
|
|
1484
|
+
const wrap = (method) => async (request, context) => forBrowser(await method(forLibrary(request, (original, headers2) => new Request(original, { headers: headers2 })), context));
|
|
1485
|
+
const post = wrap(methods.POST);
|
|
1486
|
+
const signUp = async (request, context) => {
|
|
1487
|
+
const response = await post(request, context);
|
|
1488
|
+
const { path } = await context.params;
|
|
1489
|
+
if (response.ok && path?.join("/") === "sign-up/email") await sendSignUpCode(baseUrl, request, response);
|
|
1490
|
+
return response;
|
|
1491
|
+
};
|
|
1492
|
+
return { GET: wrap(methods.GET), POST: signUp, PUT: wrap(methods.PUT), DELETE: wrap(methods.DELETE), PATCH: wrap(methods.PATCH) };
|
|
1493
|
+
};
|
|
1494
|
+
auth.middleware = (config) => {
|
|
1495
|
+
const run = middleware(config);
|
|
1496
|
+
return async (request) => forBrowser(await run(forLibrary(request, (original, headers2) => new NextRequest2(original, { headers: headers2 }))));
|
|
1497
|
+
};
|
|
1498
|
+
return auth;
|
|
16
1499
|
}
|
|
17
1500
|
export {
|
|
18
|
-
createAuth
|
|
1501
|
+
createAuth,
|
|
1502
|
+
forBrowser
|
|
19
1503
|
};
|