@nihplod/sso-sdk 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,19 +9,35 @@ var DEFAULT_STATE_COOKIE_NAME = "__Host-nihplod_sso_state";
9
9
  var DEFAULT_RETURN_COOKIE_NAME = "__Host-nihplod_sso_return";
10
10
  var DEFAULT_VERIFIER_COOKIE_NAME = "__Secure-nihplod_sso_verifier";
11
11
  var DEFAULT_LOGOUT_STATE_COOKIE_NAME = "__Host-nihplod_sso_logout_state";
12
- function getHostCookieOptions(maxAge) {
12
+ function toInsecureCookieName(name) {
13
+ return name.replace(/^__(Host|Secure)-/, "");
14
+ }
15
+ function resolveInsecureLocalDev(enabled, ssoBaseUrl) {
16
+ if (!enabled) return false;
17
+ if (process.env.NODE_ENV === "production" && /^https:\/\//i.test(ssoBaseUrl)) {
18
+ console.warn(
19
+ "[SSO SDK] insecureLocalDev=true \u5DF2\u88AB\u5FFD\u7565\uFF1A\u5F53\u524D\u4E3A\u751F\u4EA7\u73AF\u5883\uFF08NODE_ENV=production\uFF09\u4E14 ssoBaseUrl \u4F7F\u7528 HTTPS\uFF0CCookie \u4ECD\u4FDD\u6301 Secure \u5C5E\u6027\u4E0E __Host-/__Secure- \u524D\u7F00\u3002\u8BF7\u4ECE\u751F\u4EA7\u914D\u7F6E\u4E2D\u79FB\u9664\u8BE5\u5F00\u5173\u3002"
20
+ );
21
+ return false;
22
+ }
23
+ console.warn(
24
+ "[SSO SDK] insecureLocalDev=true\uFF1ACookie \u7684 Secure \u5C5E\u6027\u5DF2\u5173\u95ED\u4E14\u53BB\u9664 __Host-/__Secure- \u524D\u7F00\u3002\u4EC5\u9650 http://localhost \u672C\u5730\u5F00\u53D1\u4F7F\u7528\uFF0C\u751F\u4EA7\u73AF\u5883\u5FC5\u987B\u79FB\u9664\u8BE5\u914D\u7F6E\uFF08\u751F\u4EA7\u5FC5\u987B\u7528 HTTPS\uFF09\u3002"
25
+ );
26
+ return true;
27
+ }
28
+ function getHostCookieOptions(maxAge, secure = true) {
13
29
  return {
14
30
  httpOnly: true,
15
- secure: true,
31
+ secure,
16
32
  sameSite: "lax",
17
33
  path: "/",
18
34
  ...maxAge !== void 0 ? { maxAge } : {}
19
35
  };
20
36
  }
21
- function getSecureCookieOptions(maxAge, path = "/") {
37
+ function getSecureCookieOptions(maxAge, path = "/", secure = true) {
22
38
  return {
23
39
  httpOnly: true,
24
- secure: true,
40
+ secure,
25
41
  sameSite: "lax",
26
42
  path,
27
43
  ...maxAge !== void 0 ? { maxAge } : {}
@@ -139,11 +155,14 @@ function createSsoMiddleware(config) {
139
155
  callbackPath = "/api/auth/callback",
140
156
  ssoCookieName = "__Host-user_token",
141
157
  validateSsoCookie = true,
142
- accessTokenCookieName = DEFAULT_ACCESS_TOKEN_COOKIE_NAME,
143
- stateCookieName = DEFAULT_STATE_COOKIE_NAME,
144
- returnUrlCookieName = DEFAULT_RETURN_COOKIE_NAME,
145
- verifierCookieName = DEFAULT_VERIFIER_COOKIE_NAME
158
+ insecureLocalDev: insecureLocalDevOpt = false
146
159
  } = config;
160
+ const insecureLocalDev = resolveInsecureLocalDev(insecureLocalDevOpt, ssoBaseUrl);
161
+ const secureCookies = !insecureLocalDev;
162
+ const accessTokenCookieName = insecureLocalDev ? toInsecureCookieName(config.accessTokenCookieName ?? DEFAULT_ACCESS_TOKEN_COOKIE_NAME) : config.accessTokenCookieName ?? DEFAULT_ACCESS_TOKEN_COOKIE_NAME;
163
+ const stateCookieName = insecureLocalDev ? toInsecureCookieName(config.stateCookieName ?? DEFAULT_STATE_COOKIE_NAME) : config.stateCookieName ?? DEFAULT_STATE_COOKIE_NAME;
164
+ const returnUrlCookieName = insecureLocalDev ? toInsecureCookieName(config.returnUrlCookieName ?? DEFAULT_RETURN_COOKIE_NAME) : config.returnUrlCookieName ?? DEFAULT_RETURN_COOKIE_NAME;
165
+ const verifierCookieName = insecureLocalDev ? toInsecureCookieName(config.verifierCookieName ?? DEFAULT_VERIFIER_COOKIE_NAME) : config.verifierCookieName ?? DEFAULT_VERIFIER_COOKIE_NAME;
147
166
  const normalizedBase = ssoBaseUrl.replace(/\/+$/, "");
148
167
  if (process.env.NODE_ENV !== "production") {
149
168
  if (!validateSsoCookie) {
@@ -211,12 +230,12 @@ function createSsoMiddleware(config) {
211
230
  const loginUrl = new URL("/api/oauth/authorize", normalizedBase);
212
231
  loginUrl.search = authorizeParams.toString();
213
232
  const response = NextResponse.redirect(loginUrl);
214
- response.cookies.set(stateCookieName, state, getHostCookieOptions(600));
215
- response.cookies.set(verifierCookieName, verifier, getSecureCookieOptions(600, callbackPath));
233
+ response.cookies.set(stateCookieName, state, getHostCookieOptions(600, secureCookies));
234
+ response.cookies.set(verifierCookieName, verifier, getSecureCookieOptions(600, callbackPath, secureCookies));
216
235
  const safeReturnUrl = (request.nextUrl.pathname + request.nextUrl.search).slice(0, 2048);
217
- response.cookies.set(returnUrlCookieName, safeReturnUrl, getHostCookieOptions(600));
236
+ response.cookies.set(returnUrlCookieName, safeReturnUrl, getHostCookieOptions(600, secureCookies));
218
237
  if (accessTokenCookie?.value) {
219
- response.cookies.set(accessTokenCookieName, "", getHostCookieOptions(0));
238
+ response.cookies.set(accessTokenCookieName, "", getHostCookieOptions(0, secureCookies));
220
239
  }
221
240
  return response;
222
241
  };
@@ -239,6 +258,7 @@ var SsoError = class extends Error {
239
258
  // src/core/security.ts
240
259
  function isTrustedReturnUrl(url, currentOrigin) {
241
260
  if (!url) return false;
261
+ if (url.includes("\\")) return false;
242
262
  if (url.startsWith("/") && !url.startsWith("//")) return true;
243
263
  try {
244
264
  return new URL(url).origin === currentOrigin;
@@ -444,7 +464,7 @@ async function validateIdToken(idToken, accessToken, expectedIssuer, expectedCli
444
464
  if (typeof payload.exp !== "number") {
445
465
  throw new SsoError("id_token_invalid", "ID Token \u7F3A\u5C11 exp \u58F0\u660E");
446
466
  }
447
- if (Date.now() >= payload.exp * 1e3) {
467
+ if (Date.now() >= payload.exp * 1e3 + 6e4) {
448
468
  throw new SsoError("id_token_expired", "ID Token \u5DF2\u8FC7\u671F");
449
469
  }
450
470
  if (typeof payload.iat === "number" && payload.iat * 1e3 > Date.now() + 6e4) {
@@ -470,13 +490,17 @@ function createCallbackRouteHandler(config) {
470
490
  redirectUri,
471
491
  clientSecret,
472
492
  defaultReturnPath = "/",
473
- accessTokenCookieName = DEFAULT_ACCESS_TOKEN_COOKIE_NAME,
474
- refreshTokenCookieName = DEFAULT_REFRESH_TOKEN_COOKIE_NAME,
475
- idTokenCookieName = DEFAULT_ID_TOKEN_COOKIE_NAME,
476
- stateCookieName = DEFAULT_STATE_COOKIE_NAME,
477
- returnUrlCookieName = DEFAULT_RETURN_COOKIE_NAME,
478
- verifierCookieName = DEFAULT_VERIFIER_COOKIE_NAME
493
+ insecureLocalDev: insecureLocalDevOpt = false
479
494
  } = config;
495
+ const insecureLocalDev = resolveInsecureLocalDev(insecureLocalDevOpt, ssoBaseUrl);
496
+ const secureCookies = !insecureLocalDev;
497
+ const pickName = (explicit, fallback) => insecureLocalDev ? toInsecureCookieName(explicit ?? fallback) : explicit ?? fallback;
498
+ const accessTokenCookieName = pickName(config.accessTokenCookieName, DEFAULT_ACCESS_TOKEN_COOKIE_NAME);
499
+ const refreshTokenCookieName = pickName(config.refreshTokenCookieName, DEFAULT_REFRESH_TOKEN_COOKIE_NAME);
500
+ const idTokenCookieName = pickName(config.idTokenCookieName, DEFAULT_ID_TOKEN_COOKIE_NAME);
501
+ const stateCookieName = pickName(config.stateCookieName, DEFAULT_STATE_COOKIE_NAME);
502
+ const returnUrlCookieName = pickName(config.returnUrlCookieName, DEFAULT_RETURN_COOKIE_NAME);
503
+ const verifierCookieName = pickName(config.verifierCookieName, DEFAULT_VERIFIER_COOKIE_NAME);
480
504
  const normalizedBase = ssoBaseUrl.replace(/\/+$/, "");
481
505
  return async function GET(request) {
482
506
  const { searchParams } = request.nextUrl;
@@ -584,6 +608,15 @@ function createCallbackRouteHandler(config) {
584
608
  );
585
609
  }
586
610
  const tokenData = await res.json();
611
+ if (!tokenData.access_token || !tokenData.refresh_token) {
612
+ return NextResponse2.json(
613
+ {
614
+ error: "server_error",
615
+ error_description: "Token \u54CD\u5E94\u7F3A\u5C11 access_token \u6216 refresh_token"
616
+ },
617
+ { status: 502 }
618
+ );
619
+ }
587
620
  if (tokenData.id_token) {
588
621
  try {
589
622
  await validateIdToken(
@@ -606,11 +639,11 @@ function createCallbackRouteHandler(config) {
606
639
  const returnUrl = isTrustedReturnUrl(rawReturnUrl, request.nextUrl.origin) ? rawReturnUrl : "/";
607
640
  const response = NextResponse2.redirect(new URL(returnUrl, request.url));
608
641
  response.cookies.set(accessTokenCookieName, tokenData.access_token, {
609
- ...getHostCookieOptions(tokenData.expires_in)
642
+ ...getHostCookieOptions(tokenData.expires_in, secureCookies)
610
643
  });
611
644
  const refreshMaxAge = tokenData.refresh_expires_in != null ? tokenData.refresh_expires_in : 30 * 24 * 60 * 60;
612
645
  response.cookies.set(refreshTokenCookieName, tokenData.refresh_token, {
613
- ...getHostCookieOptions(refreshMaxAge)
646
+ ...getHostCookieOptions(refreshMaxAge, secureCookies)
614
647
  });
615
648
  if (tokenData.id_token) {
616
649
  let idTokenMaxAge = 3600;
@@ -628,14 +661,14 @@ function createCallbackRouteHandler(config) {
628
661
  }
629
662
  if (idTokenMaxAge > 0) {
630
663
  response.cookies.set(idTokenCookieName, tokenData.id_token, {
631
- ...getHostCookieOptions(idTokenMaxAge)
664
+ ...getHostCookieOptions(idTokenMaxAge, secureCookies)
632
665
  });
633
666
  }
634
667
  }
635
- response.cookies.set(stateCookieName, "", getHostCookieOptions(0));
636
- response.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0));
637
- response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/"));
638
- response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, request.nextUrl.pathname));
668
+ response.cookies.set(stateCookieName, "", getHostCookieOptions(0, secureCookies));
669
+ response.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0, secureCookies));
670
+ response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/", secureCookies));
671
+ response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, request.nextUrl.pathname, secureCookies));
639
672
  return response;
640
673
  };
641
674
  }
@@ -680,15 +713,19 @@ function createLogoutRouteHandler(config) {
680
713
  clientSecret,
681
714
  postLogoutRedirectUri = new URL(redirectUri).origin + "/",
682
715
  redirectToSso = true,
683
- accessTokenCookieName = DEFAULT_ACCESS_TOKEN_COOKIE_NAME,
684
- refreshTokenCookieName = DEFAULT_REFRESH_TOKEN_COOKIE_NAME,
685
- idTokenCookieName = DEFAULT_ID_TOKEN_COOKIE_NAME,
686
- stateCookieName = DEFAULT_STATE_COOKIE_NAME,
687
- returnUrlCookieName = DEFAULT_RETURN_COOKIE_NAME,
688
- verifierCookieName = DEFAULT_VERIFIER_COOKIE_NAME,
689
716
  callbackPath = "/api/auth/callback",
690
- logoutStateCookieName = DEFAULT_LOGOUT_STATE_COOKIE_NAME
717
+ insecureLocalDev: insecureLocalDevOpt = false
691
718
  } = config;
719
+ const insecureLocalDev = resolveInsecureLocalDev(insecureLocalDevOpt, ssoBaseUrl);
720
+ const secureCookies = !insecureLocalDev;
721
+ const pickName = (explicit, fallback) => insecureLocalDev ? toInsecureCookieName(explicit ?? fallback) : explicit ?? fallback;
722
+ const accessTokenCookieName = pickName(config.accessTokenCookieName, DEFAULT_ACCESS_TOKEN_COOKIE_NAME);
723
+ const refreshTokenCookieName = pickName(config.refreshTokenCookieName, DEFAULT_REFRESH_TOKEN_COOKIE_NAME);
724
+ const idTokenCookieName = pickName(config.idTokenCookieName, DEFAULT_ID_TOKEN_COOKIE_NAME);
725
+ const stateCookieName = pickName(config.stateCookieName, DEFAULT_STATE_COOKIE_NAME);
726
+ const returnUrlCookieName = pickName(config.returnUrlCookieName, DEFAULT_RETURN_COOKIE_NAME);
727
+ const verifierCookieName = pickName(config.verifierCookieName, DEFAULT_VERIFIER_COOKIE_NAME);
728
+ const logoutStateCookieName = pickName(config.logoutStateCookieName, DEFAULT_LOGOUT_STATE_COOKIE_NAME);
692
729
  const normalizedBase = ssoBaseUrl.replace(/\/+$/, "");
693
730
  return async function handler(request) {
694
731
  const returnedState = request.nextUrl.searchParams.get("state");
@@ -701,7 +738,7 @@ function createLogoutRouteHandler(config) {
701
738
  );
702
739
  }
703
740
  const res = NextResponse3.redirect(request.nextUrl.origin + "/");
704
- res.cookies.set(logoutStateCookieName, "", getHostCookieOptions(0));
741
+ res.cookies.set(logoutStateCookieName, "", getHostCookieOptions(0, secureCookies));
705
742
  return res;
706
743
  }
707
744
  const refreshToken = request.cookies.get(refreshTokenCookieName)?.value;
@@ -727,18 +764,18 @@ function createLogoutRouteHandler(config) {
727
764
  }
728
765
  }
729
766
  const clearCookies = (res) => {
730
- res.cookies.set(accessTokenCookieName, "", getHostCookieOptions(0));
731
- res.cookies.set(refreshTokenCookieName, "", getHostCookieOptions(0));
732
- res.cookies.set(idTokenCookieName, "", getHostCookieOptions(0));
733
- res.cookies.set(stateCookieName, "", getHostCookieOptions(0));
734
- res.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0));
735
- res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/"));
736
- res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, callbackPath));
767
+ res.cookies.set(accessTokenCookieName, "", getHostCookieOptions(0, secureCookies));
768
+ res.cookies.set(refreshTokenCookieName, "", getHostCookieOptions(0, secureCookies));
769
+ res.cookies.set(idTokenCookieName, "", getHostCookieOptions(0, secureCookies));
770
+ res.cookies.set(stateCookieName, "", getHostCookieOptions(0, secureCookies));
771
+ res.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0, secureCookies));
772
+ res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/", secureCookies));
773
+ res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, callbackPath, secureCookies));
737
774
  return res;
