@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/AI_GUIDE.md +63 -5
- package/CHANGELOG.md +26 -0
- package/README.md +4 -2
- package/dist/chunk-4PKQUQZD.js +6 -0
- package/dist/{chunk-G54JBUO3.js → chunk-WX3OU7ED.js} +17 -4
- package/dist/client.d.ts +13 -4
- package/dist/client.js +68 -770
- package/dist/db.js +1 -1
- package/dist/files.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +29 -5
- package/dist/server.js +494 -1428
- package/package.json +7 -10
- package/dist/chunk-7EXXDRMF.js +0 -376
package/dist/server.js
CHANGED
|
@@ -1,1503 +1,569 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
346
|
-
|
|
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
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
444
|
-
} catch
|
|
445
|
-
|
|
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
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
|
487
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
let
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
521
|
-
|
|
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
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
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
|
-
|
|
605
|
-
|
|
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
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
|
896
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
-
|
|
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
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
|
1084
|
-
|
|
1085
|
-
if (
|
|
1086
|
-
return
|
|
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
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
-
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
request,
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
-
|
|
1453
|
-
|
|
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
|
};
|