@neondatabase/auth 0.1.0-beta.8 → 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +108 -18
  2. package/dist/{adapter-core-Bw9mn_AS.d.mts → adapter-core-CnrOXh1T.d.mts} +246 -280
  3. package/dist/{adapter-core-C_NEMs0b.mjs → adapter-core-CtmnMMJ7.mjs} +392 -67
  4. package/dist/better-auth-react-adapter-DNi5PC5D.d.mts +2170 -0
  5. package/dist/{better-auth-react-adapter-BbM3jLLv.mjs → better-auth-react-adapter-Dv-o6A6O.mjs} +10 -8
  6. package/dist/{chunk-5DLVHPZS-Bxj7snpZ-DoVNlsyk.mjs → chunk-VCZJYX65-CLnrj1o7-D6ZQkcc_.mjs} +13 -3
  7. package/dist/constants-Cupc_bln.mjs +28 -0
  8. package/dist/index.d.mts +4 -98
  9. package/dist/index.mjs +2 -1
  10. package/dist/neon-auth-BEGCfAe6.d.mts +107 -0
  11. package/dist/{neon-auth-DdlToh7_.mjs → neon-auth-Cs2cWh1B.mjs} +7 -4
  12. package/dist/next/index.d.mts +61 -170
  13. package/dist/next/index.mjs +4 -311
  14. package/dist/next/server/index.d.mts +538 -0
  15. package/dist/next/server/index.mjs +1373 -0
  16. package/dist/react/adapters/index.d.mts +4 -4
  17. package/dist/react/adapters/index.mjs +2 -1
  18. package/dist/react/index.d.mts +5 -5
  19. package/dist/react/index.mjs +4 -3
  20. package/dist/react/ui/index.d.mts +1 -1
  21. package/dist/react/ui/index.mjs +2 -2
  22. package/dist/react/ui/server.mjs +1 -1
  23. package/dist/{supabase-adapter-CAqbpOC7.mjs → supabase-adapter-BlcGPyOf.mjs} +28 -45
  24. package/dist/supabase-adapter-DUqw2fw8.d.mts +2258 -0
  25. package/dist/types/index.d.mts +2 -7
  26. package/dist/ui/.safelist.html +3 -0
  27. package/dist/ui/css.css +2 -2
  28. package/dist/ui/tailwind.css +4 -3
  29. package/dist/ui/theme-inline.css +44 -0
  30. package/dist/ui/theme.css +221 -118
  31. package/dist/{ui-aMoA-9nq.mjs → ui-COLWzDsu.mjs} +6024 -3004
  32. package/dist/vanilla/adapters/index.d.mts +3 -3
  33. package/dist/vanilla/adapters/index.mjs +2 -1
  34. package/dist/vanilla/index.d.mts +3 -3
  35. package/dist/vanilla/index.mjs +2 -1
  36. package/llms.txt +330 -0
  37. package/package.json +17 -10
  38. package/dist/better-auth-react-adapter-JoscqoDc.d.mts +0 -722
  39. package/dist/better-auth-types-CE4hLv9E.d.mts +0 -9
  40. package/dist/supabase-adapter-Clxlqg1x.d.mts +0 -127
  41. /package/dist/{adapters-D0mxG3F-.mjs → adapters-B7YKkjaL.mjs} +0 -0
  42. /package/dist/{adapters-Df6Dd3KK.mjs → adapters-CivF9wql.mjs} +0 -0
  43. /package/dist/{index-ClXLQ1fw.d.mts → index-CPnFzULh.d.mts} +0 -0
  44. /package/dist/{index-BXlAjlSt.d.mts → index-CzsGMS7C.d.mts} +0 -0
  45. /package/dist/{index-DCQ5Y2ED.d.mts → index-OEBbnNdr.d.mts} +0 -0