738
775
  };
739
776
  if (redirectToSso) {
740
777
  const discovery = await fetchDiscovery(normalizedBase);
741
- const endSessionEndpoint = discovery?.end_session_endpoint || `${normalizedBase}/logout`;
778
+ const endSessionEndpoint = discovery?.end_session_endpoint || `${normalizedBase}/api/oauth/end-session`;
742
779
  const logoutUrl = new URL(endSessionEndpoint);
743
780
  logoutUrl.searchParams.set("client_id", clientId);
744
781
  logoutUrl.searchParams.set(
@@ -751,7 +788,7 @@ function createLogoutRouteHandler(config) {
751
788
  const logoutState = generateRandomString2(32);
752
789
  logoutUrl.searchParams.set("state", logoutState);
753
790
  const res = clearCookies(NextResponse3.redirect(logoutUrl.toString()));
754
- res.cookies.set(logoutStateCookieName, logoutState, getHostCookieOptions(600));
791
+ res.cookies.set(logoutStateCookieName, logoutState, getHostCookieOptions(600, secureCookies));
755
792
  return res;
756
793
  }
757
794
  return clearCookies(
@@ -771,6 +808,7 @@ export {
771
808
  createLogoutRouteHandler,
772
809
  createSsoMiddleware,
773
810
  getHostCookieOptions,
774
- getSecureCookieOptions
811
+ getSecureCookieOptions,
812
+ toInsecureCookieName
775
813
  };
776
814
  //# sourceMappingURL=index.mjs.map