@neondatabase/auth 0.3.0-beta → 0.4.1-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/README.md +22 -7
- package/codemods/migrate-auth-ui-imports.mjs +439 -0
- package/dist/{adapter-core-D00qcqMo.mjs → adapter-core-BFMM3lwe.mjs} +21 -11
- package/dist/{adapter-core-B9uDhoYq.d.mts → adapter-core-ClY-p_AI.d.mts} +325 -190
- package/dist/auth-interface-Clz-oWq1.d.mts +8 -0
- package/dist/better-auth-helpers-Bkezghej.mjs +541 -0
- package/dist/{better-auth-react-adapter-Xdj-69i9.mjs → better-auth-react-adapter-DZTZVVnk.mjs} +1 -1
- package/dist/{better-auth-react-adapter-BO4jLN4H.d.mts → better-auth-react-adapter-iJMZCLUI.d.mts} +388 -301
- package/dist/index.d.mts +5 -4
- package/dist/index.mjs +4 -3
- package/dist/{neon-auth-DBOB8sXF.mjs → neon-auth-VDrC3GwX.mjs} +1 -1
- package/dist/next/index.d.mts +144 -56
- package/dist/next/index.mjs +5 -4
- package/dist/next/server/index.d.mts +131 -14
- package/dist/next/server/index.mjs +402 -52
- package/dist/react/adapters/index.d.mts +3 -3
- package/dist/react/adapters/index.mjs +2 -2
- package/dist/react/index.d.mts +4 -4
- package/dist/react/index.mjs +2 -2
- package/dist/react/ui/index.d.mts +1 -1
- package/dist/{supabase-adapter-CIBMebXB.mjs → supabase-adapter-CAyBFrNn.mjs} +3 -514
- package/dist/{supabase-adapter-CSDRL1ZU.d.mts → supabase-adapter-cuLnmLDs.d.mts} +390 -303
- package/dist/types/index.d.mts +2 -2
- package/dist/vanilla/adapters/index.d.mts +4 -3
- package/dist/vanilla/adapters/index.mjs +2 -2
- package/dist/vanilla/index.d.mts +4 -3
- package/dist/vanilla/index.mjs +2 -2
- package/llms.txt +2 -2
- package/package.json +6 -2
- package/dist/constants-Cupc_bln.mjs +0 -28
- /package/dist/{index-CPnFzULh.d.mts → index-B0Pd4HOH.d.mts} +0 -0
- /package/dist/{index-UW23fDSn.d.mts → index-CzpoWrv9.d.mts} +0 -0
- /package/dist/{index-B_Q0Tp1D.d.mts → index-DHryUj7e.d.mts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as isAuthApiError, l as isAuthError, m as NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, o as AuthApiError, r as normalizeBetterAuthError, s as AuthError } from "../../better-auth-helpers-Bkezghej.mjs";
|
|
2
2
|
import { SignJWT, jwtVerify } from "jose";
|
|
3
3
|
import { parseCookies, parseSetCookieHeader } from "better-auth/cookies";
|
|
4
4
|
import { cookies, headers } from "next/headers";
|
|
@@ -54,6 +54,10 @@ const API_ENDPOINTS = {
|
|
|
54
54
|
emailOtp: {
|
|
55
55
|
path: "sign-in/email-otp",
|
|
56
56
|
method: "POST"
|
|
57
|
+
},
|
|
58
|
+
magicLink: {
|
|
59
|
+
path: "sign-in/magic-link",
|
|
60
|
+
method: "POST"
|
|
57
61
|
}
|
|
58
62
|
},
|
|
59
63
|
signUp: { email: {
|
|
@@ -273,7 +277,11 @@ const API_ENDPOINTS = {
|
|
|
273
277
|
path: "email-otp/passcode",
|
|
274
278
|
method: "POST"
|
|
275
279
|
}
|
|
276
|
-
}
|
|
280
|
+
},
|
|
281
|
+
magicLink: { verify: {
|
|
282
|
+
path: "magic-link/verify",
|
|
283
|
+
method: "GET"
|
|
284
|
+
} }
|
|
277
285
|
};
|
|
278
286
|
|
|
279
287
|
//#endregion
|
|
@@ -558,7 +566,7 @@ async function mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig)
|
|
|
558
566
|
domain: cookieConfig.domain,
|
|
559
567
|
httpOnly: true,
|
|
560
568
|
secure: true,
|
|
561
|
-
sameSite: "
|
|
569
|
+
sameSite: cookieConfig.sameSite ?? "strict",
|
|
562
570
|
maxAge
|
|
563
571
|
});
|
|
564
572
|
} catch (error) {
|
|
@@ -593,7 +601,7 @@ async function mintSessionDataFromResponse(responseHeaders, baseUrl, cookieConfi
|
|
|
593
601
|
domain: cookieConfig.domain,
|
|
594
602
|
httpOnly: true,
|
|
595
603
|
secure: true,
|
|
596
|
-
sameSite: "
|
|
604
|
+
sameSite: cookieConfig.sameSite ?? "strict",
|
|
597
605
|
maxAge: 0
|
|
598
606
|
});
|
|
599
607
|
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig);
|
|
@@ -613,10 +621,138 @@ async function mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfi
|
|
|
613
621
|
return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig);
|
|
614
622
|
}
|
|
615
623
|
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/server/network-error.ts
|
|
626
|
+
/**
|
|
627
|
+
* Classifies failed upstream `fetch` calls for clearer client responses and logs.
|
|
628
|
+
*/
|
|
629
|
+
/**
|
|
630
|
+
* Semver-stable transport error codes surfaced as JSON `code` on synthetic HTTP responses
|
|
631
|
+
* (for example 502 from the proxy) and in logs. Treat adding values as non-breaking;
|
|
632
|
+
* renaming or removing values is a breaking change.
|
|
633
|
+
*/
|
|
634
|
+
const NEON_AUTH_NETWORK_ERROR_CODES = [
|
|
635
|
+
"NETWORK_ERROR",
|
|
636
|
+
"NETWORK_DNS",
|
|
637
|
+
"NETWORK_REFUSED",
|
|
638
|
+
"NETWORK_TIMEOUT",
|
|
639
|
+
"NETWORK_TLS",
|
|
640
|
+
"NETWORK_RESET",
|
|
641
|
+
"NETWORK_ABORT"
|
|
642
|
+
];
|
|
643
|
+
function readErrnoCode(node) {
|
|
644
|
+
if (!node || typeof node !== "object") return void 0;
|
|
645
|
+
const code = node.code;
|
|
646
|
+
return typeof code === "string" ? code : void 0;
|
|
647
|
+
}
|
|
648
|
+
function isAbortError(node) {
|
|
649
|
+
if (node instanceof Error && node.name === "AbortError") return true;
|
|
650
|
+
if (typeof node === "object" && node !== null && "name" in node && node.name === "AbortError") return true;
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
function isTlsRelated(code, message) {
|
|
654
|
+
if (!code && !message) return false;
|
|
655
|
+
const c = code ?? "";
|
|
656
|
+
if (c.startsWith("ERR_TLS") || c.startsWith("CERT_") || c === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return true;
|
|
657
|
+
const m = message.toLowerCase();
|
|
658
|
+
return m.includes("certificate") || m.includes("ssl") || m.includes("tls") || m.includes("wrong version number");
|
|
659
|
+
}
|
|
660
|
+
function isLegacyFetchTypeError(node) {
|
|
661
|
+
return node instanceof TypeError && typeof node.message === "string" && node.message.includes("fetch");
|
|
662
|
+
}
|
|
663
|
+
function collectRelatedErrors(error) {
|
|
664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
665
|
+
const list = [];
|
|
666
|
+
function add(e) {
|
|
667
|
+
if (e === void 0 || e === null) return;
|
|
668
|
+
if (typeof e === "object") {
|
|
669
|
+
if (seen.has(e)) return;
|
|
670
|
+
seen.add(e);
|
|
671
|
+
}
|
|
672
|
+
list.push(e);
|
|
673
|
+
if (e instanceof Error && e.cause !== void 0) add(e.cause);
|
|
674
|
+
if (e instanceof AggregateError && Array.isArray(e.errors)) for (const sub of e.errors) add(sub);
|
|
675
|
+
}
|
|
676
|
+
add(error);
|
|
677
|
+
return list;
|
|
678
|
+
}
|
|
679
|
+
function clientMessageFor(code) {
|
|
680
|
+
switch (code) {
|
|
681
|
+
case "NETWORK_DNS": return "Could not resolve authentication server hostname";
|
|
682
|
+
case "NETWORK_REFUSED": return "Connection refused by authentication server";
|
|
683
|
+
case "NETWORK_TIMEOUT": return "Authentication server connection timed out";
|
|
684
|
+
case "NETWORK_TLS": return "TLS error connecting to authentication server";
|
|
685
|
+
case "NETWORK_RESET": return "Connection to authentication server was reset";
|
|
686
|
+
case "NETWORK_ABORT": return "Authentication request was aborted";
|
|
687
|
+
default: return "Unable to connect to authentication server";
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const INTERNAL_PUBLIC_MESSAGE = "Internal Server Error";
|
|
691
|
+
/**
|
|
692
|
+
* Inspects an error from `fetch` (including `cause` and aggregate errors) and
|
|
693
|
+
* returns a stable code for responses and observability.
|
|
694
|
+
*/
|
|
695
|
+
function classifyFetchFailure(error) {
|
|
696
|
+
const nodes = collectRelatedErrors(error);
|
|
697
|
+
for (const node of nodes) if (isAbortError(node)) return {
|
|
698
|
+
kind: "transport",
|
|
699
|
+
code: "NETWORK_ABORT",
|
|
700
|
+
detail: "AbortError",
|
|
701
|
+
clientMessage: clientMessageFor("NETWORK_ABORT")
|
|
702
|
+
};
|
|
703
|
+
for (const node of nodes) {
|
|
704
|
+
const code = readErrnoCode(node);
|
|
705
|
+
const message = node instanceof Error ? node.message : "";
|
|
706
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return {
|
|
707
|
+
kind: "transport",
|
|
708
|
+
code: "NETWORK_DNS",
|
|
709
|
+
detail: code,
|
|
710
|
+
clientMessage: clientMessageFor("NETWORK_DNS")
|
|
711
|
+
};
|
|
712
|
+
if (code === "ECONNREFUSED") return {
|
|
713
|
+
kind: "transport",
|
|
714
|
+
code: "NETWORK_REFUSED",
|
|
715
|
+
detail: code,
|
|
716
|
+
clientMessage: clientMessageFor("NETWORK_REFUSED")
|
|
717
|
+
};
|
|
718
|
+
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") return {
|
|
719
|
+
kind: "transport",
|
|
720
|
+
code: "NETWORK_TIMEOUT",
|
|
721
|
+
detail: code ?? "timeout",
|
|
722
|
+
clientMessage: clientMessageFor("NETWORK_TIMEOUT")
|
|
723
|
+
};
|
|
724
|
+
if (code === "ECONNRESET" || code === "EPIPE" || code === "ECONNABORTED") return {
|
|
725
|
+
kind: "transport",
|
|
726
|
+
code: "NETWORK_RESET",
|
|
727
|
+
detail: code,
|
|
728
|
+
clientMessage: clientMessageFor("NETWORK_RESET")
|
|
729
|
+
};
|
|
730
|
+
if (isTlsRelated(code, message)) return {
|
|
731
|
+
kind: "transport",
|
|
732
|
+
code: "NETWORK_TLS",
|
|
733
|
+
detail: code ?? "tls",
|
|
734
|
+
clientMessage: clientMessageFor("NETWORK_TLS")
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
for (const node of nodes) if (isLegacyFetchTypeError(node)) return {
|
|
738
|
+
kind: "transport",
|
|
739
|
+
code: "NETWORK_ERROR",
|
|
740
|
+
detail: "fetch TypeError",
|
|
741
|
+
clientMessage: clientMessageFor("NETWORK_ERROR")
|
|
742
|
+
};
|
|
743
|
+
const head = nodes[0];
|
|
744
|
+
return {
|
|
745
|
+
kind: "internal",
|
|
746
|
+
detail: (head instanceof Error ? head.message : INTERNAL_PUBLIC_MESSAGE).slice(0, 200),
|
|
747
|
+
clientMessage: INTERNAL_PUBLIC_MESSAGE
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
616
751
|
//#endregion
|
|
617
752
|
//#region src/server/client-factory.ts
|
|
618
753
|
function createAuthServerInternal(config) {
|
|
619
|
-
const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain } = config;
|
|
754
|
+
const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain, sameSite, log } = config;
|
|
755
|
+
const effectiveSameSite = sameSite ?? "strict";
|
|
620
756
|
const fetchWithAuth = async (path, method, args) => {
|
|
621
757
|
const ctx = await getContext();
|
|
622
758
|
const cookies$1 = await ctx.getCookies();
|
|
@@ -638,10 +774,61 @@ function createAuthServerInternal(config) {
|
|
|
638
774
|
headers$1["Content-Type"] = "application/json";
|
|
639
775
|
requestBody = JSON.stringify(Object.keys(body).length > 0 ? body : {});
|
|
640
776
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
777
|
+
let response;
|
|
778
|
+
try {
|
|
779
|
+
response = await fetch(url.toString(), {
|
|
780
|
+
method,
|
|
781
|
+
headers: headers$1,
|
|
782
|
+
body: requestBody
|
|
783
|
+
});
|
|
784
|
+
} catch (error) {
|
|
785
|
+
const classified = classifyFetchFailure(error);
|
|
786
|
+
if (classified.kind === "transport") {
|
|
787
|
+
log?.warn("[neon-auth] Server API upstream fetch failed", {
|
|
788
|
+
component: "server-api",
|
|
789
|
+
path: url.pathname,
|
|
790
|
+
code: classified.code,
|
|
791
|
+
detail: classified.detail,
|
|
792
|
+
host: url.host
|
|
793
|
+
});
|
|
794
|
+
return {
|
|
795
|
+
data: null,
|
|
796
|
+
error: {
|
|
797
|
+
message: classified.clientMessage,
|
|
798
|
+
status: 502,
|
|
799
|
+
statusText: "Bad Gateway",
|
|
800
|
+
code: classified.code
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
log?.error("[neon-auth] Server API unexpected fetch error", {
|
|
805
|
+
component: "server-api",
|
|
806
|
+
path: url.pathname,
|
|
807
|
+
detail: classified.detail,
|
|
808
|
+
host: url.host,
|
|
809
|
+
err: error
|
|
810
|
+
});
|
|
811
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
812
|
+
}
|
|
813
|
+
if (response.ok) log?.debug("[neon-auth] Server API upstream fetch completed", {
|
|
814
|
+
component: "server-api",
|
|
815
|
+
path: url.pathname,
|
|
816
|
+
status: response.status,
|
|
817
|
+
host: url.host
|
|
818
|
+
});
|
|
819
|
+
else if (response.status >= 500) log?.warn("[neon-auth] Server API upstream HTTP error", {
|
|
820
|
+
component: "server-api",
|
|
821
|
+
path: url.pathname,
|
|
822
|
+
status: response.status,
|
|
823
|
+
statusText: response.statusText,
|
|
824
|
+
host: url.host
|
|
825
|
+
});
|
|
826
|
+
else log?.info("[neon-auth] Server API upstream HTTP non-2xx", {
|
|
827
|
+
component: "server-api",
|
|
828
|
+
path: url.pathname,
|
|
829
|
+
status: response.status,
|
|
830
|
+
statusText: response.statusText,
|
|
831
|
+
host: url.host
|
|
645
832
|
});
|
|
646
833
|
const setCookieHeaders = response.headers.getSetCookie();
|
|
647
834
|
if (setCookieHeaders.length > 0) {
|
|
@@ -652,7 +839,7 @@ function createAuthServerInternal(config) {
|
|
|
652
839
|
...cookie,
|
|
653
840
|
domain,
|
|
654
841
|
partitioned: void 0,
|
|
655
|
-
sameSite:
|
|
842
|
+
sameSite: effectiveSameSite
|
|
656
843
|
};
|
|
657
844
|
await ctx.setCookie(cookie.name, cookie.value, cookieOptions);
|
|
658
845
|
}
|
|
@@ -661,25 +848,40 @@ function createAuthServerInternal(config) {
|
|
|
661
848
|
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, {
|
|
662
849
|
secret: cookieSecret,
|
|
663
850
|
sessionDataTtl,
|
|
664
|
-
domain
|
|
851
|
+
domain,
|
|
852
|
+
sameSite
|
|
665
853
|
});
|
|
666
854
|
if (sessionDataCookie) {
|
|
667
855
|
const [parsedSessionData] = parseSetCookies(sessionDataCookie);
|
|
668
856
|
if (parsedSessionData) await ctx.setCookie(parsedSessionData.name, parsedSessionData.value, parsedSessionData);
|
|
669
857
|
}
|
|
670
858
|
} catch (error) {
|
|
671
|
-
|
|
859
|
+
log?.warn("[neon-auth] Failed to mint session data cookie", {
|
|
860
|
+
component: "server-api",
|
|
861
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
862
|
+
err: error
|
|
863
|
+
});
|
|
672
864
|
}
|
|
673
865
|
}
|
|
674
866
|
const responseData = await response.json().catch(() => null);
|
|
675
|
-
if (!response.ok)
|
|
676
|
-
|
|
677
|
-
error: {
|
|
678
|
-
message: responseData?.message || response.statusText,
|
|
867
|
+
if (!response.ok) {
|
|
868
|
+
const normalized = normalizeBetterAuthError({
|
|
679
869
|
status: response.status,
|
|
680
|
-
statusText: response.statusText
|
|
681
|
-
|
|
682
|
-
|
|
870
|
+
statusText: response.statusText,
|
|
871
|
+
message: responseData?.message || response.statusText,
|
|
872
|
+
code: responseData?.code,
|
|
873
|
+
body: responseData
|
|
874
|
+
});
|
|
875
|
+
return {
|
|
876
|
+
data: null,
|
|
877
|
+
error: {
|
|
878
|
+
message: normalized.message,
|
|
879
|
+
status: normalized.status ?? response.status,
|
|
880
|
+
statusText: response.statusText,
|
|
881
|
+
code: normalized.code
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
683
885
|
return {
|
|
684
886
|
data: responseData,
|
|
685
887
|
error: null
|
|
@@ -701,7 +903,11 @@ function createAuthServerInternal(config) {
|
|
|
701
903
|
};
|
|
702
904
|
}
|
|
703
905
|
} catch (error) {
|
|
704
|
-
|
|
906
|
+
log?.warn("[neon-auth] Cookie validation error before getSession upstream call", {
|
|
907
|
+
component: "server-api",
|
|
908
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
909
|
+
err: error
|
|
910
|
+
});
|
|
705
911
|
}
|
|
706
912
|
return originalGetSession(...args);
|
|
707
913
|
};
|
|
@@ -726,6 +932,70 @@ function createApiProxy(endpoints, fetchFn) {
|
|
|
726
932
|
});
|
|
727
933
|
}
|
|
728
934
|
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/server/logger.ts
|
|
937
|
+
const LEVEL_RANK = {
|
|
938
|
+
error: 0,
|
|
939
|
+
warn: 1,
|
|
940
|
+
info: 2,
|
|
941
|
+
debug: 3
|
|
942
|
+
};
|
|
943
|
+
const consoleSink = {
|
|
944
|
+
error: (message, meta) => {
|
|
945
|
+
if (meta && Object.keys(meta).length > 0) console.error(message, meta);
|
|
946
|
+
else console.error(message);
|
|
947
|
+
},
|
|
948
|
+
warn: (message, meta) => {
|
|
949
|
+
if (meta && Object.keys(meta).length > 0) console.warn(message, meta);
|
|
950
|
+
else console.warn(message);
|
|
951
|
+
},
|
|
952
|
+
info: (message, meta) => {
|
|
953
|
+
if (meta && Object.keys(meta).length > 0) console.info(message, meta);
|
|
954
|
+
else console.info(message);
|
|
955
|
+
},
|
|
956
|
+
debug: (message, meta) => {
|
|
957
|
+
if (meta && Object.keys(meta).length > 0) console.debug(message, meta);
|
|
958
|
+
else console.debug(message);
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
function wrapWithLevel(level, logger) {
|
|
962
|
+
const minRank = LEVEL_RANK[level];
|
|
963
|
+
const gate = (messageLevel, fn) => {
|
|
964
|
+
return (message, meta) => {
|
|
965
|
+
if (LEVEL_RANK[messageLevel] <= minRank) fn(message, meta);
|
|
966
|
+
};
|
|
967
|
+
};
|
|
968
|
+
return {
|
|
969
|
+
error: gate("error", logger.error),
|
|
970
|
+
warn: gate("warn", logger.warn),
|
|
971
|
+
info: gate("info", logger.info),
|
|
972
|
+
debug: gate("debug", logger.debug)
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const noopResolved = {
|
|
976
|
+
error: () => {},
|
|
977
|
+
warn: () => {},
|
|
978
|
+
info: () => {},
|
|
979
|
+
debug: () => {}
|
|
980
|
+
};
|
|
981
|
+
/**
|
|
982
|
+
* Merges user logger with `console`, applies {@link NeonAuthLoggingInput} level rules.
|
|
983
|
+
*
|
|
984
|
+
* **Opt-out:** Defaults to `warn` (structured `error` / `warn` to `console`). Set **`logLevel: 'silent'`**
|
|
985
|
+
* to disable completely. Custom **`logger`** overrides `console` per level when not silent.
|
|
986
|
+
*/
|
|
987
|
+
function resolveNeonAuthLogging(input) {
|
|
988
|
+
if (input?.logLevel === "silent") return noopResolved;
|
|
989
|
+
const level = input?.logLevel ?? "warn";
|
|
990
|
+
const raw = input?.logger ?? {};
|
|
991
|
+
return wrapWithLevel(level, {
|
|
992
|
+
error: raw.error ?? consoleSink.error,
|
|
993
|
+
warn: raw.warn ?? consoleSink.warn,
|
|
994
|
+
info: raw.info ?? consoleSink.info,
|
|
995
|
+
debug: raw.debug ?? consoleSink.debug
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
729
999
|
//#endregion
|
|
730
1000
|
//#region src/next/server/adapter.ts
|
|
731
1001
|
/**
|
|
@@ -783,36 +1053,80 @@ const PROXY_HEADERS = [
|
|
|
783
1053
|
* This is framework-agnostic and can be used by any server framework
|
|
784
1054
|
*/
|
|
785
1055
|
const NEON_AUTH_HEADER_MIDDLEWARE_NAME = "x-neon-auth-middleware";
|
|
1056
|
+
function safeAuthHost(baseUrl) {
|
|
1057
|
+
try {
|
|
1058
|
+
return new URL(baseUrl).host;
|
|
1059
|
+
} catch {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
786
1063
|
/**
|
|
787
1064
|
* Handles proxying authentication requests to the upstream Neon Auth server
|
|
788
1065
|
*
|
|
789
1066
|
* @param baseUrl - Base URL of the Neon Auth server
|
|
790
1067
|
* @param request - Standard Web API Request object
|
|
791
1068
|
* @param path - API path to proxy to (e.g., 'get-session', 'sign-in')
|
|
1069
|
+
* @param log - Optional resolved logging sink
|
|
792
1070
|
* @returns Response from upstream server or error response
|
|
793
1071
|
*/
|
|
794
|
-
const handleAuthRequest = async (baseUrl, request, path) => {
|
|
1072
|
+
const handleAuthRequest = async (baseUrl, request, path, log) => {
|
|
795
1073
|
const headers$1 = prepareRequestHeaders(request);
|
|
796
1074
|
const body = await parseRequestBody(request);
|
|
797
1075
|
try {
|
|
798
1076
|
const upstreamURL = getUpstreamURL(baseUrl, path, { originalUrl: new URL(request.url) });
|
|
799
|
-
|
|
1077
|
+
const response = await fetch(upstreamURL.toString(), {
|
|
800
1078
|
method: request.method,
|
|
801
1079
|
headers: headers$1,
|
|
802
1080
|
body
|
|
803
1081
|
});
|
|
1082
|
+
const host = safeAuthHost(baseUrl);
|
|
1083
|
+
if (response.ok) log?.debug("[neon-auth] Upstream fetch completed", {
|
|
1084
|
+
component: "proxy",
|
|
1085
|
+
proxyPath: path,
|
|
1086
|
+
status: response.status,
|
|
1087
|
+
host
|
|
1088
|
+
});
|
|
1089
|
+
else if (response.status >= 500) log?.warn("[neon-auth] Upstream HTTP error", {
|
|
1090
|
+
component: "proxy",
|
|
1091
|
+
proxyPath: path,
|
|
1092
|
+
status: response.status,
|
|
1093
|
+
statusText: response.statusText,
|
|
1094
|
+
host
|
|
1095
|
+
});
|
|
1096
|
+
else log?.info("[neon-auth] Upstream HTTP non-2xx", {
|
|
1097
|
+
component: "proxy",
|
|
1098
|
+
proxyPath: path,
|
|
1099
|
+
status: response.status,
|
|
1100
|
+
statusText: response.statusText,
|
|
1101
|
+
host
|
|
1102
|
+
});
|
|
1103
|
+
return response;
|
|
804
1104
|
} catch (error) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1105
|
+
const classified = classifyFetchFailure(error);
|
|
1106
|
+
if (classified.kind === "transport") {
|
|
1107
|
+
log?.warn("[neon-auth] Upstream fetch failed", {
|
|
1108
|
+
component: "proxy",
|
|
1109
|
+
proxyPath: path,
|
|
1110
|
+
code: classified.code,
|
|
1111
|
+
detail: classified.detail,
|
|
1112
|
+
host: safeAuthHost(baseUrl)
|
|
1113
|
+
});
|
|
1114
|
+
return Response.json({
|
|
1115
|
+
error: classified.clientMessage,
|
|
1116
|
+
code: classified.code
|
|
1117
|
+
}, {
|
|
1118
|
+
status: 502,
|
|
1119
|
+
headers: { "Content-Type": "application/json" }
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
log?.error("[neon-auth] Unexpected proxy error", {
|
|
1123
|
+
component: "proxy",
|
|
1124
|
+
proxyPath: path,
|
|
1125
|
+
detail: classified.detail,
|
|
1126
|
+
host: safeAuthHost(baseUrl)
|
|
811
1127
|
});
|
|
812
|
-
const message = error instanceof Error ? error.message : "Internal Server Error";
|
|
813
|
-
console.error(`[AuthError] ${message}`, error);
|
|
814
1128
|
return Response.json({
|
|
815
|
-
error:
|
|
1129
|
+
error: classified.clientMessage,
|
|
816
1130
|
code: "INTERNAL_ERROR"
|
|
817
1131
|
}, {
|
|
818
1132
|
status: 500,
|
|
@@ -876,7 +1190,7 @@ const RESPONSE_HEADERS_ALLOWLIST = [
|
|
|
876
1190
|
* @returns New Response with proxied headers and session data cookie
|
|
877
1191
|
*/
|
|
878
1192
|
const handleAuthResponse = async (response, baseUrl, cookieConfig) => {
|
|
879
|
-
const responseHeaders = prepareResponseHeaders(response, cookieConfig
|
|
1193
|
+
const responseHeaders = prepareResponseHeaders(response, cookieConfig);
|
|
880
1194
|
const sessionDataCookie = await mintSessionDataFromResponse(response.headers, baseUrl, cookieConfig);
|
|
881
1195
|
if (sessionDataCookie) responseHeaders.append("Set-Cookie", sessionDataCookie);
|
|
882
1196
|
return new Response(response.body, {
|
|
@@ -885,15 +1199,17 @@ const handleAuthResponse = async (response, baseUrl, cookieConfig) => {
|
|
|
885
1199
|
headers: responseHeaders
|
|
886
1200
|
});
|
|
887
1201
|
};
|
|
888
|
-
const prepareResponseHeaders = (response,
|
|
1202
|
+
const prepareResponseHeaders = (response, cookieConfig) => {
|
|
889
1203
|
const headers$1 = new Headers();
|
|
1204
|
+
const effectiveSameSite = cookieConfig.sameSite ?? "strict";
|
|
1205
|
+
const { domain } = cookieConfig;
|
|
890
1206
|
for (const header of RESPONSE_HEADERS_ALLOWLIST) if (header === "set-cookie") {
|
|
891
1207
|
const cookies$1 = response.headers.getSetCookie();
|
|
892
1208
|
for (const cookieHeader of cookies$1) {
|
|
893
1209
|
const parsedCookies = parseSetCookies(cookieHeader);
|
|
894
1210
|
for (const parsedCookie of parsedCookies) {
|
|
895
1211
|
parsedCookie.partitioned = void 0;
|
|
896
|
-
parsedCookie.sameSite =
|
|
1212
|
+
parsedCookie.sameSite = effectiveSameSite;
|
|
897
1213
|
if (domain) parsedCookie.domain = domain;
|
|
898
1214
|
headers$1.append("Set-Cookie", serializeSetCookie(parsedCookie));
|
|
899
1215
|
}
|
|
@@ -992,19 +1308,21 @@ function extractSessionTokenCookie(cookieHeader) {
|
|
|
992
1308
|
* @returns Standard Web API Response
|
|
993
1309
|
*/
|
|
994
1310
|
async function handleAuthProxyRequest(config) {
|
|
995
|
-
const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain } = config;
|
|
1311
|
+
const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log } = config;
|
|
996
1312
|
if (path === API_ENDPOINTS.getSession.path && request.method === API_ENDPOINTS.getSession.method) {
|
|
997
1313
|
const cachedResponse = await trySessionCache(request, baseUrl, {
|
|
998
1314
|
secret: cookieSecret,
|
|
999
1315
|
sessionDataTtl,
|
|
1000
|
-
domain
|
|
1316
|
+
domain,
|
|
1317
|
+
sameSite
|
|
1001
1318
|
});
|
|
1002
1319
|
if (cachedResponse) return cachedResponse;
|
|
1003
1320
|
}
|
|
1004
|
-
return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path), baseUrl, {
|
|
1321
|
+
return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path, log), baseUrl, {
|
|
1005
1322
|
secret: cookieSecret,
|
|
1006
1323
|
sessionDataTtl,
|
|
1007
|
-
domain
|
|
1324
|
+
domain,
|
|
1325
|
+
sameSite
|
|
1008
1326
|
});
|
|
1009
1327
|
}
|
|
1010
1328
|
|
|
@@ -1040,6 +1358,7 @@ async function handleAuthProxyRequest(config) {
|
|
|
1040
1358
|
function authApiHandler(config) {
|
|
1041
1359
|
const { baseUrl, cookies: cookies$1 } = config;
|
|
1042
1360
|
validateCookieConfig(cookies$1);
|
|
1361
|
+
const log = resolveNeonAuthLogging(config);
|
|
1043
1362
|
const handler = async (request, { params }) => {
|
|
1044
1363
|
return handleAuthProxyRequest({
|
|
1045
1364
|
request,
|
|
@@ -1047,7 +1366,9 @@ function authApiHandler(config) {
|
|
|
1047
1366
|
baseUrl,
|
|
1048
1367
|
cookieSecret: cookies$1.secret,
|
|
1049
1368
|
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1050
|
-
domain: cookies$1.domain
|
|
1369
|
+
domain: cookies$1.domain,
|
|
1370
|
+
sameSite: cookies$1.sameSite,
|
|
1371
|
+
log
|
|
1051
1372
|
});
|
|
1052
1373
|
};
|
|
1053
1374
|
return {
|
|
@@ -1084,9 +1405,11 @@ function needsSessionVerification(request) {
|
|
|
1084
1405
|
* @param cookieSecret - Secret for signing session cookies
|
|
1085
1406
|
* @param sessionDataTtl - Optional TTL for session data cache
|
|
1086
1407
|
* @param domain - Optional cookie domain
|
|
1408
|
+
* @param sameSite - SameSite for cookies set during exchange
|
|
1409
|
+
* @param log - Resolved Neon Auth logging sink (proxy / middleware)
|
|
1087
1410
|
* @returns Exchange result with redirect URL and cookies, or null if exchange not needed/failed
|
|
1088
1411
|
*/
|
|
1089
|
-
async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain) {
|
|
1412
|
+
async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log) {
|
|
1090
1413
|
const url = new URL(request.url);
|
|
1091
1414
|
const verifier = url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
1092
1415
|
const cookieHeader = request.headers.get("cookie");
|
|
@@ -1096,10 +1419,11 @@ async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl
|
|
|
1096
1419
|
const response = await handleAuthResponse(await handleAuthRequest(baseUrl, new Request(request.url, {
|
|
1097
1420
|
method: "GET",
|
|
1098
1421
|
headers: request.headers
|
|
1099
|
-
}), "get-session"), baseUrl, {
|
|
1422
|
+
}), "get-session", log), baseUrl, {
|
|
1100
1423
|
secret: cookieSecret,
|
|
1101
1424
|
sessionDataTtl,
|
|
1102
|
-
domain
|
|
1425
|
+
domain,
|
|
1426
|
+
sameSite
|
|
1103
1427
|
});
|
|
1104
1428
|
if (response.ok) {
|
|
1105
1429
|
const setCookieHeaders = response.headers.getSetCookie();
|
|
@@ -1174,10 +1498,15 @@ function checkSessionRequired(pathname, skipRoutes, loginUrl, session) {
|
|
|
1174
1498
|
* @returns Decision object indicating what action to take
|
|
1175
1499
|
*/
|
|
1176
1500
|
async function processAuthMiddleware(config) {
|
|
1177
|
-
const
|
|
1501
|
+
const log = config.log ?? resolveNeonAuthLogging({
|
|
1502
|
+
logger: config.logger,
|
|
1503
|
+
logLevel: config.logLevel
|
|
1504
|
+
});
|
|
1505
|
+
const { request, pathname, skipRoutes, loginUrl, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite } = config;
|
|
1506
|
+
const effectiveSameSite = sameSite ?? "strict";
|
|
1178
1507
|
if (pathname.startsWith(loginUrl)) return { action: "allow" };
|
|
1179
1508
|
if (needsSessionVerification(request)) {
|
|
1180
|
-
const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain);
|
|
1509
|
+
const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log);
|
|
1181
1510
|
if (exchangeResult !== null) return {
|
|
1182
1511
|
action: "redirect_oauth",
|
|
1183
1512
|
redirectUrl: exchangeResult.redirectUrl,
|
|
@@ -1199,10 +1528,18 @@ async function processAuthMiddleware(config) {
|
|
|
1199
1528
|
baseUrl,
|
|
1200
1529
|
cookieSecret,
|
|
1201
1530
|
sessionDataTtl,
|
|
1202
|
-
domain
|
|
1531
|
+
domain,
|
|
1532
|
+
sameSite,
|
|
1533
|
+
log
|
|
1203
1534
|
});
|
|
1204
1535
|
if (sessionResponse.ok) {
|
|
1205
|
-
const data = await sessionResponse.json().catch(() =>
|
|
1536
|
+
const data = await sessionResponse.json().catch((error) => {
|
|
1537
|
+
log.debug("[neon-auth] Failed to parse session response JSON", {
|
|
1538
|
+
component: "middleware",
|
|
1539
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1540
|
+
});
|
|
1541
|
+
return null;
|
|
1542
|
+
});
|
|
1206
1543
|
if (data) sessionData = data;
|
|
1207
1544
|
}
|
|
1208
1545
|
sessionCookies = sessionResponse.headers.getSetCookie();
|
|
@@ -1220,7 +1557,7 @@ async function processAuthMiddleware(config) {
|
|
|
1220
1557
|
domain,
|
|
1221
1558
|
httpOnly: true,
|
|
1222
1559
|
secure: true,
|
|
1223
|
-
sameSite:
|
|
1560
|
+
sameSite: effectiveSameSite,
|
|
1224
1561
|
maxAge: 0
|
|
1225
1562
|
}));
|
|
1226
1563
|
return {
|
|
@@ -1249,6 +1586,9 @@ const SKIP_ROUTES = [
|
|
|
1249
1586
|
* @param config.cookies - Cookie configuration
|
|
1250
1587
|
* @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
|
|
1251
1588
|
* @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
|
|
1589
|
+
* @param config.logger - Optional structured logger; omitted methods fall back to `console`
|
|
1590
|
+
* @param config.logLevel - Minimum log level; `'silent'` disables Neon Auth console output (default: `warn`)
|
|
1591
|
+
* @param config.log - Pre-resolved logging sink (set by {@link createNeonAuth})
|
|
1252
1592
|
* @param config.loginUrl - The URL to redirect to when the user is not authenticated (default: '/auth/sign-in')
|
|
1253
1593
|
* @returns A middleware function that can be used in the Next.js app.
|
|
1254
1594
|
* @throws Error if `cookies.secret` is less than 32 characters
|
|
@@ -1267,7 +1607,7 @@ const SKIP_ROUTES = [
|
|
|
1267
1607
|
* ```
|
|
1268
1608
|
*/
|
|
1269
1609
|
function neonAuthMiddleware(config) {
|
|
1270
|
-
const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in" } = config;
|
|
1610
|
+
const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in", log, logger, logLevel } = config;
|
|
1271
1611
|
validateCookieConfig(cookies$1);
|
|
1272
1612
|
return async (request) => {
|
|
1273
1613
|
const pathname = request.nextUrl.pathname;
|
|
@@ -1279,7 +1619,11 @@ function neonAuthMiddleware(config) {
|
|
|
1279
1619
|
baseUrl,
|
|
1280
1620
|
cookieSecret: cookies$1.secret,
|
|
1281
1621
|
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1282
|
-
domain: cookies$1.domain
|
|
1622
|
+
domain: cookies$1.domain,
|
|
1623
|
+
sameSite: cookies$1.sameSite,
|
|
1624
|
+
log,
|
|
1625
|
+
logger,
|
|
1626
|
+
logLevel
|
|
1283
1627
|
});
|
|
1284
1628
|
switch (result.action) {
|
|
1285
1629
|
case "allow": {
|
|
@@ -1330,6 +1674,8 @@ function neonAuthMiddleware(config) {
|
|
|
1330
1674
|
* @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
|
|
1331
1675
|
* @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
|
|
1332
1676
|
* @param config.cookies.domain - Optional cookie domain (default: current domain)
|
|
1677
|
+
* @param config.logger - Optional structured logger; omitted methods fall back to `console` (see {@link NeonAuthLogger})
|
|
1678
|
+
* @param config.logLevel - Minimum level; `'silent'` disables Neon Auth server console logs (default: `warn`)
|
|
1333
1679
|
* @returns Unified auth instance with server methods, handler, and middleware
|
|
1334
1680
|
* @throws Error if `cookies.secret` is less than 32 characters
|
|
1335
1681
|
*
|
|
@@ -1402,12 +1748,15 @@ function neonAuthMiddleware(config) {
|
|
|
1402
1748
|
function createNeonAuth(config) {
|
|
1403
1749
|
const { baseUrl, cookies: cookies$1 } = config;
|
|
1404
1750
|
validateCookieConfig(cookies$1);
|
|
1751
|
+
const log = resolveNeonAuthLogging(config);
|
|
1405
1752
|
const server = createAuthServerInternal({
|
|
1406
1753
|
baseUrl,
|
|
1407
1754
|
context: createNextRequestContext,
|
|
1408
1755
|
cookieSecret: cookies$1.secret,
|
|
1409
1756
|
sessionDataTtl: cookies$1.sessionDataTtl,
|
|
1410
|
-
domain: cookies$1.domain
|
|
1757
|
+
domain: cookies$1.domain,
|
|
1758
|
+
sameSite: cookies$1.sameSite,
|
|
1759
|
+
log
|
|
1411
1760
|
});
|
|
1412
1761
|
/**
|
|
1413
1762
|
* Creates API route handlers for Next.js
|
|
@@ -1452,10 +1801,11 @@ function createNeonAuth(config) {
|
|
|
1452
1801
|
*/
|
|
1453
1802
|
server.middleware = (middlewareConfig) => neonAuthMiddleware({
|
|
1454
1803
|
...config,
|
|
1455
|
-
...middlewareConfig
|
|
1804
|
+
...middlewareConfig,
|
|
1805
|
+
log
|
|
1456
1806
|
});
|
|
1457
1807
|
return server;
|
|
1458
1808
|
}
|
|
1459
1809
|
|
|
1460
1810
|
//#endregion
|
|
1461
|
-
export { createNeonAuth };
|
|
1811
|
+
export { AuthApiError, AuthError, NEON_AUTH_NETWORK_ERROR_CODES, classifyFetchFailure, createNeonAuth, isAuthApiError, isAuthError, resolveNeonAuthLogging };
|