@neondatabase/auth 0.4.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.
@@ -621,10 +621,137 @@ async function mintSessionDataFromToken(sessionTokenCookie, baseUrl, cookieConfi
621
621
  return await mintSessionDataCookie(sessionTokenCookie, baseUrl, cookieConfig);
622
622
  }
623
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
+
624
751
  //#endregion
625
752
  //#region src/server/client-factory.ts
626
753
  function createAuthServerInternal(config) {
627
- const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain, sameSite } = config;
754
+ const { baseUrl, context: getContext, cookieSecret, sessionDataTtl, domain, sameSite, log } = config;
628
755
  const effectiveSameSite = sameSite ?? "strict";
629
756
  const fetchWithAuth = async (path, method, args) => {
630
757
  const ctx = await getContext();
@@ -647,10 +774,61 @@ function createAuthServerInternal(config) {
647
774
  headers$1["Content-Type"] = "application/json";
648
775
  requestBody = JSON.stringify(Object.keys(body).length > 0 ? body : {});
649
776
  }
650
- const response = await fetch(url.toString(), {
651
- method,
652
- headers: headers$1,
653
- body: requestBody
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
654
832
  });
655
833
  const setCookieHeaders = response.headers.getSetCookie();
656
834
  if (setCookieHeaders.length > 0) {
@@ -678,7 +856,11 @@ function createAuthServerInternal(config) {
678
856
  if (parsedSessionData) await ctx.setCookie(parsedSessionData.name, parsedSessionData.value, parsedSessionData);
679
857
  }
680
858
  } catch (error) {
681
- console.error("[fetchWithAuth] Failed to mint session data cookie:", error);
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
+ });
682
864
  }
683
865
  }
684
866
  const responseData = await response.json().catch(() => null);
@@ -721,7 +903,11 @@ function createAuthServerInternal(config) {
721
903
  };
722
904
  }
723
905
  } catch (error) {
724
- console.error("[auth.getSession] Cookie validation error:", error);
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
+ });
725
911
  }
726
912
  return originalGetSession(...args);
727
913
  };
@@ -746,6 +932,70 @@ function createApiProxy(endpoints, fetchFn) {
746
932
  });
747
933
  }
748
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
+
749
999
  //#endregion
750
1000
  //#region src/next/server/adapter.ts
751
1001
  /**
@@ -803,36 +1053,80 @@ const PROXY_HEADERS = [
803
1053
  * This is framework-agnostic and can be used by any server framework
804
1054
  */
805
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
+ }
806
1063
  /**
807
1064
  * Handles proxying authentication requests to the upstream Neon Auth server
808
1065
  *
809
1066
  * @param baseUrl - Base URL of the Neon Auth server
810
1067
  * @param request - Standard Web API Request object
811
1068
  * @param path - API path to proxy to (e.g., 'get-session', 'sign-in')
1069
+ * @param log - Optional resolved logging sink
812
1070
  * @returns Response from upstream server or error response
813
1071
  */