@@ -0,0 +1,1373 @@
1
+ import { o as NEON_AUTH_SESSION_VERIFIER_PARAM_NAME } from "../../constants-Cupc_bln.mjs";
2
+ import { SignJWT, jwtVerify } from "jose";
3
+ import { parseCookies, parseSetCookieHeader } from "better-auth/cookies";
4
+ import { cookies, headers } from "next/headers";
5
+ import { NextRequest, NextResponse } from "next/server";
6
+
7
+ //#region src/server/request-context.ts
8
+ /**
9
+ * Header name used to identify server-side proxy requests.
10
+ * The value will be the framework name (e.g., 'nextjs', 'remix').
11
+ */
12
+ const NEON_AUTH_SERVER_PROXY_HEADER = "x-neon-auth-proxy";
13
+
14
+ //#endregion
15
+ //#region src/server/endpoints.ts
16
+ const 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
+ },
59
+ signUp: { email: {
60
+ path: "sign-up/email",
61
+ method: "POST"
62
+ } },
63
+ signOut: {
64
+ path: "sign-out",
65
+ method: "POST"
66
+ },
67
+ listAccounts: {
68
+ path: "list-accounts",
69
+ method: "GET"
70
+ },
71
+ accountInfo: {
72
+ path: "account-info",
73
+ method: "GET"
74
+ },
75
+ updateUser: {
76
+ path: "update-user",
77
+ method: "POST"
78
+ },
79
+ deleteUser: {
80
+ path: "delete-user",
81
+ method: "POST"
82
+ },
83
+ changePassword: {
84
+ path: "change-password",
85
+ method: "POST"
86
+ },
87
+ sendVerificationEmail: {
88
+ path: "send-verification-email",
89
+ method: "POST"
90
+ },
91
+ verifyEmail: {
92
+ path: "verify-email",
93
+ method: "POST"
94
+ },
95
+ resetPassword: {
96
+ path: "reset-password",
97
+ method: "POST"
98
+ },
99
+ requestPasswordReset: {
100
+ path: "request-password-reset",
101
+ method: "POST"
102
+ },
103
+ token: {
104
+ path: "token",
105
+ method: "GET"
106
+ },
107
+ jwks: {
108
+ path: "jwt",
109
+ method: "GET"
110
+ },
111
+ getAnonymousToken: {
112
+ path: "token/anonymous",
113
+ method: "GET"
114
+ },
115
+ admin: {
116
+ createUser: {
117
+ path: "admin/create-user",
118
+ method: "POST"
119
+ },
120
+ listUsers: {
121
+ path: "admin/list-users",
122
+ method: "GET"
123
+ },
124
+ setRole: {
125
+ path: "admin/set-role",
126
+ method: "POST"
127
+ },
128
+ setUserPassword: {
129
+ path: "admin/set-user-password",
130
+ method: "POST"
131
+ },
132
+ updateUser: {
133
+ path: "admin/update-user",
134
+ method: "POST"
135
+ },
136
+ banUser: {
137
+ path: "admin/ban-user",
138
+ method: "POST"
139
+ },
140
+ unbanUser: {
141
+ path: "admin/unban-user",
142
+ method: "POST"
143
+ },
144
+ listUserSessions: {
145
+ path: "admin/list-user-sessions",
146
+ method: "GET"
147
+ },
148
+ revokeUserSession: {
149
+ path: "admin/revoke-user-session",
150
+ method: "POST"
151
+ },
152
+ revokeUserSessions: {
153
+ path: "admin/revoke-user-sessions",
154
+ method: "POST"
155
+ },
156
+ impersonateUser: {
157
+ path: "admin/impersonate-user",
158
+ method: "POST"
159
+ },
160
+ stopImpersonating: {
161
+ path: "admin/stop-impersonating",
162
+ method: "POST"
163
+ },
164
+ removeUser: {
165
+ path: "admin/remove-user",
166
+ method: "POST"
167
+ },
168
+ hasPermission: {
169
+ path: "admin/has-permission",
170
+ method: "POST"
171
+ }
172
+ },
173
+ organization: {
174
+ create: {
175
+ path: "organization/create",
176
+ method: "POST"
177
+ },
178
+ update: {
179
+ path: "organization/update",
180
+ method: "POST"
181
+ },
182
+ delete: {
183
+ path: "organization/delete",
184
+ method: "POST"
185
+ },
186
+ list: {
187
+ path: "organization/list",
188
+ method: "GET"
189
+ },
190
+ getFullOrganization: {
191
+ path: "organization/get-full-organization",
192
+ method: "GET"
193
+ },
194
+ setActive: {
195
+ path: "organization/set-active",
196
+ method: "POST"
197
+ },
198
+ checkSlug: {
199
+ path: "organization/check-slug",
200
+ method: "GET"
201
+ },
202
+ listMembers: {
203
+ path: "organization/list-members",
204
+ method: "GET"
205
+ },
206
+ removeMember: {
207
+ path: "organization/remove-member",
208
+ method: "POST"
209
+ },
210
+ updateMemberRole: {
211
+ path: "organization/update-member-role",
212
+ method: "POST"
213
+ },
214
+ leave: {
215
+ path: "organization/leave",
216
+ method: "POST"
217
+ },
218
+ getActiveMember: {
219
+ path: "organization/get-active-member",
220
+ method: "GET"
221
+ },
222
+ getActiveMemberRole: {
223
+ path: "organization/get-active-member-role",
224
+ method: "GET"
225
+ },
226
+ inviteMember: {
227
+ path: "organization/invite-member",
228
+ method: "POST"
229
+ },
230
+ acceptInvitation: {
231
+ path: "organization/accept-invitation",
232
+ method: "POST"
233
+ },
234
+ rejectInvitation: {
235
+ path: "organization/reject-invitation",
236
+ method: "POST"
237
+ },
238
+ cancelInvitation: {
239
+ path: "organization/cancel-invitation",
240
+ method: "POST"
241
+ },
242
+ getInvitation: {
243
+ path: "organization/get-invitation",
244
+ method: "GET"
245
+ },
246
+ listInvitations: {
247
+ path: "organization/list-invitations",
248
+ method: "GET"
249
+ },
250
+ listUserInvitations: {
251
+ path: "organization/list-user-invitations",
252
+ method: "GET"
253
+ },
254
+ hasPermission: {
255
+ path: "organization/has-permission",
256
+ method: "POST"
257
+ }
258
+ },
259
+ emailOtp: {
260
+ sendVerificationOtp: {
261
+ path: "email-otp/send-verification-otp",
262
+ method: "POST"
263
+ },
264
+ verifyEmail: {
265
+ path: "email-otp/verify-email",
266
+ method: "POST"
267
+ },
268
+ checkVerificationOtp: {
269
+ path: "email-otp/check-verification-otp",
270
+ method: "POST"
271
+ },
272
+ resetPassword: {
273
+ path: "email-otp/passcode",
274
+ method: "POST"
275
+ }
276
+ }
277
+ };
278
+
279
+ //#endregion
280
+ //#region src/server/constants.ts
281
+ /** Prefix for all Neon Auth cookies */
282
+ const NEON_AUTH_COOKIE_PREFIX = "__Secure-neon-auth";
283
+ /** Cookie name for cached session data (signed JWT) - used for server-side session caching */
284
+ const NEON_AUTH_SESSION_DATA_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.local.session_data`;
285
+ /** Cookie name for OAuth session challenge - used for OAuth flow security */
286
+ const NEON_AUTH_SESSION_CHALLENGE_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.session_challange`;
287
+ /** Cookie name for session token - the primary authentication cookie */
288
+ const NEON_AUTH_SESSION_COOKIE_NAME = `${NEON_AUTH_COOKIE_PREFIX}.session_token`;
289
+
290
+ //#endregion
291
+ //#region src/server/utils/cookies.ts
292
+ /**
293
+ * Extract the Neon Auth cookies from the request headers.
294
+ * Only returns cookies that start with the NEON_AUTH_COOKIE_PREFIX.
295
+ *
296
+ * @param headers - The request headers or cookie header string.
297
+ * @returns The cookie string with all Neon Auth cookies (e.g., "name=value; name2=value2").
298
+ */
299
+ const extractNeonAuthCookies = (headers$1) => {
300
+ const cookieHeader = typeof headers$1 === "string" ? headers$1 : headers$1.get("cookie");
301
+ if (!cookieHeader) return "";
302
+ const parsedCookies = parseCookies(cookieHeader);
303
+ const result = [];
304
+ for (const [name, value] of parsedCookies.entries()) if (name.startsWith(NEON_AUTH_COOKIE_PREFIX)) result.push(`${name}=${value}`);
305
+ return result.join("; ");
306
+ };
307
+ /**
308
+ * Parses the `set-cookie` header from Neon Auth response into a list of cookies.
309
+ *
310
+ * @param setCookieHeader - The `set-cookie` header from Neon Auth response.
311
+ * @returns The list of parsed cookies with their options.
312
+ */
313
+ const parseSetCookies = (setCookieHeader) => {
314
+ const parsedCookies = parseSetCookieHeader(setCookieHeader);
315
+ const cookies$1 = [];
316
+ for (const entry of parsedCookies.entries()) {
317
+ const [name, parsedCookie] = entry;
318
+ cookies$1.push({
319
+ name,
320
+ value: decodeURIComponent(parsedCookie.value),
321
+ path: parsedCookie.path,
322
+ domain: parsedCookie.domain,
323
+ maxAge: parsedCookie["max-age"] ?? parsedCookie.maxAge,
324
+ httpOnly: parsedCookie.httponly ?? true,
325
+ secure: parsedCookie.secure ?? true,
326
+ sameSite: parsedCookie.samesite ?? "lax",
327
+ partitioned: parsedCookie.partitioned
328
+ });
329
+ }
330
+ return cookies$1;
331
+ };
332
+ /**
333
+ * Serializes a parsed cookie object back into a Set-Cookie header string
334
+ *
335
+ * @param cookie - The parsed cookie object
336
+ * @returns The Set-Cookie header string
337
+ */
338
+ const serializeSetCookie = (cookie) => {
339
+ let result = `${cookie.name}=${encodeURIComponent(cookie.value)}`;
340
+ if (cookie.path) result += `; Path=${cookie.path}`;
341
+ if (cookie.domain) result += `; Domain=${cookie.domain}`;
342
+ if (cookie.maxAge !== void 0) result += `; Max-Age=${cookie.maxAge}`;
343
+ if (cookie.expires) result += `; Expires=${cookie.expires.toUTCString()}`;
344
+ if (cookie.httpOnly) result += "; HttpOnly";
345
+ if (cookie.secure) result += "; Secure";
346
+ if (cookie.sameSite) {
347
+ const sameSite = cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1);
348
+ result += `; SameSite=${sameSite}`;
349
+ }
350
+ if (cookie.partitioned) result += "; Partitioned";
351
+ return result;
352
+ };
353
+ /**
354
+ * Extract a single cookie value by name from a cookie header string
355
+ *
356
+ * @param cookieString - The cookie header string (e.g., "name=value; name2=value2")
357
+ * @param name - The cookie name to extract
358
+ * @returns The cookie value or null if not found
359
+ */
360
+ const parseCookieValue = (cookieString, name) => {
361
+ if (!cookieString) return null;
362
+ return parseCookies(cookieString).get(name) ?? null;
363
+ };
364
+
365
+ //#endregion
366
+ //#region src/server/session/operations.ts
367
+ const DEFAULT_SESSION_CACHE_TTL_SECONDS = 300;
368
+ const JWS_ALGO = "HS256";
369
+ /**
370
+ * Parse and validate date value, throwing descriptive error on failure
371
+ * @internal
372
+ */
373
+ function parseDate(dateValue, fieldName) {
374
+ const date = new Date(dateValue);
375
+ if (Number.isNaN(date.getTime())) throw new TypeError(`Invalid date value for ${fieldName}: ${JSON.stringify(dateValue)}`);
376
+ return date;
377
+ }
378
+ /**
379
+ * Convert session data from /get-session into a signed cookie
380
+ * @param sessionData - Session and user data from Auth server
381
+ * @param secret - Secret for signing the cookie
382
+ * @param ttlSeconds - Time-to-live in seconds (default: 300 = 5 minutes)
383
+ * @returns Signed session data cookie
384
+ */
385
+ async function signSessionDataCookie(sessionData, secret, ttlSeconds = DEFAULT_SESSION_CACHE_TTL_SECONDS) {
386
+ const ttlMs = ttlSeconds * 1e3;
387
+ const expiresAt = Math.min(sessionData.session.expiresAt.getTime(), Date.now() + ttlMs);
388
+ return {
389
+ value: await signPayload(sessionData, expiresAt, secret),
390
+ expiresAt: new Date(expiresAt)
391
+ };
392
+ }
393
+ function signPayload(sessionData, expiresAt, secret) {
394
+ const encodedSecret = new TextEncoder().encode(secret);
395
+ const expSeconds = Math.floor(expiresAt / 1e3);
396
+ return new SignJWT(sessionData).setProtectedHeader({
397
+ alg: JWS_ALGO,
398
+ typ: "JWT"
399
+ }).setIssuedAt().setExpirationTime(expSeconds).setSubject(sessionData.user?.id ?? "anonymous").sign(encodedSecret);
400
+ }
401
+ /**
402
+ * Parse session data from JSON, converting date strings to Date objects
403
+ *
404
+ * Note: Better Auth API returns ISO 8601 date strings. JSON.parse() does not
405
+ * automatically convert these to Date objects, so manual conversion is required.
406
+ *
407
+ * @internal Exported for internal use by auth handler
408
+ */
409
+ function parseSessionData(json) {
410
+ if (!json || typeof json !== "object") return {
411
+ session: null,
412
+ user: null
413
+ };
414
+ const data = json;
415
+ if (!data.session || !data.user) return {
416
+ session: null,
417
+ user: null
418
+ };
419
+ try {
420
+ return {
421
+ session: {
422
+ ...data.session,
423
+ expiresAt: parseDate(data.session.expiresAt, "session.expiresAt"),
424
+ createdAt: parseDate(data.session.createdAt, "session.createdAt"),
425
+ updatedAt: parseDate(data.session.updatedAt, "session.updatedAt")
426
+ },
427
+ user: {
428
+ ...data.user,
429
+ createdAt: parseDate(data.user.createdAt, "user.createdAt"),
430
+ updatedAt: parseDate(data.user.updatedAt, "user.updatedAt")
431
+ }
432
+ };
433
+ } catch (error) {
434
+ console.error("[parseSessionData] Failed to parse session dates:", {
435
+ error: error instanceof Error ? error.message : String(error),
436
+ hasSession: !!data.session,
437
+ hasUser: !!data.user
438
+ });
439
+ return {
440
+ session: null,
441
+ user: null
442
+ };
443
+ }
444
+ }
445
+ /**
446
+ * Extract and validate session data from cookie header
447
+ * Falls back to null on any error (caller should fetch from API)
448
+ *
449
+ * @param request - Request object with cookie header
450
+ * @param cookieName - Name of session data cookie
451
+ * @param cookieSecret - cookie secret for validation
452
+ * @returns SessionData or null on validation failure
453
+ */
454
+ async function getSessionDataFromCookie(request, cookieName, cookieSecret) {
455
+ try {
456
+ const cookieHeader = request.headers.get("cookie");
457
+ if (!cookieHeader) return null;
458
+ const sessionDataCookie = parseCookies(cookieHeader).get(cookieName);
459
+ if (!sessionDataCookie) return null;
460
+ const result = await validateSessionData(sessionDataCookie, cookieSecret);
461
+ if (result.valid && result.payload) return result.payload;
462
+ console.warn("[getSessionDataFromCookie] Invalid session cookie:", {
463
+ error: result.error,
464
+ cookieName
465
+ });
466
+ return null;
467
+ } catch (error) {
468
+ console.error("[getSessionDataFromCookie] Unexpected validation error:", {
469
+ error: error instanceof Error ? error.message : String(error),
470
+ cookieName,
471
+ ...process.env.NODE_ENV !== "production" && { stack: error instanceof Error ? error.stack : void 0 }
472
+ });
473
+ return null;
474
+ }
475
+ }
476
+
477
+ //#endregion
478
+ //#region src/server/errors.ts
479
+ const ERRORS = {
480
+ MISSING_AUTH_BASE_URL: "Missing required config: baseUrl. You must provide the auth URL of your Neon Auth instance in the config object.",
481
+ MISSING_COOKIE_SECRET: "Missing required config: cookies.secret. You must provide the cookie secret in the config object.",
482
+ COOKIE_SECRET_TOO_SHORT: "cookies.secret must be at least 32 characters long for security. Generate a secure secret with: openssl rand -base64 32",
483
+ INVALID_SESSION_DATA_TTL: "cookies.sessionDataTtl must be a positive number (in seconds)"
484
+ };
485
+
486
+ //#endregion
487
+ //#region src/server/session/validator.ts
488
+ /**
489
+ * Validate session data signature and expiry using jose
490
+ * @param sessionDataString - Session data string to validate
491
+ * @param cookieSecret - cookie secret for validation
492
+ * @returns Validation result with payload if valid
493
+ */
494
+ async function validateSessionData(sessionDataString, cookieSecret) {
495
+ try {
496
+ const { payload } = await jwtVerify(sessionDataString, new TextEncoder().encode(cookieSecret), { algorithms: ["HS256"] });
497
+ return {
498
+ valid: true,
499
+ payload: parseSessionData(payload)
500
+ };
501
+ } catch (error) {
502
+ return {
503
+ valid: false,
504
+ error: error instanceof Error ? error.message : "Invalid session data"
505
+ };
506
+ }
507
+ }
508
+
509
+ //#endregion
510
+ //#region src/server/proxy/request.ts
511
+ const PROXY_HEADERS = [
512
+ "user-agent",
513
+ "authorization",
514
+ "referer",
515
+ "content-type"
516
+ ];
517
+ /**
518
+ * Proxy header constant - indicates request went through Neon Auth middleware/handler
519
+ * This is framework-agnostic and can be used by any server framework
520
+ */
521
+ const NEON_AUTH_HEADER_MIDDLEWARE_NAME = "x-neon-auth-middleware";
522
+ /**
523
+ * Handles proxying authentication requests to the upstream Neon Auth server
524
+ *
525
+ * @param baseUrl - Base URL of the Neon Auth server
526
+ * @param request - Standard Web API Request object
527
+ * @param path - API path to proxy to (e.g., 'get-session', 'sign-in')
528
+ * @returns Response from upstream server or error response
529
+ */
530
+ const handleAuthRequest = async (baseUrl, request, path) => {
531
+ const headers$1 = prepareRequestHeaders(request);
532
+ const body = await parseRequestBody(request);
533
+ try {
534
+ const upstreamURL = getUpstreamURL(baseUrl, path, { originalUrl: new URL(request.url) });
535
+ return await fetch(upstreamURL.toString(), {
536
+ method: request.method,
537
+ headers: headers$1,
538
+ body
539
+ });
540
+ } catch (error) {
541
+ if (error instanceof Error && error.name === "TypeError" && error.message.includes("fetch")) return Response.json({
542
+ error: "Unable to connect to authentication server",
543
+ code: "NETWORK_ERROR"
544
+ }, {
545
+ status: 502,
546
+ headers: { "Content-Type": "application/json" }
547
+ });
548
+ const message = error instanceof Error ? error.message : "Internal Server Error";
549
+ console.error(`[AuthError] ${message}`, error);
550
+ return Response.json({
551
+ error: message,
552
+ code: "INTERNAL_ERROR"
553
+ }, {
554
+ status: 500,
555
+ headers: { "Content-Type": "application/json" }
556
+ });
557
+ }
558
+ };
559
+ /**
560
+ * Constructs the upstream URL for proxying to Neon Auth server
561
+ *
562
+ * @param baseUrl - Base URL of the Neon Auth server
563
+ * @param path - API path (e.g., 'get-session')
564
+ * @param options - Options including original URL for preserving query params
565
+ * @returns Constructed upstream URL
566
+ */
567
+ const getUpstreamURL = (baseUrl, path, { originalUrl }) => {
568
+ const url = new URL(`${baseUrl}/${path}`);
569
+ if (originalUrl) {
570
+ url.search = originalUrl.search;
571
+ return url;
572
+ }
573
+ return url;
574
+ };
575
+ const prepareRequestHeaders = (request) => {
576
+ const headers$1 = new Headers();
577
+ for (const header of PROXY_HEADERS) if (request.headers.get(header)) headers$1.set(header, request.headers.get(header));
578
+ headers$1.set("Origin", getOrigin(request));
579
+ headers$1.set("Cookie", extractNeonAuthCookies(request.headers));
580
+ headers$1.set(NEON_AUTH_HEADER_MIDDLEWARE_NAME, "true");
581
+ return headers$1;
582
+ };
583
+ const getOrigin = (request) => {
584
+ return request.headers.get("origin") || request.headers.get("referer")?.split("/").slice(0, 3).join("/") || new URL(request.url).origin;
585
+ };
586
+ const parseRequestBody = async (request) => {
587
+ if (request.body) return request.text();
588
+ };
589
+
590
+ //#endregion
591
+ //#region src/server/proxy/response.ts
592
+ const RESPONSE_HEADERS_ALLOWLIST = [
593
+ "content-type",
594
+ "content-length",
595
+ "content-encoding",
596
+ "transfer-encoding",
597
+ "connection",
598
+ "date",
599
+ "set-cookie",
600
+ "set-auth-jwt",
601
+ "set-auth-token",
602
+ "x-neon-ret-request-id"
603
+ ];
604
+ /**
605
+ * Handles responses from upstream Neon Auth server
606
+ * - Proxies allowed headers to client
607
+ * - Mints session data cookie if session token is present
608
+ *
609
+ * @param response - Response from upstream Neon Auth server
610
+ * @param baseUrl - Base URL of Neon Auth server
611
+ * @param cookieConfig - Session cookie configuration
612
+ * @returns New Response with proxied headers and session data cookie
613
+ */
614
+ const handleAuthResponse = async (response, baseUrl, cookieConfig) => {
615
+ const responseHeaders = prepareResponseHeaders(response, cookieConfig.domain);
616
+ const sessionDataCookie = await mintSessionData(response.headers, baseUrl, cookieConfig);
617
+ if (sessionDataCookie) responseHeaders.append("Set-Cookie", sessionDataCookie);
618
+ return new Response(response.body, {
619
+ status: response.status,
620
+ statusText: response.statusText,
621
+ headers: responseHeaders
622
+ });
623
+ };
624
+ const prepareResponseHeaders = (response, domain) => {
625
+ const headers$1 = new Headers();
626
+ for (const header of RESPONSE_HEADERS_ALLOWLIST) if (header === "set-cookie") {
627
+ const cookies$1 = response.headers.getSetCookie();
628
+ for (const cookieHeader of cookies$1) if (domain) {
629
+ const parsedCookies = parseSetCookies(cookieHeader);
630
+ for (const parsedCookie of parsedCookies) {
631
+ parsedCookie.domain = domain;
632
+ headers$1.append("Set-Cookie", serializeSetCookie(parsedCookie));
633
+ }
634
+ } else headers$1.append("Set-Cookie", cookieHeader);
635
+ } else {
636
+ const value = response.headers.get(header);
637
+ if (value) headers$1.set(header, value);
638
+ }
639
+ return headers$1;
640
+ };
641
+ async function mintSessionData(headers$1, baseUrl, cookieConfig) {
642
+ const { secret, sessionDataTtl, domain } = cookieConfig;
643
+ const sessionToken = headers$1.getSetCookie().find((cookie) => cookie.includes("session_token"));
644
+ if (!sessionToken) return null;
645
+ if (sessionToken.toLowerCase().includes("max-age=0")) return serializeSetCookie({
646
+ name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
647
+ value: "",
648
+ path: "/",
649
+ domain,
650
+ httpOnly: true,
651
+ secure: true,
652
+ sameSite: "lax",
653
+ maxAge: 0
654
+ });
655
+ try {
656
+ const sessionData = await fetchSessionWithCookie(sessionToken, baseUrl);
657
+ if (sessionData.session) {
658
+ const { value: signedData, expiresAt } = await signSessionDataCookie(sessionData, secret, sessionDataTtl);
659
+ return serializeSetCookie({
660
+ name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
661
+ value: signedData,
662
+ path: "/",
663
+ domain,
664
+ httpOnly: true,
665
+ secure: true,
666
+ sameSite: "lax",
667
+ maxAge: Math.floor((expiresAt.getTime() - Date.now()) / 1e3)
668
+ });
669
+ }
670
+ } catch (error) {
671
+ const errorMessage = error instanceof Error ? error.message : String(error);
672
+ const errorContext = {
673
+ error: errorMessage,
674
+ setCookieHeaderLength: sessionToken?.length || 0
675
+ };
676
+ if (errorMessage.includes("session_token not found")) console.warn("[mintSessionData] Session token missing in set-cookie:", errorContext);
677
+ else if (errorMessage.includes("Failed to fetch session data")) console.error("[mintSessionData] Upstream /get-session request failed:", errorContext);
678
+ else if (errorMessage.includes("NEON_AUTH_COOKIE_SECRET")) console.error("[mintSessionData] Cookie secret configuration error:", errorContext);
679
+ else if (errorMessage.includes("Invalid date")) console.error("[mintSessionData] Date parsing error:", errorContext);
680
+ else console.error("[mintSessionData] Unexpected error:", {
681
+ ...errorContext,
682
+ ...process.env.NODE_ENV !== "production" && { stack: error instanceof Error ? error.stack : void 0 }
683
+ });
684
+ }
685
+ return null;
686
+ }
687
+ async function fetchSessionWithCookie(setCookieHeader, baseUrl) {
688
+ const sessionToken = parseSetCookies(setCookieHeader).find((c) => c.name.includes("session_token"));
689
+ if (!sessionToken) throw new Error("session_token not found in set-cookie header");
690
+ const response = await fetch(`${baseUrl}/get-session`, {
691
+ headers: { Cookie: `${sessionToken.name}=${sessionToken.value}` },
692
+ signal: AbortSignal.timeout(3e3)
693
+ });
694
+ if (!response.ok) throw new Error(`Failed to fetch session data: ${response.status} ${response.statusText}`);
695
+ let body;
696
+ try {
697
+ body = await response.json();
698
+ } catch (error) {
699
+ throw new Error(`Failed to parse /get-session response as JSON: ${error instanceof Error ? error.message : String(error)}`);
700
+ }
701
+ return parseSessionData(body);
702
+ }
703
+
704
+ //#endregion
705
+ //#region src/server/session/cache-handler.ts
706
+ /**
707
+ * Attempts to retrieve session data from cookie cache
708
+ * Returns Response with session data if cache hit, null otherwise
709
+ *
710
+ * This is the framework-agnostic session cache optimization used by API handlers.
711
+ *
712
+ * @param request - Standard Web API Request object
713
+ * @param cookieSecret - Secret for validating signed session cookies
714
+ * @returns Response with session data JSON if cache hit, null if miss/disabled
715
+ */
716
+ async function trySessionCache(request, cookieSecret) {
717
+ if (new URL(request.url).searchParams.get("disableCookieCache") === "true") return null;
718
+ if (!(request.headers.get("cookie") || "").includes(NEON_AUTH_SESSION_COOKIE_NAME)) return null;
719
+ try {
720
+ const sessionData = await getSessionDataFromCookie(request, NEON_AUTH_SESSION_DATA_COOKIE_NAME, cookieSecret);
721
+ if (sessionData && sessionData.session) return Response.json(sessionData);
722
+ } catch (error) {
723
+ const errorMessage = error instanceof Error ? error.message : String(error);
724
+ const errorName = error instanceof Error ? error.name : "Unknown";
725
+ if (errorName === "JWTExpired") console.debug("[trySessionCache] Session cookie expired (expected):", {
726
+ error: errorMessage,
727
+ errorType: errorName,
728
+ url: request.url
729
+ });
730
+ else if (errorName === "JWTInvalid" || errorName === "JWTClaimValidationFailed") console.warn("[trySessionCache] Invalid session cookie (possible tampering):", {
731
+ error: errorMessage,
732
+ errorType: errorName,
733
+ url: request.url
734
+ });
735
+ else console.error("[trySessionCache] Unexpected cookie validation error:", {
736
+ error: errorMessage,
737
+ errorType: errorName,
738
+ url: request.url
739
+ });
740
+ }
741
+ return null;
742
+ }
743
+
744
+ //#endregion
745
+ //#region src/server/proxy/handler.ts
746
+ /**
747
+ * Generic authentication proxy handler (framework-agnostic)
748
+ *
749
+ * Handles the complete flow:
750
+ * 1. Check if request is for getSession endpoint
751
+ * 2. Try session cache if applicable (< 1ms fast path)
752
+ * 3. Call upstream Neon Auth API
753
+ * 4. Handle response with cookie minting
754
+ *
755
+ * This is framework-agnostic and can be used by any server framework.
756
+ *
757
+ * @param config - Proxy configuration
758
+ * @returns Standard Web API Response
759
+ */
760
+ async function handleAuthProxyRequest(config) {
761
+ const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain } = config;
762
+ if (path === API_ENDPOINTS.getSession.path && request.method === API_ENDPOINTS.getSession.method) {
763
+ const cachedResponse = await trySessionCache(request, cookieSecret);
764
+ if (cachedResponse) return cachedResponse;
765
+ }
766
+ return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path), baseUrl, {
767
+ secret: cookieSecret,
768
+ sessionDataTtl,
769
+ domain
770
+ });
771
+ }
772
+
773
+ //#endregion
774
+ //#region src/server/client-factory.ts
775
+ function createAuthServerInternal(config) {
776
+ const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain } = config;
777
+ const fetchWithAuth = async (path, method, args) => {
778
+ const ctx = await getContext();
779
+ const cookies$1 = await ctx.getCookies();
780
+ const origin = await ctx.getOrigin();
781
+ const framework = ctx.getFramework();
782
+ const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
783
+ const { query, fetchOptions: _fetchOptions, ...body } = args || {};
784
+ if (query && typeof query === "object") {
785
+ const queryParams = query;
786
+ for (const [key, value] of Object.entries(queryParams)) if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
787
+ }
788
+ const headers$1 = {
789
+ Cookie: cookies$1,
790
+ Origin: origin,
791
+ [NEON_AUTH_SERVER_PROXY_HEADER]: framework
792
+ };
793
+ let requestBody;
794
+ if (method === "POST") {
795
+ headers$1["Content-Type"] = "application/json";
796
+ requestBody = JSON.stringify(Object.keys(body).length > 0 ? body : {});
797
+ }
798
+ const response = await fetch(url.toString(), {
799
+ method,
800
+ headers: headers$1,
801
+ body: requestBody
802
+ });
803
+ const setCookieHeaders = response.headers.getSetCookie();
804
+ if (setCookieHeaders.length > 0) {
805
+ for (const setCookieHeader of setCookieHeaders) {
806
+ const parsedCookies = parseSetCookies(setCookieHeader);
807
+ for (const cookie of parsedCookies) {
808
+ const cookieOptions = domain ? {
809
+ ...cookie,
810
+ domain
811
+ } : cookie;
812
+ await ctx.setCookie(cookie.name, cookie.value, cookieOptions);
813
+ }
814
+ }
815
+ try {
816
+ const sessionDataCookie = await mintSessionData(response.headers, baseUrl, {
817
+ secret: cookieSecret,
818
+ sessionDataTtl,
819
+ domain
820
+ });
821
+ if (sessionDataCookie) {
822
+ const [parsedSessionData] = parseSetCookies(sessionDataCookie);
823
+ if (parsedSessionData) await ctx.setCookie(parsedSessionData.name, parsedSessionData.value, parsedSessionData);
824
+ }
825
+ } catch (error) {
826
+ console.error("[fetchWithAuth] Failed to mint session data cookie:", error);
827
+ }
828
+ }
829
+ const responseData = await response.json().catch(() => null);
830
+ if (!response.ok) return {
831
+ data: null,
832
+ error: {
833
+ message: responseData?.message || response.statusText,
834
+ status: response.status,
835
+ statusText: response.statusText
836
+ }
837
+ };
838
+ return {
839
+ data: responseData,
840
+ error: null
841
+ };
842
+ };
843
+ const baseServer = createApiProxy(API_ENDPOINTS, fetchWithAuth);
844
+ const originalGetSession = baseServer.getSession;
845
+ baseServer.getSession = async (...args) => {
846
+ const [data] = args;
847
+ if (!(data?.query?.disableCookieCache === "true")) try {
848
+ const cookiesString = await (await getContext()).getCookies();
849
+ const hasSessionToken = cookiesString.includes(NEON_AUTH_SESSION_COOKIE_NAME);
850
+ const sessionDataCookie = parseCookieValue(cookiesString, NEON_AUTH_SESSION_DATA_COOKIE_NAME);
851
+ if (sessionDataCookie && hasSessionToken) {
852
+ const result = await validateSessionData(sessionDataCookie, cookieSecret);
853
+ if (result.valid && result.payload) return {
854
+ data: result.payload,
855
+ error: null
856
+ };
857
+ }
858
+ } catch (error) {
859
+ console.error("[auth.getSession] Cookie validation error:", error);
860
+ }
861
+ return originalGetSession(...args);
862
+ };
863
+ return baseServer;
864
+ }
865
+ function isEndpointConfig(value) {
866
+ return typeof value === "object" && value !== null && "path" in value && "method" in value;
867
+ }
868
+ function createApiProxy(endpoints, fetchFn) {
869
+ return new Proxy({}, {
870
+ get(target, prop) {
871
+ if (prop in target) return target[prop];
872
+ const endpoint = endpoints[prop];
873
+ if (!endpoint) return;
874
+ if (isEndpointConfig(endpoint)) return (args) => fetchFn(endpoint.path, endpoint.method, args);
875
+ return createApiProxy(endpoint, fetchFn);
876
+ },
877
+ set(target, prop, value) {
878
+ target[prop] = value;
879
+ return true;
880
+ }
881
+ });
882
+ }
883
+
884
+ //#endregion
885
+ //#region src/next/server/adapter.ts
886
+ /**
887
+ * Creates a Next.js-specific RequestContext that reads cookies and headers
888
+ * from next/headers and handles cookie setting.
889
+ */
890
+ async function createNextRequestContext() {
891
+ const cookieStore = await cookies();
892
+ const headerStore = await headers();
893
+ return {
894
+ getCookies() {
895
+ return extractNeonAuthCookies(headerStore);
896
+ },
897
+ setCookie(name, value, options) {
898
+ cookieStore.set(name, value, options);
899
+ },
900
+ getHeader(name) {
901
+ return headerStore.get(name) ?? null;
902
+ },
903
+ getOrigin() {
904
+ return headerStore.get("origin") || headerStore.get("referer")?.split("/").slice(0, 3).join("/") || "";
905
+ },
906
+ getFramework() {
907
+ return "nextjs";
908
+ }
909
+ };
910
+ }
911
+
912
+ //#endregion
913
+ //#region src/server/config.ts
914
+ /**
915
+ * Framework-agnostic configuration types for Neon Auth
916
+ */
917
+ /**
918
+ * Validates cookie configuration meets security requirements
919
+ * @param cookies - The cookie configuration to validate
920
+ * @throws Error if secret is too short (< 32 characters)
921
+ */
922
+ function validateCookieConfig(cookies$1) {
923
+ if (!cookies$1.secret) throw new Error(ERRORS.MISSING_COOKIE_SECRET);
924
+ if (cookies$1.secret.length < 32) throw new Error(ERRORS.COOKIE_SECRET_TOO_SHORT);
925
+ if (cookies$1.sessionDataTtl !== void 0 && cookies$1.sessionDataTtl <= 0) throw new Error(ERRORS.INVALID_SESSION_DATA_TTL);
926
+ }
927
+
928
+ //#endregion
929
+ //#region src/next/server/handler.ts
930
+ /**
931
+ * An API route handler to handle the auth requests from the client and proxy them to the Neon Auth.
932
+ *
933
+ * @param config - Required configuration
934
+ * @param config.baseUrl - Base URL of your Neon Auth instance
935
+ * @param config.cookies - Cookie configuration
936
+ * @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
937
+ * @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
938
+ * @returns A Next.js API handler functions that can be used in a Next.js route.
939
+ * @throws Error if `cookies.secret` is less than 32 characters
940
+ *
941
+ * @example
942
+ * Mount the `authApiHandler` to an API route. Create a route file inside `/api/auth/[...all]/route.ts` directory.
943
+ * And add the following code:
944
+ *
945
+ * ```ts
946
+ * // app/api/auth/[...all]/route.ts
947
+ * import { authApiHandler } from '@neondatabase/auth/next';
948
+ *
949
+ * export const { GET, POST } = authApiHandler({
950
+ * baseUrl: process.env.NEON_AUTH_BASE_URL!,
951
+ * cookies: {
952
+ * secret: process.env.NEON_AUTH_COOKIE_SECRET!,
953
+ * },
954
+ * });
955
+ * ```
956
+ */
957
+ function authApiHandler(config) {
958
+ const { baseUrl, cookies: cookies$1 } = config;
959
+ validateCookieConfig(cookies$1);
960
+ const handler = async (request, { params }) => {
961
+ return handleAuthProxyRequest({
962
+ request,
963
+ path: (await params).path.join("/"),
964
+ baseUrl,
965
+ cookieSecret: cookies$1.secret,
966
+ sessionDataTtl: cookies$1.sessionDataTtl,
967
+ domain: cookies$1.domain
968
+ });
969
+ };
970
+ return {
971
+ GET: handler,
972
+ POST: handler,
973
+ PUT: handler,
974
+ DELETE: handler,
975
+ PATCH: handler
976
+ };
977
+ }
978
+
979
+ //#endregion
980
+ //#region src/server/middleware/oauth.ts
981
+ /**
982
+ * Checks if the current request needs OAuth session verification
983
+ * This happens when returning from OAuth provider with a verifier token
984
+ *
985
+ * @param request - Standard Web API Request object
986
+ * @returns true if session verification is needed
987
+ */
988
+ function needsSessionVerification(request) {
989
+ const hasVerifier = new URL(request.url).searchParams.has(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
990
+ const cookieHeader = request.headers.get("cookie");
991
+ if (!cookieHeader) return false;
992
+ const hasChallenge = parseCookies(cookieHeader).has(NEON_AUTH_SESSION_CHALLENGE_COOKIE_NAME);
993
+ return hasVerifier && hasChallenge;
994
+ }
995
+ /**
996
+ * Exchanges OAuth verifier token for session cookie
997
+ * This completes the OAuth flow by verifying the session challenge
998
+ *
999
+ * @param request - Standard Web API Request object
1000
+ * @param baseUrl - Base URL of Neon Auth server
1001
+ * @param cookieSecret - Secret for signing session cookies
1002
+ * @param sessionDataTtl - Optional TTL for session data cache
1003
+ * @param domain - Optional cookie domain
1004
+ * @returns Exchange result with redirect URL and cookies, or null if exchange not needed/failed
1005
+ */
1006
+ async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain) {
1007
+ const url = new URL(request.url);
1008
+ const verifier = url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
1009
+ const cookieHeader = request.headers.get("cookie");
1010
+ if (!cookieHeader) return null;
1011
+ const challenge = parseCookies(cookieHeader).get(NEON_AUTH_SESSION_CHALLENGE_COOKIE_NAME);
1012
+ if (!verifier || !challenge) return null;
1013
+ const response = await handleAuthResponse(await handleAuthRequest(baseUrl, new Request(request.url, {
1014
+ method: "GET",
1015
+ headers: request.headers
1016
+ }), "get-session"), baseUrl, {
1017
+ secret: cookieSecret,
1018
+ sessionDataTtl,
1019
+ domain
1020
+ });
1021
+ if (response.ok) {
1022
+ const setCookieHeaders = response.headers.getSetCookie();
1023
+ url.searchParams.delete(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
1024
+ return {
1025
+ redirectUrl: url,
1026
+ cookies: setCookieHeaders,
1027
+ success: true
1028
+ };
1029
+ }
1030
+ return null;
1031
+ }
1032
+
1033
+ //#endregion
1034
+ //#region src/server/middleware/route-protection.ts
1035
+ /**
1036
+ * Checks if a given pathname should be protected (require authentication)
1037
+ *
1038
+ * @param pathname - URL pathname to check
1039
+ * @param skipRoutes - Array of route prefixes to skip protection
1040
+ * @returns true if route should be protected, false if it should be skipped
1041
+ */
1042
+ function shouldProtectRoute(pathname, skipRoutes) {
1043
+ return !skipRoutes.some((route) => pathname.startsWith(route));
1044
+ }
1045
+ /**
1046
+ * Checks if the current request requires a valid session
1047
+ * Returns result indicating if request should proceed, redirect, or continue
1048
+ *
1049
+ * @param pathname - URL pathname being accessed
1050
+ * @param skipRoutes - Routes that don't require authentication
1051
+ * @param loginUrl - URL to redirect to for login (if applicable)
1052
+ * @param session - Current session data (null if not authenticated)
1053
+ * @returns Session check result
1054
+ */
1055
+ function checkSessionRequired(pathname, skipRoutes, loginUrl, session) {
1056
+ if (pathname.startsWith(loginUrl)) return {
1057
+ allowed: true,
1058
+ requiresRedirect: false
1059
+ };
1060
+ if (!shouldProtectRoute(pathname, skipRoutes)) return {
1061
+ allowed: true,
1062
+ requiresRedirect: false
1063
+ };
1064
+ if (!session || session.session === null) return {
1065
+ allowed: false,
1066
+ requiresRedirect: true
1067
+ };
1068
+ return {
1069
+ allowed: true,
1070
+ session,
1071
+ requiresRedirect: false
1072
+ };
1073
+ }
1074
+
1075
+ //#endregion
1076
+ //#region src/server/middleware/processor.ts
1077
+ /**
1078
+ * Generic authentication middleware processor (framework-agnostic)
1079
+ *
1080
+ * Handles the complete middleware flow:
1081
+ * 1. Check if login URL (skip auth)
1082
+ * 2. Check OAuth verification (exchange token)
1083
+ * 3. Get session (delegates to handleAuthProxyRequest for cookie cache + upstream fallback)
1084
+ * 4. Check if route requires protection
1085
+ * 5. Return decision object
1086
+ *
1087
+ * This is framework-agnostic - it returns a decision, NOT a framework-specific response.
1088
+ * The calling framework converts the decision to its response type (NextResponse, etc.)
1089
+ *
1090
+ * @param config - Middleware configuration
1091
+ * @returns Decision object indicating what action to take
1092
+ */
1093
+ async function processAuthMiddleware(config) {
1094
+ const { request, pathname, skipRoutes, loginUrl, baseUrl, cookieSecret, sessionDataTtl, domain } = config;
1095
+ if (pathname.startsWith(loginUrl)) return { action: "allow" };
1096
+ if (needsSessionVerification(request)) {
1097
+ const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain);
1098
+ if (exchangeResult !== null) return {
1099
+ action: "redirect_oauth",
1100
+ redirectUrl: exchangeResult.redirectUrl,
1101
+ cookies: exchangeResult.cookies
1102
+ };
1103
+ }
1104
+ const cookieHeader = request.headers.get("cookie") || "";
1105
+ const hasSessionToken = cookieHeader.includes(NEON_AUTH_SESSION_COOKIE_NAME);
1106
+ const hasStaleSessionData = parseCookies(cookieHeader).has(NEON_AUTH_SESSION_DATA_COOKIE_NAME) && !hasSessionToken;
1107
+ let sessionData = {
1108
+ session: null,
1109
+ user: null
1110
+ };
1111
+ if (hasSessionToken) {
1112
+ const sessionResponse = await handleAuthProxyRequest({
1113
+ request,
1114
+ path: "get-session",
1115
+ baseUrl,
1116
+ cookieSecret,
1117
+ sessionDataTtl,
1118
+ domain
1119
+ });
1120
+ if (sessionResponse.ok) {
1121
+ const data = await sessionResponse.json().catch(() => null);
1122
+ if (data) sessionData = data;
1123
+ }
1124
+ }
1125
+ if (checkSessionRequired(pathname, skipRoutes, loginUrl, sessionData).allowed) return {
1126
+ action: "allow",
1127
+ headers: { [NEON_AUTH_HEADER_MIDDLEWARE_NAME]: "true" }
1128
+ };
1129
+ const cookies$1 = [];
1130
+ if (hasStaleSessionData) cookies$1.push(serializeSetCookie({
1131
+ name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
1132
+ value: "",
1133
+ path: "/",
1134
+ domain,
1135
+ httpOnly: true,
1136
+ secure: true,
1137
+ sameSite: "lax",
1138
+ maxAge: 0
1139
+ }));
1140
+ return {
1141
+ action: "redirect_login",
1142
+ redirectUrl: new URL(loginUrl, request.url),
1143
+ cookies: cookies$1.length > 0 ? cookies$1 : void 0
1144
+ };
1145
+ }
1146
+
1147
+ //#endregion
1148
+ //#region src/next/server/middleware.ts
1149
+ const SKIP_ROUTES = [
1150
+ "/api/auth",
1151
+ "/auth/callback",
1152
+ "/auth/sign-in",
1153
+ "/auth/sign-up",
1154
+ "/auth/magic-link",
1155
+ "/auth/email-otp",
1156
+ "/auth/forgot-password"
1157
+ ];
1158
+ /**
1159
+ * A Next.js middleware to protect routes from unauthenticated requests and refresh the session if required.
1160
+ *
1161
+ * @param config - Required middleware configuration
1162
+ * @param config.baseUrl - Base URL of your Neon Auth instance
1163
+ * @param config.cookies - Cookie configuration
1164
+ * @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
1165
+ * @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
1166
+ * @param config.loginUrl - The URL to redirect to when the user is not authenticated (default: '/auth/sign-in')
1167
+ * @returns A middleware function that can be used in the Next.js app.
1168
+ * @throws Error if `cookies.secret` is less than 32 characters
1169
+ *
1170
+ * @example
1171
+ * ```ts
1172
+ * import { neonAuthMiddleware } from "@neondatabase/auth/next"
1173
+ *
1174
+ * export default neonAuthMiddleware({
1175
+ * baseUrl: process.env.NEON_AUTH_BASE_URL!,
1176
+ * cookies: {
1177
+ * secret: process.env.NEON_AUTH_COOKIE_SECRET!,
1178
+ * },
1179
+ * loginUrl: '/auth/sign-in',
1180
+ * });
1181
+ * ```
1182
+ */
1183
+ function neonAuthMiddleware(config) {
1184
+ const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in" } = config;
1185
+ validateCookieConfig(cookies$1);
1186
+ return async (request) => {
1187
+ const pathname = request.nextUrl.pathname;
1188
+ const result = await processAuthMiddleware({
1189
+ request,
1190
+ pathname,
1191
+ skipRoutes: SKIP_ROUTES,
1192
+ loginUrl,
1193
+ baseUrl,
1194
+ cookieSecret: cookies$1.secret,
1195
+ sessionDataTtl: cookies$1.sessionDataTtl,
1196
+ domain: cookies$1.domain
1197
+ });
1198
+ switch (result.action) {
1199
+ case "allow": {
1200
+ const headers$1 = new Headers(request.headers);
1201
+ if (result.headers) for (const [key, value] of Object.entries(result.headers)) headers$1.set(key, value);
1202
+ return NextResponse.next({ request: { headers: headers$1 } });
1203
+ }
1204
+ case "redirect_oauth": {
1205
+ const oauthHeaders = new Headers();
1206
+ for (const cookie of result.cookies) oauthHeaders.append("Set-Cookie", cookie);
1207
+ return NextResponse.redirect(result.redirectUrl, { headers: oauthHeaders });
1208
+ }
1209
+ case "redirect_login":
1210
+ if (result.cookies && result.cookies.length > 0) {
1211
+ const loginHeaders = new Headers();
1212
+ for (const cookie of result.cookies) loginHeaders.append("Set-Cookie", cookie);
1213
+ return NextResponse.redirect(result.redirectUrl, { headers: loginHeaders });
1214
+ }
1215
+ return NextResponse.redirect(result.redirectUrl);
1216
+ }
1217
+ };
1218
+ }
1219
+
1220
+ //#endregion
1221
+ //#region src/next/server/index.ts
1222
+ /**
1223
+ * Unified entry point for Neon Auth in Next.js
1224
+ *
1225
+ * This is the recommended way to use Neon Auth in Next.js. It provides a single
1226
+ * entry point that combines all server-side functionality.
1227
+ *
1228
+ * **Features:**
1229
+ * - All Better Auth server methods (signIn, signUp, getSession, etc.)
1230
+ * - `.handler()` - API route handler for `/api/auth/[...path]`
1231
+ * - `.middleware(config?)` - Middleware for route protection
1232
+ *
1233
+ * **Where to use:**
1234
+ * - React Server Components
1235
+ * - Server Actions
1236
+ * - Route Handlers
1237
+ * - Middleware
1238
+ *
1239
+ * @param config - Required configuration
1240
+ * @param config.baseUrl - Base URL of your Neon Auth instance
1241
+ * @param config.cookies - Cookie configuration
1242
+ * @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
1243
+ * @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
1244
+ * @param config.cookies.domain - Optional cookie domain (default: current domain)
1245
+ * @returns Unified auth instance with server methods, handler, and middleware
1246
+ * @throws Error if `cookies.secret` is less than 32 characters
1247
+ *
1248
+ * @example
1249
+ * ```typescript
1250
+ * // lib/auth.ts - Create a singleton instance
1251
+ * import { createNeonAuth } from '@neondatabase/auth/next/server';
1252
+ *
1253
+ * export const auth = createNeonAuth({
1254
+ * baseUrl: process.env.NEON_AUTH_BASE_URL!,
1255
+ * cookies: {
1256
+ * secret: process.env.NEON_AUTH_COOKIE_SECRET!,
1257
+ * sessionDataTtl: 300, // 5 minutes (default)
1258
+ * },
1259
+ * });
1260
+ * ```
1261
+ *
1262
+ * @example
1263
+ * ```typescript
1264
+ * // app/api/auth/[...path]/route.ts - API handler
1265
+ * import { auth } from '@/lib/auth';
1266
+ *
1267
+ * export const { GET, POST } = auth.handler();
1268
+ * ```
1269
+ *
1270
+ * @example
1271
+ * ```typescript
1272
+ * // middleware.ts - Route protection
1273
+ * import { auth } from '@/lib/auth';
1274
+ *
1275
+ * export default auth.middleware({ loginUrl: '/auth/sign-in' });
1276
+ *
1277
+ * export const config = {
1278
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
1279
+ * };
1280
+ * ```
1281
+ *
1282
+ * @example
1283
+ * ```typescript
1284
+ * // app/page.tsx - Server Component
1285
+ * import { auth } from '@/lib/auth';
1286
+ *
1287
+ * // Server components using `auth` methods must be rendered dynamically
1288
+ * export const dynamic = 'force-dynamic'
1289
+ *
1290
+ * export default async function Page() {
1291
+ * const { data: session } = await auth.getSession();
1292
+ * if (!session?.user) return <div>Not logged in</div>;
1293
+ * return <div>Hello {session.user.name}</div>;
1294
+ * }
1295
+ * ```
1296
+ *
1297
+ * @example
1298
+ * ```typescript
1299
+ * // app/actions.ts - Server Action
1300
+ * 'use server';
1301
+ * import { auth } from '@/lib/auth';
1302
+ * import { redirect } from 'next/navigation';
1303
+ *
1304
+ * export async function signIn(formData: FormData) {
1305
+ * const { error } = await auth.signIn.email({
1306
+ * email: formData.get('email') as string,
1307
+ * password: formData.get('password') as string,
1308
+ * });
1309
+ * if (error) return { error: error.message };
1310
+ * redirect('/dashboard');
1311
+ * }
1312
+ * ```
1313
+ */
1314
+ function createNeonAuth(config) {
1315
+ const { baseUrl, cookies: cookies$1 } = config;
1316
+ validateCookieConfig(cookies$1);
1317
+ const server = createAuthServerInternal({
1318
+ baseUrl,
1319
+ context: createNextRequestContext,
1320
+ cookieSecret: cookies$1.secret,
1321
+ sessionDataTtl: cookies$1.sessionDataTtl,
1322
+ domain: cookies$1.domain
1323
+ });
1324
+ /**
1325
+ * Creates API route handlers for Next.js
1326
+ *
1327
+ * Mount this in your API routes to handle auth requests:
1328
+ * - `/api/auth/[...path]/route.ts`
1329
+ *
1330
+ * @returns Object with GET, POST, PUT, DELETE, PATCH handlers
1331
+ *
1332
+ * @example
1333
+ * ```typescript
1334
+ * // app/api/auth/[...path]/route.ts
1335
+ * import { auth } from '@/lib/auth';
1336
+ *
1337
+ * export const { GET, POST } = auth.handler();
1338
+ * ```
1339
+ */
1340
+ server.handler = () => authApiHandler(config);
1341
+ /**
1342
+ * Creates middleware for route protection
1343
+ *
1344
+ * Protects routes from unauthenticated access and handles:
1345
+ * - Session validation and refresh
1346
+ * - OAuth callback processing
1347
+ * - Login redirects
1348
+ *
1349
+ * @param middlewareConfig - Optional middleware configuration
1350
+ * @param middlewareConfig.loginUrl - URL to redirect to when not authenticated (default: '/auth/sign-in')
1351
+ * @returns Middleware function for Next.js
1352
+ *
1353
+ * @example
1354
+ * ```typescript
1355
+ * // middleware.ts
1356
+ * import { auth } from '@/lib/auth';
1357
+ *
1358
+ * export default auth.middleware({ loginUrl: '/auth/sign-in' });
1359
+ *
1360
+ * export const config = {
1361
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
1362
+ * };
1363
+ * ```
1364
+ */
1365
+ server.middleware = (middlewareConfig) => neonAuthMiddleware({
1366
+ ...config,
1367
+ ...middlewareConfig
1368
+ });
1369
+ return server;
1370
+ }
1371
+
1372
+ //#endregion
1373
+ export { createNeonAuth };