@neondatabase/auth 0.2.0-beta.1 → 0.3.0-beta
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/LICENSE +201 -0
- package/dist/{adapter-core-CnrOXh1T.d.mts → adapter-core-B9uDhoYq.d.mts} +291 -362
- package/dist/{adapter-core-CtmnMMJ7.mjs → adapter-core-D00qcqMo.mjs} +1 -1
- package/dist/{better-auth-react-adapter-DNi5PC5D.d.mts → better-auth-react-adapter-BO4jLN4H.d.mts} +251 -251
- package/dist/{better-auth-react-adapter-Dv-o6A6O.mjs → better-auth-react-adapter-Xdj-69i9.mjs} +1 -1
- package/dist/{index-CzsGMS7C.d.mts → index-B_Q0Tp1D.d.mts} +0 -1
- package/dist/index.d.mts +92 -4
- package/dist/index.mjs +2 -2
- package/dist/{neon-auth-Cs2cWh1B.mjs → neon-auth-DBOB8sXF.mjs} +1 -1
- package/dist/next/index.d.mts +17 -17
- package/dist/next/index.mjs +3 -3
- package/dist/next/server/index.d.mts +2 -4
- package/dist/next/server/index.mjs +323 -235
- package/dist/react/adapters/index.d.mts +3 -3
- package/dist/react/adapters/index.mjs +2 -2
- package/dist/react/index.d.mts +6 -5
- package/dist/react/index.mjs +5 -5
- package/dist/react/ui/index.d.mts +1 -2
- package/dist/react/ui/index.mjs +2 -4
- package/dist/react/ui/server.mjs +2 -2
- package/dist/{supabase-adapter-BlcGPyOf.mjs → supabase-adapter-CIBMebXB.mjs} +1 -1
- package/dist/{supabase-adapter-DUqw2fw8.d.mts → supabase-adapter-CSDRL1ZU.d.mts} +285 -316
- package/dist/types/index.d.mts +2 -2
- package/dist/ui/.safelist.html +1 -1
- package/dist/ui/css.css +2 -2
- package/dist/ui/theme.css +1 -1
- package/dist/ui-CnVnqGns.mjs +3 -0
- package/dist/vanilla/adapters/index.d.mts +3 -3
- package/dist/vanilla/adapters/index.mjs +2 -2
- package/dist/vanilla/index.d.mts +3 -3
- package/dist/vanilla/index.mjs +2 -2
- package/package.json +23 -21
- package/dist/chunk-VCZJYX65-CLnrj1o7-D6ZQkcc_.mjs +0 -543
- package/dist/neon-auth-BEGCfAe6.d.mts +0 -107
- package/dist/ui-COLWzDsu.mjs +0 -12469
- /package/dist/{adapters-B7YKkjaL.mjs → adapters-CUvhsAvY.mjs} +0 -0
- /package/dist/{index-OEBbnNdr.d.mts → index-UW23fDSn.d.mts} +0 -0
|
@@ -473,6 +473,35 @@ async function getSessionDataFromCookie(request, cookieName, cookieSecret) {
|
|
|
473
473
|
return null;
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Fetch session data from upstream using session token cookie
|
|
478
|
+
*
|
|
479
|
+
* @param sessionTokenCookie - Session token cookie string (can be Set-Cookie header or "name=value" format)
|
|
480
|
+
* @param baseUrl - Auth server base URL
|
|
481
|
+
* @returns Session data from upstream
|
|
482
|
+
*/
|
|
483
|
+
async function fetchSessionWithCookie(sessionTokenCookie, baseUrl) {
|
|
484
|
+
let cookieName;
|
|
485
|
+
let cookieValue;
|
|
486
|
+
if (sessionTokenCookie.includes("=")) {
|
|
487
|
+
const [name, ...valueParts] = sessionTokenCookie.split(";")[0].trim().split("=");
|
|
488
|
+
cookieName = name.trim();
|
|
489
|
+
cookieValue = valueParts.join("=").trim();
|
|
490
|
+
} else throw new Error("Invalid session token cookie format");
|
|
491
|
+
if (!cookieName.includes("session_token")) throw new Error("session_token not found in cookie");
|
|
492
|
+
const response = await fetch(`${baseUrl}/get-session`, {
|
|
493
|
+
headers: { Cookie: `${cookieName}=${cookieValue}` },
|
|
494
|
+
signal: AbortSignal.timeout(3e3)
|
|
495
|
+
});
|
|
496
|
+
if (!response.ok) throw new Error(`Failed to fetch session data: ${response.status} ${response.statusText}`);
|
|
497
|
+
let body;
|
|
498
|
+
try {
|
|
499
|
+
body = await response.json();
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw new Error(`Failed to parse /get-session response as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
502
|
+
}
|
|
503
|
+
return parseSessionData(body);
|
|
504
|
+
}
|
|
476
505
|
|
|
477
506
|
//#endregion
|
|
478
507
|
//#region src/server/errors.ts
|
|
@@ -506,6 +535,241 @@ async function validateSessionData(sessionDataString, cookieSecret) {
|
|
|
506
535
|
}
|
|
507
536
|
}
|
|
508
537
|
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/server/session/minting.ts
|
|
540
|
+
/**
|
|
541
|
+
* Core minting logic - creates session_data cookie from session token
|
|
542
|
+
*
|
|
543
|
+
* @param sessionTokenCookie - Session token cookie string (format: "name=value")
|
|
544
|
+
* @param baseUrl - Auth server base URL
|
|
545
|
+
* @param cookieConfig - Cookie configuration
|
|
546
|
+
* @returns Set-Cookie string or null on error
|
|
547
|
+
*/
|
|
548
|
+
async function mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig) {
|
|
549
|
+
try {
|
|
550
|
+
const sessionData = await fetchSessionWithCookie(sessionTokenCookie, baseUrl);
|
|
551
|
+
if (!sessionData.session) return null;
|
|
552
|
+
const { value: signedData, expiresAt } = await signSessionDataCookie(sessionData, cookieConfig.secret, cookieConfig.sessionDataTtl);
|
|
553
|
+
const maxAge = Math.floor((expiresAt.getTime() - Date.now()) / 1e3);
|
|
554
|
+
return serializeSetCookie({
|
|
555
|
+
name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
556
|
+
value: signedData,
|
|
557
|
+
path: "/",
|
|
558
|
+
domain: cookieConfig.domain,
|
|
559
|
+
httpOnly: true,
|
|
560
|
+
secure: true,
|
|
561
|
+
sameSite: "lax",
|
|
562
|
+
maxAge
|
|
563
|
+
});
|
|
564
|
+
} catch (error) {
|
|
565
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
566
|
+
console.error("[mintSessionDataCookie] Failed to mint session_data cookie:", {
|
|
567
|
+
error: errorMessage,
|
|
568
|
+
...process.env.NODE_ENV !== "production" && { stack: error instanceof Error ? error.stack : void 0 }
|
|
569
|
+
});
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Utility A: Mint session_data cookie when session_token is updated by upstream
|
|
575
|
+
*
|
|
576
|
+
* Checks response headers for session_token in Set-Cookie, then mints session_data.
|
|
577
|
+
* Handles token deletion (max-age=0) by returning deletion cookie.
|
|
578
|
+
*
|
|
579
|
+
* Use case: Response handling in proxy/middleware after upstream auth calls
|
|
580
|
+
*
|
|
581
|
+
* @param responseHeaders - Response headers from upstream auth server
|
|
582
|
+
* @param baseUrl - Auth server base URL
|
|
583
|
+
* @param cookieConfig - Cookie configuration
|
|
584
|
+
* @returns Set-Cookie string for session_data or null if no action needed
|
|
585
|
+
*/
|
|
586
|
+
async function mintSessionDataFromResponse(responseHeaders, baseUrl, cookieConfig) {
|
|
587
|
+
const sessionTokenCookie = responseHeaders.getSetCookie().find((cookie) => cookie.includes("session_token"));
|
|
588
|
+
if (!sessionTokenCookie) return null;
|
|
589
|
+
if (sessionTokenCookie.toLowerCase().includes("max-age=0")) return serializeSetCookie({
|
|
590
|
+
name: NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
591
|
+
value: "",
|
|
592
|
+
path: "/",
|
|
593
|
+
domain: cookieConfig.domain,
|
|
594
|
+
httpOnly: true,
|
|
595
|
+
secure: true,
|
|
596
|
+
sameSite: "lax",
|
|
597
|
+
maxAge: 0
|
|
598
|
+
});
|
|
599
|
+
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Utility B: Mint/refresh session_data cookie from existing session_token
|
|
603
|
+
*
|
|
604
|
+
* Extracts session_token from request cookies and mints a fresh session_data cookie.
|
|
605
|
+
* Use case: Cache misses, expired cookies, proactive refresh
|
|
606
|
+
*
|
|
607
|
+
* @param sessionTokenCookie - Session token cookie string (format: "name=value")
|
|
608
|
+
* @param baseUrl - Auth server base URL
|
|
609
|
+
* @param cookieConfig - Cookie configuration
|
|
610
|
+
* @returns Set-Cookie string for session_data or null on error
|
|
611
|
+
*/
|
|
612
|
+
async function mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfig) {
|
|
613
|
+
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/server/client-factory.ts
|
|
618
|
+
function createAuthServerInternal(config) {
|
|
619
|
+
const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain } = config;
|
|
620
|
+
const fetchWithAuth = async (path, method, args) => {
|
|
621
|
+
const ctx = await getContext();
|
|
622
|
+
const cookies$1 = await ctx.getCookies();
|
|
623
|
+
const origin = await ctx.getOrigin();
|
|
624
|
+
const framework = ctx.getFramework();
|
|
625
|
+
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
626
|
+
const { query, fetchOptions: _fetchOptions, ...body } = args || {};
|
|
627
|
+
if (query && typeof query === "object") {
|
|
628
|
+
const queryParams = query;
|
|
629
|
+
for (const [key, value] of Object.entries(queryParams)) if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
630
|
+
}
|
|
631
|
+
const headers$1 = {
|
|
632
|
+
Cookie: cookies$1,
|
|
633
|
+
Origin: origin,
|
|
634
|
+
[NEON_AUTH_SERVER_PROXY_HEADER]: framework
|
|
635
|
+
};
|
|
636
|
+
let requestBody;
|
|
637
|
+
if (method === "POST") {
|
|
638
|
+
headers$1["Content-Type"] = "application/json";
|
|
639
|
+
requestBody = JSON.stringify(Object.keys(body).length > 0 ? body : {});
|
|
640
|
+
}
|
|
641
|
+
const response = await fetch(url.toString(), {
|
|
642
|
+
method,
|
|
643
|
+
headers: headers$1,
|
|
644
|
+
body: requestBody
|
|
645
|
+
});
|
|
646
|
+
const setCookieHeaders = response.headers.getSetCookie();
|
|
647
|
+
if (setCookieHeaders.length > 0) {
|
|
648
|
+
for (const setCookieHeader of setCookieHeaders) {
|
|
649
|
+
const parsedCookies = parseSetCookies(setCookieHeader);
|
|
650
|
+
for (const cookie of parsedCookies) {
|
|
651
|
+
const cookieOptions = {
|
|
652
|
+
...cookie,
|
|
653
|
+
domain,
|
|
654
|
+
partitioned: void 0,
|
|
655
|
+
sameSite: "lax"
|
|
656
|
+
};
|
|
657
|
+
await ctx.setCookie(cookie.name, cookie.value, cookieOptions);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, {
|
|
662
|
+
secret: cookieSecret,
|
|
663
|
+
sessionDataTtl,
|
|
664
|
+
domain
|
|
665
|
+
});
|
|
666
|
+
if (sessionDataCookie) {
|
|
667
|
+
const [parsedSessionData] = parseSetCookies(sessionDataCookie);
|
|
668
|
+
if (parsedSessionData) await ctx.setCookie(parsedSessionData.name, parsedSessionData.value, parsedSessionData);
|
|
669
|
+
}
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.error("[fetchWithAuth] Failed to mint session data cookie:", error);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const responseData = await response.json().catch(() => null);
|
|
675
|
+
if (!response.ok) return {
|
|
676
|
+
data: null,
|
|
677
|
+
error: {
|
|
678
|
+
message: responseData?.message || response.statusText,
|
|
679
|
+
status: response.status,
|
|
680
|
+
statusText: response.statusText
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
data: responseData,
|
|
685
|
+
error: null
|
|
686
|
+
};
|
|
687
|
+
};
|
|
688
|
+
const baseServer = createApiProxy(API_ENDPOINTS, fetchWithAuth);
|
|
689
|
+
const originalGetSession = baseServer.getSession;
|
|
690
|
+
baseServer.getSession = async (...args) => {
|
|
691
|
+
const [data] = args;
|
|
692
|
+
if (!(data?.query?.disableCookieCache === "true")) try {
|
|
693
|
+
const cookiesString = await (await getContext()).getCookies();
|
|
694
|
+
const hasSessionToken = cookiesString.includes(NEON_AUTH_SESSION_COOKIE_NAME);
|
|
695
|
+
const sessionDataCookie = parseCookieValue(cookiesString, NEON_AUTH_SESSION_DATA_COOKIE_NAME);
|
|
696
|
+
if (sessionDataCookie && hasSessionToken) {
|
|
697
|
+
const result = await validateSessionData(sessionDataCookie, cookieSecret);
|
|
698
|
+
if (result.valid && result.payload) return {
|
|
699
|
+
data: result.payload,
|
|
700
|
+
error: null
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
} catch (error) {
|
|
704
|
+
console.error("[auth.getSession] Cookie validation error:", error);
|
|
705
|
+
}
|
|
706
|
+
return originalGetSession(...args);
|
|
707
|
+
};
|
|
708
|
+
return baseServer;
|
|
709
|
+
}
|
|
710
|
+
function isEndpointConfig(value) {
|
|
711
|
+
return typeof value === "object" && value !== null && "path" in value && "method" in value;
|
|
712
|
+
}
|
|
713
|
+
function createApiProxy(endpoints, fetchFn) {
|
|
714
|
+
return new Proxy({}, {
|
|
715
|
+
get(target, prop) {
|
|
716
|
+
if (prop in target) return target[prop];
|
|
717
|
+
const endpoint = endpoints[prop];
|
|
718
|
+
if (!endpoint) return;
|
|
719
|
+
if (isEndpointConfig(endpoint)) return (args) => fetchFn(endpoint.path, endpoint.method, args);
|
|
720
|
+
return createApiProxy(endpoint, fetchFn);
|
|
721
|
+
},
|
|
722
|
+
set(target, prop, value) {
|
|
723
|
+
target[prop] = value;
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/next/server/adapter.ts
|
|
731
|
+
/**
|
|
732
|
+
* Creates a Next.js-specific RequestContext that reads cookies and headers
|
|
733
|
+
* from next/headers and handles cookie setting.
|
|
734
|
+
*/
|
|
735
|
+
async function createNextRequestContext() {
|
|
736
|
+
const cookieStore = await cookies();
|
|
737
|
+
const headerStore = await headers();
|
|
738
|
+
return {
|
|
739
|
+
getCookies() {
|
|
740
|
+
return extractNeonAuthCookies(headerStore);
|
|
741
|
+
},
|
|
742
|
+
setCookie(name, value, options) {
|
|
743
|
+
cookieStore.set(name, value, options);
|
|
744
|
+
},
|
|
745
|
+
getHeader(name) {
|
|
746
|
+
return headerStore.get(name) ?? null;
|
|
747
|
+
},
|
|
748
|
+
getOrigin() {
|
|
749
|
+
return headerStore.get("origin") || headerStore.get("referer")?.split("/").slice(0, 3).join("/") || "";
|
|
750
|
+
},
|
|
751
|
+
getFramework() {
|
|
752
|
+
return "nextjs";
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/server/config.ts
|
|
759
|
+
/**
|
|
760
|
+
* Framework-agnostic configuration types for Neon Auth
|
|
761
|
+
*/
|
|
762
|
+
/**
|
|
763
|
+
* Validates cookie configuration meets security requirements
|
|
764
|
+
* @param cookies - The cookie configuration to validate
|
|
765
|
+
* @throws Error if secret is too short (< 32 characters)
|
|
766
|
+
*/
|
|
767
|
+
function validateCookieConfig(cookies$1) {
|
|
768
|
+
if (!cookies$1.secret) throw new Error(ERRORS.MISSING_COOKIE_SECRET);
|
|
769
|
+
if (cookies$1.secret.length < 32) throw new Error(ERRORS.COOKIE_SECRET_TOO_SHORT);
|
|
770
|
+
if (cookies$1.sessionDataTtl !== void 0 && cookies$1.sessionDataTtl <= 0) throw new Error(ERRORS.INVALID_SESSION_DATA_TTL);
|
|
771
|
+
}
|
|
772
|
+
|
|
509
773
|
//#endregion
|
|
510
774
|
//#region src/server/proxy/request.ts
|
|
511
775
|
const PROXY_HEADERS = [
|
|
@@ -613,7 +877,7 @@ const RESPONSE_HEADERS_ALLOWLIST = [
|
|
|
613
877
|
*/
|
|
614
878
|
const handleAuthResponse = async (response, baseUrl, cookieConfig) => {
|
|
615
879
|
const responseHeaders = prepareResponseHeaders(response, cookieConfig.domain);
|
|
616
|
-
const sessionDataCookie = await
|
|
880
|
+
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, cookieConfig);
|
|
617
881
|
if (sessionDataCookie) responseHeaders.append("Set-Cookie", sessionDataCookie);
|
|
618
882
|
return new Response(response.body, {
|
|
619
883
|
status: response.status,
|
|
@@ -625,81 +889,21 @@ const prepareResponseHeaders = (response, domain) => {
|
|
|
625
889
|
const headers$1 = new Headers();
|
|
626
890
|
for (const header of RESPONSE_HEADERS_ALLOWLIST) if (header === "set-cookie") {
|
|
627
891
|
const cookies$1 = response.headers.getSetCookie();
|
|
628
|
-
for (const cookieHeader of cookies$1)
|
|
892
|
+
for (const cookieHeader of cookies$1) {
|
|
629
893
|
const parsedCookies = parseSetCookies(cookieHeader);
|
|
630
894
|
for (const parsedCookie of parsedCookies) {
|
|
631
|
-
parsedCookie.
|
|
895
|
+
parsedCookie.partitioned = void 0;
|
|
896
|
+
parsedCookie.sameSite = "lax";
|
|
897
|
+
if (domain) parsedCookie.domain = domain;
|
|
632
898
|
headers$1.append("Set-Cookie", serializeSetCookie(parsedCookie));
|
|
633
899
|
}
|
|
634
|
-
}
|
|
900
|
+
}
|
|
635
901
|
} else {
|
|
636
902
|
const value = response.headers.get(header);
|
|
637
903
|
if (value) headers$1.set(header, value);
|
|
638
904
|
}
|
|
639
905
|
return headers$1;
|
|
640
906
|
};
|
|
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
907
|
|
|
704
908
|
//#endregion
|
|
705
909
|
//#region src/server/session/cache-handler.ts
|
|
@@ -707,38 +911,68 @@ async function fetchSessionWithCookie(setCookieHeader, baseUrl) {
|
|
|
707
911
|
* Attempts to retrieve session data from cookie cache
|
|
708
912
|
* Returns Response with session data if cache hit, null otherwise
|
|
709
913
|
*
|
|
914
|
+
* If session_data cookie is missing or invalid, attempts to mint a new one
|
|
915
|
+
* from the session_token cookie (reactive minting).
|
|
916
|
+
*
|
|
710
917
|
* This is the framework-agnostic session cache optimization used by API handlers.
|
|
711
918
|
*
|
|
712
919
|
* @param request - Standard Web API Request object
|
|
713
|
-
* @param
|
|
920
|
+
* @param baseUrl - Auth server base URL for upstream calls
|
|
921
|
+
* @param cookieConfig - Cookie configuration (secret, TTL, domain)
|
|
714
922
|
* @returns Response with session data JSON if cache hit, null if miss/disabled
|
|
715
923
|
*/
|
|
716
|
-
async function trySessionCache(request,
|
|
924
|
+
async function trySessionCache(request, baseUrl, cookieConfig) {
|
|
717
925
|
if (new URL(request.url).searchParams.get("disableCookieCache") === "true") return null;
|
|
718
|
-
|
|
926
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
927
|
+
if (!cookieHeader.includes(NEON_AUTH_SESSION_COOKIE_NAME)) return null;
|
|
928
|
+
const hasSessionData = parseCookies(cookieHeader).has(NEON_AUTH_SESSION_DATA_COOKIE_NAME);
|
|
929
|
+
const mintAndReturn = async () => {
|
|
930
|
+
const sessionTokenCookie = extractSessionTokenCookie(cookieHeader);
|
|
931
|
+
if (!sessionTokenCookie) return null;
|
|
932
|
+
const sessionDataCookieString = await mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfig);
|
|
933
|
+
if (!sessionDataCookieString) return null;
|
|
934
|
+
try {
|
|
935
|
+
const sessionData = await fetchSessionWithCookie(sessionTokenCookie, baseUrl);
|
|
936
|
+
if (!sessionData.session) return null;
|
|
937
|
+
const response = Response.json(sessionData);
|
|
938
|
+
response.headers.set("Set-Cookie", sessionDataCookieString);
|
|
939
|
+
return response;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
console.error("[trySessionCache] Failed to fetch session after minting cookie:", error);
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
if (!hasSessionData) return await mintAndReturn();
|
|
719
946
|
try {
|
|
720
|
-
const sessionData = await getSessionDataFromCookie(request, NEON_AUTH_SESSION_DATA_COOKIE_NAME,
|
|
947
|
+
const sessionData = await getSessionDataFromCookie(request, NEON_AUTH_SESSION_DATA_COOKIE_NAME, cookieConfig.secret);
|
|
721
948
|
if (sessionData && sessionData.session) return Response.json(sessionData);
|
|
949
|
+
return await mintAndReturn();
|
|
722
950
|
} catch (error) {
|
|
723
951
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
724
952
|
const errorName = error instanceof Error ? error.name : "Unknown";
|
|
725
|
-
if (errorName === "JWTExpired") console.debug("[trySessionCache] Session cookie expired
|
|
953
|
+
if (errorName === "JWTExpired") console.debug("[trySessionCache] Session cookie expired, minting new one:", {
|
|
726
954
|
error: errorMessage,
|
|
727
|
-
errorType: errorName,
|
|
728
955
|
url: request.url
|
|
729
956
|
});
|
|
730
|
-
else if (errorName === "JWTInvalid" || errorName === "JWTClaimValidationFailed") console.warn("[trySessionCache] Invalid session cookie
|
|
957
|
+
else if (errorName === "JWTInvalid" || errorName === "JWTClaimValidationFailed") console.warn("[trySessionCache] Invalid session cookie, minting new one:", {
|
|
731
958
|
error: errorMessage,
|
|
732
|
-
errorType: errorName,
|
|
733
959
|
url: request.url
|
|
734
960
|
});
|
|
735
|
-
else console.error("[trySessionCache] Unexpected
|
|
961
|
+
else console.error("[trySessionCache] Unexpected validation error:", {
|
|
736
962
|
error: errorMessage,
|
|
737
|
-
errorType: errorName,
|
|
738
963
|
url: request.url
|
|
739
964
|
});
|
|
965
|
+
return await mintAndReturn();
|
|
740
966
|
}
|
|
741
|
-
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Extract session_token cookie value from cookie header
|
|
970
|
+
* @internal
|
|
971
|
+
*/
|
|
972
|
+
function extractSessionTokenCookie(cookieHeader) {
|
|
973
|
+
const sessionToken = parseCookies(cookieHeader).get(NEON_AUTH_SESSION_COOKIE_NAME);
|
|
974
|
+
if (!sessionToken) return null;
|
|
975
|
+
return `${NEON_AUTH_SESSION_COOKIE_NAME}=${sessionToken}`;
|
|
742
976
|
}
|
|
743
977
|
|
|
744
978
|
//#endregion
|
|
@@ -760,7 +994,11 @@ async function trySessionCache(request, cookieSecret) {
|
|
|
760
994
|
async function handleAuthProxyRequest(config) {
|
|
761
995
|
const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain } = config;
|
|
762
996
|
if (path === API_ENDPOINTS.getSession.path && request.method === API_ENDPOINTS.getSession.method) {
|
|
763
|
-
const cachedResponse = await trySessionCache(request,
|
|
997
|
+
const cachedResponse = await trySessionCache(request, baseUrl, {
|
|
998
|
+
secret: cookieSecret,
|
|
999
|
+
sessionDataTtl,
|
|
1000
|
+
domain
|
|
1001
|
+
});
|
|
764
1002
|
if (cachedResponse) return cachedResponse;
|
|
765
1003
|
}
|
|
766
1004
|
return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path), baseUrl, {
|
|
@@ -770,161 +1008,6 @@ async function handleAuthProxyRequest(config) {
|
|
|
770
1008
|
});
|
|
771
1009
|
}
|
|
772
1010
|
|
|
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
1011
|
//#endregion
|
|
929
1012
|
//#region src/next/server/handler.ts
|
|
930
1013
|
/**
|
|
@@ -1108,6 +1191,7 @@ async function processAuthMiddleware(config) {
|
|
|
1108
1191
|
session: null,
|
|
1109
1192
|
user: null
|
|
1110
1193
|
};
|
|
1194
|
+
let sessionCookies = [];
|
|
1111
1195
|
if (hasSessionToken) {
|
|
1112
1196
|
const sessionResponse = await handleAuthProxyRequest({
|
|
1113
1197
|
request,
|
|
@@ -1121,10 +1205,12 @@ async function processAuthMiddleware(config) {
|
|
|
1121
1205
|
const data = await sessionResponse.json().catch(() => null);
|
|
1122
1206
|
if (data) sessionData = data;
|
|
1123
1207
|
}
|
|
1208
|
+
sessionCookies = sessionResponse.headers.getSetCookie();
|
|
1124
1209
|
}
|
|
1125
1210
|
if (checkSessionRequired(pathname, skipRoutes, loginUrl, sessionData).allowed) return {
|
|
1126
1211
|
action: "allow",
|
|
1127
|
-
headers: { [NEON_AUTH_HEADER_MIDDLEWARE_NAME]: "true" }
|
|
1212
|
+
headers: { [NEON_AUTH_HEADER_MIDDLEWARE_NAME]: "true" },
|
|
1213
|
+
cookies: sessionCookies
|
|
1128
1214
|
};
|
|
1129
1215
|
const cookies$1 = [];
|
|
1130
1216
|
if (hasStaleSessionData) cookies$1.push(serializeSetCookie({
|
|
@@ -1199,7 +1285,9 @@ function neonAuthMiddleware(config) {
|
|
|
1199
1285
|
case "allow": {
|
|
1200
1286
|
const headers$1 = new Headers(request.headers);
|
|
1201
1287
|
if (result.headers) for (const [key, value] of Object.entries(result.headers)) headers$1.set(key, value);
|
|
1202
|
-
|
|
1288
|
+
const response = NextResponse.next({ request: { headers: headers$1 } });
|
|
1289
|
+
if (result.cookies) for (const cookie of result.cookies) response.headers.append("Set-Cookie", cookie);
|
|
1290
|
+
return response;
|
|
1203
1291
|
}
|
|
1204
1292
|
case "redirect_oauth": {
|
|
1205
1293
|
const oauthHeaders = new Headers();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../../adapter-core-
|
|
2
|
-
import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-
|
|
3
|
-
import "../../index-
|
|
1
|
+
import "../../adapter-core-B9uDhoYq.mjs";
|
|
2
|
+
import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-BO4jLN4H.mjs";
|
|
3
|
+
import "../../index-UW23fDSn.mjs";
|
|
4
4
|
export { BetterAuthReactAdapter, BetterAuthReactAdapterInstance, BetterAuthReactAdapterOptions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../../adapter-core-
|
|
2
|
-
import { t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-
|
|
1
|
+
import "../../adapter-core-D00qcqMo.mjs";
|
|
2
|
+
import { t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-Xdj-69i9.mjs";
|
|
3
3
|
|
|
4
4
|
export { BetterAuthReactAdapter };
|