@webtwizz/cloud 0.4.0 → 0.5.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/dist/server.js CHANGED
@@ -1,1503 +1,569 @@
1
1
  import {
2
- NEON_AUTH_SESSION_VERIFIER_PARAM_NAME,
3
- normalizeBetterAuthError
4
- } from "./chunk-7EXXDRMF.js";
2
+ FRAME_HEADER
3
+ } from "./chunk-4PKQUQZD.js";
5
4
  import {
6
5
  env
7
6
  } from "./chunk-ESAV545E.js";
8
7
 
9
8
  // src/server.ts
10
9
  import "server-only";
10
+ import { NextResponse } from "next/server";
11
+ import { cache } from "react";
11
12
 
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;
13
+ // src/cookies.ts
14
+ var OURS = "__Host-webtwizz-auth.";
15
+ var LOCAL = "webtwizz-auth.";
16
+ var THEIRS = "__Secure-neon-auth.";
17
+ var SESSION = "session_token";
18
+ var CACHE = "local.session_data";
19
+ var KEEP = 400 * 24 * 60 * 60;
20
+ var LOCALHOST = /^(localhost|.+\.localhost|127\.0\.0\.1|::1)$/;
21
+ function siteOrigin(url, headers) {
22
+ const origin = headers.get("origin");
23
+ if (origin && origin !== "null") return origin;
24
+ const first = (value) => value?.split(",")[0].trim() || null;
25
+ const fallback = new URL(url);
26
+ const proto = first(headers.get("x-forwarded-proto")) ?? fallback.protocol.replace(":", "");
27
+ const host = first(headers.get("x-forwarded-host")) ?? first(headers.get("host")) ?? fallback.host;
28
+ return `${proto}://${host}`;
29
+ }
30
+ function cookieMode(url, headers) {
31
+ let local = false;
32
+ try {
33
+ const origin = new URL(siteOrigin(url, headers));
34
+ local = origin.protocol === "http:" && LOCALHOST.test(origin.hostname.replace(/^\[|\]$/g, ""));
35
+ } catch {
344
36
  }
345
- if (meta && Object.keys(meta).length > 0) console[level](message, meta);
346
- else console[level](message);
37
+ const framed = !local && (headers.get("sec-fetch-dest") === "iframe" || headers.get(FRAME_HEADER) === "1");
38
+ return { local, framed };
39
+ }
40
+ function pairs(header) {
41
+ if (!header) return [];
42
+ return header.split(";").flatMap((part) => {
43
+ const at = part.indexOf("=");
44
+ return at < 0 ? [] : [[part.slice(0, at).trim(), part.slice(at + 1).trim()]];
45
+ });
46
+ }
47
+ function readCookies(header, mode) {
48
+ const prefix = mode.local ? LOCAL : OURS;
49
+ const found = /* @__PURE__ */ new Map();
50
+ for (const [name, value] of pairs(header)) if (name.startsWith(prefix) && name.length > prefix.length) found.set(name.slice(prefix.length), value);
51
+ return found;
347
52
  }
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;
53
+ function upstreamCookies(ours) {
54
+ return [...ours].filter(([name]) => !name.startsWith("local.")).map(([name, value]) => `${THEIRS}${name}=${value}`).join("; ");
352
55
  }
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);
56
+ function browserCookie(name, value, mode, maxAge) {
356
57
  return {
357
- value: await signPayload(sessionData, expiresAt, secret),
358
- expiresAt: new Date(expiresAt)
58
+ name: (mode.local ? LOCAL : OURS) + name,
59
+ value,
60
+ path: "/",
61
+ maxAge,
62
+ httpOnly: true,
63
+ secure: !mode.local,
64
+ sameSite: mode.framed ? "none" : "lax",
65
+ partitioned: mode.framed
359
66
  };
360
67
  }
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
- };
68
+ var removal = (name, mode) => browserCookie(name, "", mode, 0);
69
+ function fromUpstream(setCookie, mode) {
70
+ const [pair, ...attributes] = setCookie.split(";");
71
+ const at = pair.indexOf("=");
72
+ const name = pair.slice(0, at).trim();
73
+ if (at < 0 || !name.startsWith(THEIRS)) return null;
74
+ const short = name.slice(THEIRS.length);
75
+ const value = pair.slice(at + 1).trim();
76
+ let maxAge;
77
+ for (const attribute of attributes) {
78
+ const [key, ...rest] = attribute.trim().split("=");
79
+ const lower = key.toLowerCase();
80
+ if (lower === "max-age") maxAge = Number(rest.join("="));
81
+ else if (lower === "expires" && maxAge === void 0) {
82
+ const at2 = Date.parse(rest.join("="));
83
+ if (!Number.isNaN(at2)) maxAge = Math.max(0, Math.floor((at2 - Date.now()) / 1e3));
84
+ }
403
85
  }
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;
86
+ if (Number.isNaN(maxAge)) maxAge = void 0;
87
+ if (short === SESSION && maxAge && maxAge > 0) maxAge = KEEP;
88
+ return browserCookie(short, maxAge === 0 ? "" : value, mode, maxAge);
89
+ }
90
+ function serialize(cookie) {
91
+ const parts = [`${cookie.name}=${cookie.value}`, `Path=${cookie.path}`];
92
+ if (cookie.maxAge !== void 0) parts.push(`Max-Age=${cookie.maxAge}`);
93
+ if (cookie.httpOnly) parts.push("HttpOnly");
94
+ if (cookie.secure) parts.push("Secure");
95
+ parts.push(cookie.sameSite === "none" ? "SameSite=None" : "SameSite=Lax");
96
+ if (cookie.partitioned) parts.push("Partitioned");
97
+ return parts.join("; ");
98
+ }
99
+ function applyToHeader(header, cookies) {
100
+ const jar = new Map(pairs(header));
101
+ for (const cookie of cookies) {
102
+ if (cookie.maxAge === 0) jar.delete(cookie.name);
103
+ else jar.set(cookie.name, cookie.value);
425
104
  }
105
+ return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
426
106
  }
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;
107
+
108
+ // src/next-headers.ts
109
+ import { cookies as nextCookies, headers as nextHeaders } from "next/headers";
110
+ var decode = (value) => {
442
111
  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)}`);
112
+ return decodeURIComponent(value);
113
+ } catch {
114
+ return value;
446
115
  }
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
116
  };
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
- };
117
+ async function requestScope() {
118
+ const [headers, jar] = await Promise.all([nextHeaders(), nextCookies()]);
119
+ return {
120
+ headers: new Headers(headers),
121
+ cookieHeader: jar.getAll().map(({ name, value }) => `${name}=${encodeURIComponent(value)}`).join("; "),
122
+ setCookie(cookie) {
123
+ try {
124
+ jar.set({ ...cookie, value: decode(cookie.value) });
125
+ } catch {
126
+ }
127
+ }
128
+ };
129
+ }
130
+
131
+ // src/session.ts
132
+ var VERIFIER = "neon_auth_session_verifier";
133
+ var PRIVATE = ["token", "ipAddress", "userAgent"];
134
+ var MAX_CACHE = 3800;
135
+ var STALE = 60 * 60;
136
+ var TIMEOUT = 1e4;
137
+ var unreachable = { message: "Sign-in isn't working right now.", status: 502, statusText: "Bad Gateway", code: "NETWORK_ERROR" };
138
+ function visitorHeaders(incoming) {
139
+ const out = {};
140
+ for (const name of ["x-forwarded-for", "x-real-ip", "user-agent"]) {
141
+ const value = incoming.get(name);
142
+ if (value) out[name] = value;
467
143
  }
144
+ return out;
468
145
  }
469
- async function mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig, log) {
146
+ async function upstream(config, path, call) {
147
+ const headers = { ...call.visitor, origin: call.origin, accept: "application/json" };
148
+ if (call.cookie) headers.cookie = call.cookie;
149
+ if (call.contentType) headers["content-type"] = call.contentType;
470
150
  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
151
+ return await fetch(`${config.baseUrl.replace(/\/+$/, "")}/${path}${call.search ?? ""}`, {
152
+ method: call.method ?? "GET",
153
+ headers,
154
+ body: call.body,
155
+ redirect: "manual",
156
+ signal: AbortSignal.timeout(TIMEOUT)
484
157
  });
485
158
  } 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);
159
+ const reason = error;
160
+ console.error(`[webtwizz-auth] The sign-in service could not be reached (${reason?.cause?.code ?? reason?.name ?? "unknown"})`);
492
161
  return null;
493
162
  }
494
163
  }
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);
164
+ var encoder = new TextEncoder();
165
+ function base64url(bytes) {
166
+ let text = "";
167
+ for (const byte of new Uint8Array(bytes)) text += String.fromCharCode(byte);
168
+ return btoa(text).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
519
169
  }
520
- async function mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfig, log) {
521
- return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig, log);
170
+ function fromBase64url(value) {
171
+ const text = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
172
+ return new TextDecoder().decode(Uint8Array.from(text, (char) => char.charCodeAt(0)));
522
173
  }
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
- };
174
+ var hmacKey = (secret) => crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
175
+ async function hmac(secret, data) {
176
+ return base64url(await crypto.subtle.sign("HMAC", await hmacKey(secret), encoder.encode(data)));
560
177
  }
561
- var noopResolved = {
562
- error: () => {
563
- },
564
- warn: () => {
565
- },
566
- info: () => {
567
- },
568
- debug: () => {
178
+ async function signedBy(secret, data, signature) {
179
+ try {
180
+ const bytes = Uint8Array.from(atob(signature.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0));
181
+ return await crypto.subtle.verify("HMAC", await hmacKey(secret), bytes, encoder.encode(data));
182
+ } catch {
183
+ return false;
569
184
  }
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
185
  }
604
- function isLegacyFetchTypeError(node) {
605
- return node instanceof TypeError && typeof node.message === "string" && node.message.includes("fetch");
186
+ var tokenId = async (token) => base64url((await crypto.subtle.digest("SHA-256", encoder.encode(token))).slice(0, 16));
187
+ function trim(data) {
188
+ const session = { ...data.session };
189
+ for (const key of PRIVATE) delete session[key];
190
+ return { session, user: data.user };
606
191
  }
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);
192
+ async function readCache(value, token, secret) {
193
+ if (!value) return null;
194
+ const [body, signature] = value.split(".");
195
+ if (!body || !signature || !await signedBy(secret, body, signature)) return null;
196
+ try {
197
+ const { d, s, f } = JSON.parse(fromBase64url(body));
198
+ if (s !== await tokenId(token) || !d?.session || !d?.user) return null;
199
+ return { data: d, fresh: Date.now() / 1e3 < f };
200
+ } catch {
201
+ return null;
619
202
  }
620
- add(error);
621
- return list;
622
203
  }
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";
204
+ async function cacheCookie(data, token, config, mode) {
205
+ const now = Math.floor(Date.now() / 1e3);
206
+ const ends = Math.floor(Date.parse(String(data.session.expiresAt)) / 1e3) || now + STALE;
207
+ const body = base64url(encoder.encode(JSON.stringify({ d: data, s: await tokenId(token), f: Math.min(now + config.ttl, ends) })));
208
+ const cookie = browserCookie(CACHE, `${body}.${await hmac(config.secret, body)}`, mode, Math.max(0, Math.min(STALE, ends - now)));
209
+ return cookie.name.length + cookie.value.length > MAX_CACHE ? removal(CACHE, mode) : cookie;
210
+ }
211
+ async function resolveSession(config, lookup) {
212
+ const ours = readCookies(lookup.cookieHeader, lookup.mode);
213
+ const token = ours.get(SESSION);
214
+ const exchanging = lookup.search?.has(VERIFIER) ?? false;
215
+ if (!token && !exchanging) return { data: null, cookies: ours.has(CACHE) ? [removal(CACHE, lookup.mode)] : [], error: null };
216
+ const cached = token ? await readCache(ours.get(CACHE), token, config.secret) : null;
217
+ if (cached?.fresh && !lookup.fresh) return { data: cached.data, cookies: [], error: null };
218
+ const search = lookup.search?.size ? `?${lookup.search}` : "";
219
+ const response = await upstream(config, "get-session", { search, cookie: upstreamCookies(ours), visitor: visitorHeaders(lookup.headers), origin: lookup.origin });
220
+ if (!response || response.status >= 500 || response.status === 429) {
221
+ if (cached) return { data: cached.data, cookies: [], error: null };
222
+ return { data: null, cookies: [], error: response ? { ...unreachable, status: response.status, statusText: response.statusText } : unreachable };
639
223
  }
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
- };
224
+ const cookies = response.headers.getSetCookie().map((cookie) => fromUpstream(cookie, lookup.mode)).filter((cookie) => cookie !== null);
225
+ const body = await response.json().catch(() => null);
226
+ if (!response.ok) {
227
+ return { data: null, cookies, error: { message: body?.message ?? body?.error ?? response.statusText, status: response.status, statusText: response.statusText, code: body?.code ?? "UNKNOWN" } };
683
228
  }
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
- });
229
+ if (!body?.session || !body?.user) {
230
+ if (token && !cookies.some((cookie) => cookie.name.endsWith(SESSION))) cookies.push(removal(SESSION, lookup.mode));
231
+ cookies.push(removal(CACHE, lookup.mode));
232
+ return { data: null, cookies, error: null };
233
+ }
234
+ const renewed = cookies.find((cookie) => cookie.name.endsWith(SESSION) && cookie.value)?.value;
235
+ const data = trim(body);
236
+ cookies.push(await cacheCookie(data, renewed ?? token ?? "", config, lookup.mode));
237
+ return { data, cookies, error: null };
888
238
  }
889
- var PROXY_HEADERS = [
890
- "user-agent",
891
- "authorization",
892
- "referer",
893
- "content-type"
239
+
240
+ // src/server.ts
241
+ var RETURN = "return";
242
+ var NOT_FOUND = { code: "NOT_FOUND", message: "Not found" };
243
+ var NO_PROVIDER = { code: "PROVIDER_NOT_FOUND", message: "Provider not found" };
244
+ var HIDDEN = [/^reference$/, /^open-api(\/|$)/, /^error$/, /^token(\/|$)/, /^jwks$/, /^\.well-known\//, /^oauth2\//, /^callback\//];
245
+ var SERVER_ONLY = [
246
+ /^admin\//,
247
+ /^organization\//,
248
+ /^magic-link\//,
249
+ /^sign-in\/(email-otp|magic-link|anonymous|phone-number)$/,
250
+ /^request-password-reset$/,
251
+ /^forget-password$/,
252
+ /^reset-password\/./,
253
+ /^send-verification-email$/
894
254
  ];
895
- var NEON_AUTH_HEADER_MIDDLEWARE_NAME = "x-neon-auth-middleware";
896
- function safeAuthHost(baseUrl) {
255
+ var CODE_TYPES = /* @__PURE__ */ new Set(["email-verification", "forget-password"]);
256
+ var SOCIAL = /* @__PURE__ */ new Set(["sign-in/social", "link-social"]);
257
+ var GETS = /* @__PURE__ */ new Set([
258
+ "get-session",
259
+ "list-sessions",
260
+ "list-accounts",
261
+ "account-info",
262
+ "verify-email",
263
+ "admin/get-user",
264
+ "admin/list-users",
265
+ "organization/get-organization",
266
+ "organization/get-full-organization",
267
+ "organization/list",
268
+ "organization/get-invitation",
269
+ "organization/list-invitations",
270
+ "organization/get-active-member",
271
+ "organization/list-user-invitations",
272
+ "organization/list-members",
273
+ "organization/get-active-member-role"
274
+ ]);
275
+ var PASSED = ["content-type", "cache-control", "location", "retry-after", "x-retry-after"];
276
+ var EMPTY = /* @__PURE__ */ new Set([101, 204, 205, 304]);
277
+ var parse = (text) => {
897
278
  try {
898
- return new URL(baseUrl).host;
279
+ const value = text ? JSON.parse(text) : null;
280
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
899
281
  } 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);
282
+ return null;
1028
283
  }
1029
- return headers2;
1030
284
  };
1031
- function emit(log, level, message, meta) {
1032
- if (log) {
1033
- log[level](message, meta);
1034
- return;
285
+ var kebab = (key) => key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
286
+ function onSite(target, origin, fallback = `${origin}/`) {
287
+ if (!target) return fallback;
288
+ try {
289
+ const url = new URL(target, origin);
290
+ return url.origin === origin ? url.toString() : fallback;
291
+ } catch {
292
+ return fallback;
1035
293
  }
1036
- if (meta && Object.keys(meta).length > 0) console[level](message, meta);
1037
- else console[level](message);
1038
294
  }
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
- }
295
+ var within = (pathname, route) => pathname === route || pathname.startsWith(`${route.replace(/\/+$/, "")}/`);
296
+ function withError(target, code) {
297
+ const url = new URL(target);
298
+ url.searchParams.set("error", code.toUpperCase());
299
+ return url.toString();
300
+ }
301
+ function redirect(location, cookies = []) {
302
+ const headers = new Headers({ location, "cache-control": "no-store" });
303
+ for (const cookie of cookies) headers.append("set-cookie", serialize(cookie));
304
+ return new Response(null, { status: 302, headers });
305
+ }
306
+ function relay(url) {
307
+ const back = new URL(url);
308
+ back.searchParams.delete("popup");
309
+ const target = JSON.stringify(back.pathname + back.search).replace(/</g, "\\u003c");
310
+ const html = `<!doctype html><meta charset="utf-8"><title>Signing in</title><p>You can close this window.</p><script>try{window.opener&&window.opener.postMessage({type:"webtwizz-auth:return",url:${target}},location.origin)}finally{window.close()}</script>`;
311
+ return new Response(html, { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "referrer-policy": "no-referrer" } });
312
+ }
313
+ var dated = (value) => typeof value === "string" ? new Date(value) : value;
314
+ function withDates(data) {
315
+ return {
316
+ session: { ...data.session, expiresAt: dated(data.session.expiresAt), createdAt: dated(data.session.createdAt), updatedAt: dated(data.session.updatedAt) },
317
+ user: { ...data.user, createdAt: dated(data.user.createdAt), updatedAt: dated(data.user.updatedAt) }
1059
318
  };
1060
- if (!hasSessionData) return await mintAndReturn();
319
+ }
320
+ var google = null;
321
+ var warned = false;
322
+ function googleOn() {
323
+ if (!google || Date.now() - google.at > 6e4) google = { at: Date.now(), on: readGoogle() };
324
+ return google.on;
325
+ }
326
+ async function readGoogle() {
327
+ const allow = (reason) => {
328
+ if (!warned) console.error(`[webtwizz-auth] Could not read whether Google sign-in is on (${reason}), so it is allowed`);
329
+ warned = true;
330
+ return true;
331
+ };
332
+ const url = process.env.DATABASE_URL;
333
+ if (!url) return allow("DATABASE_URL is not set");
334
+ let sql;
1061
335
  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();
336
+ const { default: postgres } = await import("postgres");
337
+ sql = postgres(url, { max: 1, prepare: false, connect_timeout: 5, idle_timeout: 1, onnotice: () => {
338
+ } });
339
+ const [row] = await sql`select social_providers from neon_auth.project_config limit 1`;
340
+ const providers = row?.social_providers;
341
+ if (!Array.isArray(providers)) return allow("unexpected setting");
342
+ const entry = providers.find((provider) => provider?.id === "google");
343
+ if (!entry) return false;
344
+ if (typeof entry.isShared !== "boolean") return allow("unexpected setting");
345
+ return !entry.isShared;
1065
346
  } 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
347
+ return allow(error?.code ?? "query failed");
348
+ } finally {
349
+ sql?.end({ timeout: 1 }).catch(() => {
1079
350
  });
1080
- return await mintAndReturn();
1081
351
  }
1082
352
  }
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
- }
353
+ async function refuse(path, body, from) {
354
+ if (HIDDEN.some((pattern) => pattern.test(path))) return NOT_FOUND;
355
+ if (from === "browser" && SERVER_ONLY.some((pattern) => pattern.test(path))) return NOT_FOUND;
356
+ if (from === "browser" && path === "email-otp/send-verification-otp" && body && !CODE_TYPES.has(String(body.type))) return NOT_FOUND;
357
+ if (SOCIAL.has(path) && (body?.provider !== "google" || !await googleOn())) return NO_PROVIDER;
1142
358
  return null;
1143
359
  }
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
360
+ function socialReturn(body, origin, mount, popup, referer) {
361
+ const start = onSite(referer, origin);
362
+ const back = (target, fallback) => {
363
+ const url = new URL(`${origin}${mount}/${RETURN}`);
364
+ url.searchParams.set("to", onSite(typeof target === "string" ? target : null, origin, fallback));
365
+ if (popup) url.searchParams.set("popup", "1");
366
+ return url.toString();
1173
367
  };
1174
368
  return {
1175
- allowed: true,
1176
- session,
1177
- requiresRedirect: false
369
+ ...body,
370
+ callbackURL: back(body.callbackURL, `${origin}/`),
371
+ errorCallbackURL: back(body.errorCallbackURL, start),
372
+ ...body.newUserCallbackURL ? { newUserCallbackURL: back(body.newUserCallbackURL, `${origin}/`) } : {}
1178
373
  };
1179
374
  }
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
- };
375
+ async function forBrowser(response, mode) {
376
+ const headers = new Headers();
377
+ for (const name of PASSED) {
378
+ const value = response.headers.get(name);
379
+ if (value) headers.set(name, value);
1205
380
  }
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
381
+ const cookies = response.headers.getSetCookie().map((cookie) => fromUpstream(cookie, mode)).filter((cookie) => cookie !== null);
382
+ if (cookies.some((cookie) => cookie.name.endsWith(SESSION))) cookies.push(removal(CACHE, mode));
383
+ for (const cookie of cookies) headers.append("set-cookie", serialize(cookie));
384
+ if (EMPTY.has(response.status)) return new Response(null, { status: response.status, headers });
385
+ let body = await response.text();
386
+ if (!response.ok && /neon/i.test(body)) body = JSON.stringify({ code: parse(body)?.code ?? "UNKNOWN", message: "Something went wrong" });
387
+ return new Response(body, { status: response.status, statusText: response.statusText, headers });
388
+ }
389
+ function sessionResponse({ data, cookies, error }) {
390
+ const headers = new Headers({ "cache-control": "no-store" });
391
+ for (const cookie of cookies) headers.append("set-cookie", serialize(cookie));
392
+ if (error && error.status >= 500) return Response.json({ code: error.code, message: error.message }, { status: error.status, headers });
393
+ return Response.json(data, { headers });
394
+ }
395
+ function withCookies(response, cookies) {
396
+ for (const cookie of cookies) response.headers.append("set-cookie", serialize(cookie));
397
+ return response;
398
+ }
399
+ async function sendSignUpCode(config, request, origin, response) {
400
+ const data = parse(await response.clone().text());
401
+ const email = data?.user?.email;
402
+ if (!email || data.user?.emailVerified !== false || data.token) return;
403
+ const sent = await upstream(config, "email-otp/send-verification-otp", {
404
+ method: "POST",
405
+ origin,
406
+ visitor: visitorHeaders(request.headers),
407
+ contentType: "application/json",
408
+ body: JSON.stringify({ email, type: "email-verification" })
409
+ });
410
+ if (!sent?.ok) console.error("[webtwizz-auth] Could not send the sign-up code", sent?.status);
411
+ }
412
+ function createAuth(options = {}) {
413
+ const config = {
414
+ baseUrl: options.url ?? env("WEBTWIZZ_AUTH_URL"),
415
+ secret: options.secret ?? env("WEBTWIZZ_AUTH_SECRET"),
416
+ ttl: options.sessionDataTtl ?? 10
1212
417
  };
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
418
+ if (config.secret.length < 32) throw new Error("WEBTWIZZ_AUTH_SECRET must be at least 32 characters.");
419
+ if (!(config.ttl > 0)) throw new Error("sessionDataTtl must be a positive number of seconds.");
420
+ async function finishSocial(request, url, mode, origin) {
421
+ const to = onSite(url.searchParams.get("to"), origin);
422
+ const failed = url.searchParams.get("error");
423
+ const verifier = url.searchParams.get(VERIFIER);
424
+ if (failed || !verifier) return redirect(withError(to, failed ?? "sign_in_failed"));
425
+ if (url.searchParams.get("popup") === "1") return relay(url);
426
+ const { data, cookies } = await resolveSession(config, {
427
+ cookieHeader: request.headers.get("cookie"),
428
+ headers: request.headers,
429
+ origin,
430
+ mode,
431
+ fresh: true,
432
+ search: new URLSearchParams({ [VERIFIER]: verifier })
1227
433
  });
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();
434
+ return redirect(data ? to : withError(to, "sign_in_failed"), cookies);
1239
435
  }
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;
436
+ const route = async (request, context) => {
437
+ const segments = (await context.params).path ?? [];
438
+ if (!segments.length || segments.some((segment) => !segment || segment === "." || segment === ".." || /[\\/]/.test(segment))) {
439
+ return Response.json(NOT_FOUND, { status: 404 });
1294
440
  }
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";
441
+ const path = segments.join("/").toLowerCase();
442
+ const url = new URL(request.url);
443
+ const mode = cookieMode(request.url, request.headers);
444
+ const origin = siteOrigin(request.url, request.headers);
445
+ if (path === RETURN && request.method === "GET") return finishSocial(request, url, mode, origin);
446
+ const text = request.method === "GET" || request.method === "HEAD" ? void 0 : await request.text();
447
+ const body = parse(text);
448
+ const refusal = await refuse(path, body, "browser");
449
+ if (refusal) return Response.json(refusal, { status: 404 });
450
+ if (path === "get-session" && request.method === "GET") {
451
+ return sessionResponse(await resolveSession(config, {
452
+ cookieHeader: request.headers.get("cookie"),
453
+ headers: request.headers,
454
+ origin,
455
+ mode,
456
+ fresh: url.searchParams.get("disableCookieCache") === "true",
457
+ search: url.searchParams
458
+ }));
1337
459
  }
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
460
+ let sent = text;
461
+ if (SOCIAL.has(path) && body) {
462
+ const mount = url.pathname.split("/").slice(0, -segments.length).join("/");
463
+ sent = JSON.stringify(socialReturn(body, origin, mount, mode.framed, request.headers.get("referer")));
464
+ }
465
+ const response = await upstream(config, segments.map(encodeURIComponent).join("/"), {
466
+ method: request.method,
467
+ search: url.search,
468
+ cookie: upstreamCookies(readCookies(request.headers.get("cookie"), mode)),
469
+ visitor: visitorHeaders(request.headers),
470
+ origin,
471
+ body: sent,
472
+ contentType: request.headers.get("content-type")
1354
473
  });
474
+ if (!response) return Response.json({ code: unreachable.code, message: unreachable.message }, { status: 502 });
475
+ if (path === "sign-up/email" && response.ok) await sendSignUpCode(config, request, origin, response);
476
+ return forBrowser(response, mode);
1355
477
  };
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
478
+ const lookups = cache(() => /* @__PURE__ */ new Map());
479
+ async function getSession(input = {}) {
480
+ const scope = await requestScope();
481
+ const site = `http://${scope.headers.get("host") ?? "localhost"}/`;
482
+ const mode = cookieMode(site, scope.headers);
483
+ const query = input?.query ?? {};
484
+ const fresh = query.disableCookieCache === true || query.disableCookieCache === "true";
485
+ const search = new URLSearchParams(Object.entries(query).filter(([key2, value]) => key2 !== "disableCookieCache" && value !== void 0).map(([key2, value]) => [key2, String(value)]));
486
+ const key = `${fresh}|${search}|${scope.cookieHeader}`;
487
+ const memo = lookups();
488
+ let pending = memo.get(key);
489
+ if (!pending) {
490
+ pending = resolveSession(config, { cookieHeader: scope.cookieHeader, headers: scope.headers, origin: siteOrigin(site, scope.headers), mode, fresh, search });
491
+ memo.set(key, pending);
492
+ for (const cookie of (await pending).cookies) scope.setCookie(cookie);
493
+ }
494
+ const { data, error } = await pending;
495
+ return { data: data ? withDates(data) : null, error: data ? null : error };
496
+ }
497
+ async function call(path, input = {}) {
498
+ const { query, fetchOptions: _fetchOptions, ...rest } = input ?? {};
499
+ const refusal = await refuse(path, rest, "server");
500
+ if (refusal) return { data: null, error: { ...refusal, status: 404, statusText: "Not Found" } };
501
+ const scope = await requestScope();
502
+ const site = `http://${scope.headers.get("host") ?? "localhost"}/`;
503
+ const mode = cookieMode(site, scope.headers);
504
+ const method = GETS.has(path) ? "GET" : "POST";
505
+ const origin = siteOrigin(site, scope.headers);
506
+ const search = query && typeof query === "object" ? `?${new URLSearchParams(Object.entries(query).map(([key, value]) => [key, String(value)]))}` : "";
507
+ const body = SOCIAL.has(path) ? socialReturn(rest, origin, "/api/auth", false, scope.headers.get("referer")) : rest;
508
+ const response = await upstream(config, path, {
509
+ method,
510
+ search,
511
+ cookie: upstreamCookies(readCookies(scope.cookieHeader, mode)),
512
+ visitor: visitorHeaders(scope.headers),
513
+ origin,
514
+ body: method === "POST" ? JSON.stringify(body) : void 0,
515
+ contentType: method === "POST" ? "application/json" : null
1383
516
  });
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);
517
+ if (!response) return { data: null, error: unreachable };
518
+ const cookies = response.headers.getSetCookie().map((cookie) => fromUpstream(cookie, mode)).filter((cookie) => cookie !== null);
519
+ if (cookies.some((cookie) => cookie.name.endsWith(SESSION))) cookies.push(removal(CACHE, mode));
520
+ for (const cookie of cookies) scope.setCookie(cookie);
521
+ const json = parse(await response.text());
522
+ if (!response.ok) {
523
+ const message = String(json?.message ?? json?.error ?? response.statusText);
524
+ return { data: null, error: { message: /neon/i.test(message) ? "Something went wrong" : message, status: response.status, statusText: response.statusText, code: String(json?.code ?? "UNKNOWN") } };
1404
525
  }
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
526
+ return { data: json, error: null };
527
+ }
528
+ const api = (path) => new Proxy(() => {
529
+ }, {
530
+ get: (_, key) => typeof key === "string" && key !== "then" ? api([...path, kebab(key)]) : void 0,
531
+ apply: (_, __, [input]) => call(path.join("/"), input)
1425
532
  });
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));
533
+ const auth = {
534
+ getSession,
535
+ handler: () => ({ GET: route, POST: route, PUT: route, DELETE: route, PATCH: route }),
536
+ middleware: ({ loginUrl = "/auth/sign-in" } = {}) => async (request) => {
537
+ const url = new URL(request.url);
538
+ const mode = cookieMode(request.url, request.headers);
539
+ const origin = siteOrigin(request.url, request.headers);
540
+ const login = new URL(loginUrl, request.url);
541
+ const local = login.origin === url.origin;
542
+ if (local && within(url.pathname, login.pathname) || within(url.pathname, "/api/auth")) return NextResponse.next();
543
+ const lookup = { cookieHeader: request.headers.get("cookie"), headers: request.headers, origin, mode };
544
+ const verifier = url.searchParams.get(VERIFIER);
545
+ if (verifier) {
546
+ const { cookies: cookies2 } = await resolveSession(config, { ...lookup, fresh: true, search: new URLSearchParams({ [VERIFIER]: verifier }) });
547
+ url.searchParams.delete(VERIFIER);
548
+ return withCookies(NextResponse.redirect(url), cookies2);
549
+ }
550
+ const { data, cookies } = await resolveSession(config, lookup);
551
+ if (data) {
552
+ if (!cookies.length) return NextResponse.next();
553
+ const headers = new Headers(request.headers);
554
+ headers.set("cookie", applyToHeader(request.headers.get("cookie"), cookies));
555
+ return withCookies(NextResponse.next({ request: { headers } }), cookies);
556
+ }
557
+ if (local) {
558
+ for (const [name, value] of url.searchParams) if (!login.searchParams.has(name)) login.searchParams.append(name, value);
559
+ }
560
+ return withCookies(NextResponse.redirect(login), cookies);
1450
561
  }
1451
562
  };
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
- }
1471
- function createAuth(options = {}) {
1472
- const baseUrl = options.url ?? env("WEBTWIZZ_AUTH_URL");
1473
- const auth = createNeonAuth({
1474
- baseUrl,
1475
- cookies: {
1476
- secret: options.secret ?? env("WEBTWIZZ_AUTH_SECRET"),
1477
- sessionDataTtl: options.sessionDataTtl
1478
- }
563
+ return new Proxy(auth, {
564
+ get: (target, key) => key in target ? target[key] : typeof key === "string" && key !== "then" ? api([kebab(key)]) : void 0
1479
565
  });
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;
1499
566
  }
1500
567
  export {
1501
- createAuth,
1502
- forBrowser
568
+ createAuth
1503
569
  };