814
- const handleAuthRequest = async (baseUrl, request, path) => {
1072
+ const handleAuthRequest = async (baseUrl, request, path, log) => {
815
1073
  const headers$1 = prepareRequestHeaders(request);
816
1074
  const body = await parseRequestBody(request);
817
1075
  try {
818
1076
  const upstreamURL = getUpstreamURL(baseUrl, path, { originalUrl: new URL(request.url) });
819
- return await fetch(upstreamURL.toString(), {
1077
+ const response = await fetch(upstreamURL.toString(), {
820
1078
  method: request.method,
821
1079
  headers: headers$1,
822
1080
  body
823
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;
824
1104
  } catch (error) {
825
- if (error instanceof Error && error.name === "TypeError" && error.message.includes("fetch")) return Response.json({
826
- error: "Unable to connect to authentication server",
827
- code: "NETWORK_ERROR"
828
- }, {
829
- status: 502,
830
- headers: { "Content-Type": "application/json" }
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)
831
1127
  });
832
- const message = error instanceof Error ? error.message : "Internal Server Error";
833
- console.error(`[AuthError] ${message}`, error);
834
1128
  return Response.json({
835
- error: message,
1129
+ error: classified.clientMessage,
836
1130
  code: "INTERNAL_ERROR"
837
1131
  }, {
838
1132
  status: 500,
@@ -1014,7 +1308,7 @@ function extractSessionTokenCookie(cookieHeader) {
1014
1308
  * @returns Standard Web API Response
1015
1309
  */
1016
1310
  async function handleAuthProxyRequest(config) {
1017
- const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite } = config;
1311
+ const { request, path, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log } = config;
1018
1312
  if (path === API_ENDPOINTS.getSession.path && request.method === API_ENDPOINTS.getSession.method) {
1019
1313
  const cachedResponse = await trySessionCache(request, baseUrl, {
1020
1314
  secret: cookieSecret,
@@ -1024,7 +1318,7 @@ async function handleAuthProxyRequest(config) {
1024
1318
  });
1025
1319
  if (cachedResponse) return cachedResponse;
1026
1320
  }
1027
- return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path), baseUrl, {
1321
+ return await handleAuthResponse(await handleAuthRequest(baseUrl, request, path, log), baseUrl, {
1028
1322
  secret: cookieSecret,
1029
1323
  sessionDataTtl,
1030
1324
  domain,
@@ -1064,6 +1358,7 @@ async function handleAuthProxyRequest(config) {
1064
1358
  function authApiHandler(config) {
1065
1359
  const { baseUrl, cookies: cookies$1 } = config;
1066
1360
  validateCookieConfig(cookies$1);
1361
+ const log = resolveNeonAuthLogging(config);
1067
1362
  const handler = async (request, { params }) => {
1068
1363
  return handleAuthProxyRequest({
1069
1364
  request,
@@ -1072,7 +1367,8 @@ function authApiHandler(config) {
1072
1367
  cookieSecret: cookies$1.secret,
1073
1368
  sessionDataTtl: cookies$1.sessionDataTtl,
1074
1369
  domain: cookies$1.domain,
1075
- sameSite: cookies$1.sameSite
1370
+ sameSite: cookies$1.sameSite,
1371
+ log
1076
1372
  });
1077
1373
  };
1078
1374
  return {
@@ -1109,9 +1405,11 @@ function needsSessionVerification(request) {
1109
1405
  * @param cookieSecret - Secret for signing session cookies
1110
1406
  * @param sessionDataTtl - Optional TTL for session data cache
1111
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)
1112
1410
  * @returns Exchange result with redirect URL and cookies, or null if exchange not needed/failed
1113
1411
  */
1114
- async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite) {
1412
+ async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log) {
1115
1413
  const url = new URL(request.url);
1116
1414
  const verifier = url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
1117
1415
  const cookieHeader = request.headers.get("cookie");
@@ -1121,7 +1419,7 @@ async function exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl
1121
1419
  const response = await handleAuthResponse(await handleAuthRequest(baseUrl, new Request(request.url, {
1122
1420
  method: "GET",
1123
1421
  headers: request.headers
1124
- }), "get-session"), baseUrl, {
1422
+ }), "get-session", log), baseUrl, {
1125
1423
  secret: cookieSecret,
1126
1424
  sessionDataTtl,
1127
1425
  domain,
@@ -1200,11 +1498,15 @@ function checkSessionRequired(pathname, skipRoutes, loginUrl, session) {
1200
1498
  * @returns Decision object indicating what action to take
1201
1499
  */
1202
1500
  async function processAuthMiddleware(config) {
1501
+ const log = config.log ?? resolveNeonAuthLogging({
1502
+ logger: config.logger,
1503
+ logLevel: config.logLevel
1504
+ });
1203
1505
  const { request, pathname, skipRoutes, loginUrl, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite } = config;
1204
1506
  const effectiveSameSite = sameSite ?? "strict";
1205
1507
  if (pathname.startsWith(loginUrl)) return { action: "allow" };
1206
1508
  if (needsSessionVerification(request)) {
1207
- const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite);
1509
+ const exchangeResult = await exchangeOAuthToken(request, baseUrl, cookieSecret, sessionDataTtl, domain, sameSite, log);
1208
1510
  if (exchangeResult !== null) return {
1209
1511
  action: "redirect_oauth",
1210
1512
  redirectUrl: exchangeResult.redirectUrl,
@@ -1227,10 +1529,17 @@ async function processAuthMiddleware(config) {
1227
1529
  cookieSecret,
1228
1530
  sessionDataTtl,
1229
1531
  domain,
1230
- sameSite
1532
+ sameSite,
1533
+ log
1231
1534
  });
1232
1535
  if (sessionResponse.ok) {
1233
- const data = await sessionResponse.json().catch(() => null);
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
+ });
1234
1543
  if (data) sessionData = data;
1235
1544
  }
1236
1545
  sessionCookies = sessionResponse.headers.getSetCookie();
@@ -1277,6 +1586,9 @@ const SKIP_ROUTES = [
1277
1586
  * @param config.cookies - Cookie configuration
1278
1587
  * @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
1279
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})
1280
1592
  * @param config.loginUrl - The URL to redirect to when the user is not authenticated (default: '/auth/sign-in')
1281
1593
  * @returns A middleware function that can be used in the Next.js app.
1282
1594
  * @throws Error if `cookies.secret` is less than 32 characters
@@ -1295,7 +1607,7 @@ const SKIP_ROUTES = [
1295
1607
  * ```
1296
1608
  */
1297
1609
  function neonAuthMiddleware(config) {
1298
- const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in" } = config;
1610
+ const { baseUrl, cookies: cookies$1, loginUrl = "/auth/sign-in", log, logger, logLevel } = config;
1299
1611
  validateCookieConfig(cookies$1);
1300
1612
  return async (request) => {
1301
1613
  const pathname = request.nextUrl.pathname;
@@ -1308,7 +1620,10 @@ function neonAuthMiddleware(config) {
1308
1620
  cookieSecret: cookies$1.secret,
1309
1621
  sessionDataTtl: cookies$1.sessionDataTtl,
1310
1622
  domain: cookies$1.domain,
1311
- sameSite: cookies$1.sameSite
1623
+ sameSite: cookies$1.sameSite,
1624
+ log,
1625
+ logger,
1626
+ logLevel
1312
1627
  });
1313
1628
  switch (result.action) {
1314
1629
  case "allow": {
@@ -1359,6 +1674,8 @@ function neonAuthMiddleware(config) {
1359
1674
  * @param config.cookies.secret - Secret for signing session cookies (minimum 32 characters)
1360
1675
  * @param config.cookies.sessionDataTtl - Optional TTL for session cache in seconds (default: 300)
1361
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`)
1362
1679
  * @returns Unified auth instance with server methods, handler, and middleware
1363
1680
  * @throws Error if `cookies.secret` is less than 32 characters
1364
1681
  *
@@ -1431,13 +1748,15 @@ function neonAuthMiddleware(config) {
1431
1748
  function createNeonAuth(config) {
1432
1749
  const { baseUrl, cookies: cookies$1 } = config;
1433
1750
  validateCookieConfig(cookies$1);
1751
+ const log = resolveNeonAuthLogging(config);
1434
1752
  const server = createAuthServerInternal({
1435
1753
  baseUrl,
1436
1754
  context: createNextRequestContext,
1437
1755
  cookieSecret: cookies$1.secret,
1438
1756
  sessionDataTtl: cookies$1.sessionDataTtl,
1439
1757
  domain: cookies$1.domain,
1440
- sameSite: cookies$1.sameSite
1758
+ sameSite: cookies$1.sameSite,
1759
+ log
1441
1760
  });
1442
1761
  /**
1443
1762
  * Creates API route handlers for Next.js
@@ -1482,10 +1801,11 @@ function createNeonAuth(config) {
1482
1801
  */
1483
1802
  server.middleware = (middlewareConfig) => neonAuthMiddleware({
1484
1803
  ...config,
1485
- ...middlewareConfig
1804
+ ...middlewareConfig,
1805
+ log
1486
1806
  });
1487
1807
  return server;
1488
1808
  }
1489
1809
 
1490
1810
  //#endregion
1491
- export { AuthApiError, AuthError, createNeonAuth, isAuthApiError, isAuthError };
1811
+ export { AuthApiError, AuthError, NEON_AUTH_NETWORK_ERROR_CODES, classifyFetchFailure, createNeonAuth, isAuthApiError, isAuthError, resolveNeonAuthLogging };
@@ -1,4 +1,4 @@
1
- import "../../adapter-core-BWM7cWOp.mjs";
2
- import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-BDxJ65mF.mjs";
1
+ import "../../adapter-core-ClY-p_AI.mjs";
2
+ import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-iJMZCLUI.mjs";
3
3
  import "../../index-B0Pd4HOH.mjs";
4
4
  export { BetterAuthReactAdapter, BetterAuthReactAdapterInstance, BetterAuthReactAdapterOptions };
@@ -1,4 +1,4 @@
1
- import "../../adapter-core-Bt4M5I2g.mjs";
2
- import { t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-aMv8WeDb.mjs";
1
+ import "../../adapter-core-BFMM3lwe.mjs";
2
+ import { t as BetterAuthReactAdapter } from "../../better-auth-react-adapter-DZTZVVnk.mjs";
3
3
 
4
4
  export { BetterAuthReactAdapter };
@@ -1,5 +1,5 @@
1
- import "../adapter-core-BWM7cWOp.mjs";
2
- import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../better-auth-react-adapter-BDxJ65mF.mjs";
1
+ import "../adapter-core-ClY-p_AI.mjs";
2
+ import { n as BetterAuthReactAdapterInstance, r as BetterAuthReactAdapterOptions, t as BetterAuthReactAdapter } from "../better-auth-react-adapter-iJMZCLUI.mjs";
3
3
  import "../index-B0Pd4HOH.mjs";
4
4
  import { $ as MagicLinkFormProps, $t as SignUpFormProps, A as ChangePasswordCard, An as UserViewClassNames, At as PasswordInput, B as DropboxIcon, Bn as socialProviders, Bt as ResetPasswordFormProps, C as AuthUIProviderProps, Cn as UserAvatarClassNames, Ct as OrganizationViewPageProps, D as AuthViewPaths, Dn as UserButtonProps, Dt as OrganizationsCard, E as AuthViewPath, En as UserButtonClassNames, Et as OrganizationViewProps, F as CreateTeamDialogProps, Fn as accountViewPaths, Ft as RecoverAccountFormProps, G as GitLabIcon, Gt as SettingsCard, H as ForgotPasswordForm, Hn as useAuthenticate, Ht as SecuritySettingsCards, I as DeleteAccountCard, In as authLocalization, It as RedditIcon, J as InputFieldSkeleton, Jt as SettingsCellSkeleton, K as GoogleIcon, Kt as SettingsCardClassNames, L as DeleteAccountCardProps, Ln as authViewPaths, Lt as RedirectToSignIn, M as CreateOrganizationDialog, Mn as VKIcon, Mt as ProvidersCard, N as CreateOrganizationDialogProps, Nn as XIcon, Nt as ProvidersCardProps, O as AuthViewProps, On as UserInvitationsCard, Ot as PasskeysCard, P as CreateTeamDialog, Pn as ZoomIcon, Pt as RecoverAccountForm, Q as MagicLinkForm, Qt as SignUpForm, R as DeleteOrganizationCard, Rn as getViewByPath, Rt as RedirectToSignUp, S as AuthUIProvider, Sn as UserAvatar, St as OrganizationViewClassNames, T as AuthViewClassNames, Tn as UserButton, Tt as OrganizationViewPaths, U as ForgotPasswordFormProps, Un as useCurrentOrganization, Ut as SessionsCard, V as FacebookIcon, Vn as useAuthData, Vt as RobloxIcon, W as GitHubIcon, Wn as useTheme, Wt as SessionsCardProps, X as LinearIcon, Xt as SignInFormProps, Y as KickIcon, Yt as SignInForm, Z as LinkedInIcon, Zt as SignOut, _ as AuthLoading, _n as UpdateAvatarCardProps, _t as OrganizationSlugCardProps, a as AccountViewPath, an as TeamCell, at as OrganizationInvitationsCard, b as AuthUIContext, bn as UpdateNameCard, bt as OrganizationSwitcherProps, c as AccountsCard, cn as TeamOptionsContext, ct as OrganizationLogoCardProps, d as AppleIcon, dn as TwitchIcon, dt as OrganizationMembersCard, en as SignedIn, et as MicrosoftIcon, f as AuthCallback, fn as TwoFactorCard, ft as OrganizationNameCard, g as AuthHooks, gn as UpdateAvatarCard, gt as OrganizationSlugCard, h as AuthFormProps, hn as TwoFactorFormProps, ht as OrganizationSettingsCardsProps, i as AccountView, in as Team, it as OrganizationCellView, j as ChangePasswordCardProps, jn as UserViewProps, jt as Provider, k as ChangeEmailCard, kn as UserView, kt as PasskeysCardProps, l as AccountsCardProps, ln as TeamsCard, lt as OrganizationLogoClassNames, m as AuthFormClassNames, mn as TwoFactorForm, mt as OrganizationSettingsCards, n as AcceptInvitationCardProps, nn as SlackIcon, nt as NeonAuthUIProviderProps, o as AccountViewPaths, on as TeamCellProps, ot as OrganizationLogo, p as AuthForm, pn as TwoFactorCardProps, pt as OrganizationNameCardProps, q as HuggingFaceIcon, qt as SettingsCardProps, r as AccountSettingsCards, rn as SpotifyIcon, rt as NotionIcon, s as AccountViewProps, sn as TeamOptions, st as OrganizationLogoCard, t as AcceptInvitationCard, tn as SignedOut, tt as NeonAuthUIProvider, u as ApiKeysCardProps, un as TikTokIcon, ut as OrganizationLogoProps, v as AuthLocalization, vn as UpdateFieldCard, vt as OrganizationSwitcher, w as AuthView, wn as UserAvatarProps, wt as OrganizationViewPath, x as AuthUIContextType, xn as UpdateUsernameCard, xt as OrganizationView, y as AuthMutators, yn as UpdateFieldCardProps, yt as OrganizationSwitcherClassNames, z as DiscordIcon, zn as organizationViewPaths, zt as ResetPasswordForm } from "../index-DHryUj7e.mjs";
5
5
  import { useStore } from "better-auth/react";
@@ -1,5 +1,5 @@
1
- import "../adapter-core-Bt4M5I2g.mjs";
2
- import { t as BetterAuthReactAdapter } from "../better-auth-react-adapter-aMv8WeDb.mjs";
1
+ import "../adapter-core-BFMM3lwe.mjs";
2
+ import { t as BetterAuthReactAdapter } from "../better-auth-react-adapter-DZTZVVnk.mjs";
3
3
  import { $ as RobloxIcon, A as MagicLinkForm, At as authViewPaths, B as OrganizationSettingsCards, C as GitLabIcon, Ct as UserInvitationsCard, D as KickIcon, Dt as ZoomIcon, E as InputFieldSkeleton, Et as XIcon, F as OrganizationInvitationsCard, Ft as useAuthenticate, G as PasskeysCard, H as OrganizationSwitcher, I as OrganizationLogo, It as useCurrentOrganization, J as RecoverAccountForm, K as PasswordInput, L as OrganizationLogoCard, Lt as useTheme, M as NeonAuthUIProvider, Mt as organizationViewPaths, N as NotionIcon, Nt as socialProviders, O as LinearIcon, Ot as accountViewPaths, P as OrganizationCellView, Pt as useAuthData, Q as ResetPasswordForm, R as OrganizationMembersCard, S as GitHubIcon, St as UserButton, T as HuggingFaceIcon, Tt as VKIcon, U as OrganizationView, V as OrganizationSlugCard, W as OrganizationsCard, X as RedirectToSignIn, Y as RedditIcon, Z as RedirectToSignUp, _ as DeleteOrganizationCard, _t as UpdateAvatarCard, a as AppleIcon, at as SignOut, b as FacebookIcon, bt as UpdateUsernameCard, c as AuthLoading, ct as SignedOut, d as AuthView, dt as TeamCell, et as SecuritySettingsCards, f as ChangeEmailCard, ft as TeamsCard, g as DeleteAccountCard, gt as TwoFactorForm, h as CreateTeamDialog, ht as TwoFactorCard, i as AccountsCard, it as SignInForm, j as MicrosoftIcon, jt as getViewByPath, k as LinkedInIcon, kt as authLocalization, l as AuthUIContext, lt as SlackIcon, m as CreateOrganizationDialog, mt as TwitchIcon, n as AccountSettingsCards, nt as SettingsCard, o as AuthCallback, ot as SignUpForm, p as ChangePasswordCard, pt as TikTokIcon, q as ProvidersCard, r as AccountView, rt as SettingsCellSkeleton, s as AuthForm, st as SignedIn, t as AcceptInvitationCard, tt as SessionsCard, u as AuthUIProvider, ut as SpotifyIcon, v as DiscordIcon, vt as UpdateFieldCard, w as GoogleIcon, wt as UserView, x as ForgotPasswordForm, xt as UserAvatar, y as DropboxIcon, yt as UpdateNameCard, z as OrganizationNameCard } from "../ui-CnVnqGns.mjs";
4
4
  import { useStore } from "better-auth/react";
5
5
 
@@ -1,4 +1,4 @@
1
- import { i as CURRENT_TAB_CLIENT_ID, n as BETTER_AUTH_METHODS_CACHE, r as BETTER_AUTH_METHODS_HOOKS, t as NeonAuthAdapterCore } from "./adapter-core-Bt4M5I2g.mjs";
1
+ import { i as CURRENT_TAB_CLIENT_ID, n as BETTER_AUTH_METHODS_CACHE, r as BETTER_AUTH_METHODS_HOOKS, t as NeonAuthAdapterCore } from "./adapter-core-BFMM3lwe.mjs";
2
2
  import { a as createAuthError, i as AuthErrorCode, l as isAuthError, n as mapBetterAuthSession, r as normalizeBetterAuthError, t as mapBetterAuthIdentity } from "./better-auth-helpers-Bkezghej.mjs";
3
3
  import { createAuthClient, getGlobalBroadcastChannel } from "better-auth/client";
4
4
  import "@supabase/auth-js";