@nihplod/sso-sdk 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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(
@@ -602,15 +635,16 @@ function createCallbackRouteHandler(config) {
602
635
  );
603
636
  }
604
637
  }
638
+ const callbackOrigin = new URL(redirectUri).origin;
605
639
  const rawReturnUrl = request.cookies.get(returnUrlCookieName)?.value || defaultReturnPath;
606
- const returnUrl = isTrustedReturnUrl(rawReturnUrl, request.nextUrl.origin) ? rawReturnUrl : "/";
607
- const response = NextResponse2.redirect(new URL(returnUrl, request.url));
640
+ const returnUrl = isTrustedReturnUrl(rawReturnUrl, callbackOrigin) ? rawReturnUrl : "/";
641
+ const response = NextResponse2.redirect(new URL(returnUrl, callbackOrigin));
608
642
  response.cookies.set(accessTokenCookieName, tokenData.access_token, {
609
- ...getHostCookieOptions(tokenData.expires_in)
643
+ ...getHostCookieOptions(tokenData.expires_in, secureCookies)
610
644
  });
611
645
  const refreshMaxAge = tokenData.refresh_expires_in != null ? tokenData.refresh_expires_in : 30 * 24 * 60 * 60;
612
646
  response.cookies.set(refreshTokenCookieName, tokenData.refresh_token, {
613
- ...getHostCookieOptions(refreshMaxAge)
647
+ ...getHostCookieOptions(refreshMaxAge, secureCookies)
614
648
  });
615
649
  if (tokenData.id_token) {
616
650
  let idTokenMaxAge = 3600;
@@ -628,14 +662,14 @@ function createCallbackRouteHandler(config) {
628
662
  }
629
663
  if (idTokenMaxAge > 0) {
630
664
  response.cookies.set(idTokenCookieName, tokenData.id_token, {
631
- ...getHostCookieOptions(idTokenMaxAge)
665
+ ...getHostCookieOptions(idTokenMaxAge, secureCookies)
632
666
  });
633
667
  }
634
668
  }
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));
669
+ response.cookies.set(stateCookieName, "", getHostCookieOptions(0, secureCookies));
670
+ response.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0, secureCookies));
671
+ response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/", secureCookies));
672
+ response.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, request.nextUrl.pathname, secureCookies));
639
673
  return response;
640
674
  };
641
675
  }
@@ -680,16 +714,21 @@ function createLogoutRouteHandler(config) {
680
714
  clientSecret,
681
715
  postLogoutRedirectUri = new URL(redirectUri).origin + "/",
682
716
  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
717
  callbackPath = "/api/auth/callback",
690
- logoutStateCookieName = DEFAULT_LOGOUT_STATE_COOKIE_NAME
718
+ insecureLocalDev: insecureLocalDevOpt = false
691
719
  } = config;
720
+ const insecureLocalDev = resolveInsecureLocalDev(insecureLocalDevOpt, ssoBaseUrl);
721
+ const secureCookies = !insecureLocalDev;
722
+ const pickName = (explicit, fallback) => insecureLocalDev ? toInsecureCookieName(explicit ?? fallback) : explicit ?? fallback;
723
+ const accessTokenCookieName = pickName(config.accessTokenCookieName, DEFAULT_ACCESS_TOKEN_COOKIE_NAME);
724
+ const refreshTokenCookieName = pickName(config.refreshTokenCookieName, DEFAULT_REFRESH_TOKEN_COOKIE_NAME);
725
+ const idTokenCookieName = pickName(config.idTokenCookieName, DEFAULT_ID_TOKEN_COOKIE_NAME);
726
+ const stateCookieName = pickName(config.stateCookieName, DEFAULT_STATE_COOKIE_NAME);
727
+ const returnUrlCookieName = pickName(config.returnUrlCookieName, DEFAULT_RETURN_COOKIE_NAME);
728
+ const verifierCookieName = pickName(config.verifierCookieName, DEFAULT_VERIFIER_COOKIE_NAME);
729
+ const logoutStateCookieName = pickName(config.logoutStateCookieName, DEFAULT_LOGOUT_STATE_COOKIE_NAME);
692
730
  const normalizedBase = ssoBaseUrl.replace(/\/+$/, "");
731
+ const callbackOrigin = new URL(redirectUri).origin;
693
732
  return async function handler(request) {
694
733
  const returnedState = request.nextUrl.searchParams.get("state");
695
734
  if (returnedState) {
@@ -700,8 +739,8 @@ function createLogoutRouteHandler(config) {
700
739
  { status: 400 }
701
740
  );
702
741
  }
703
- const res = NextResponse3.redirect(request.nextUrl.origin + "/");
704
- res.cookies.set(logoutStateCookieName, "", getHostCookieOptions(0));
742
+ const res = NextResponse3.redirect(callbackOrigin + "/");
743
+ res.cookies.set(logoutStateCookieName, "", getHostCookieOptions(0, secureCookies));
705
744
  return res;
706
745
  }
707
746
  const refreshToken = request.cookies.get(refreshTokenCookieName)?.value;
@@ -727,18 +766,18 @@ function createLogoutRouteHandler(config) {
727
766
  }
728
767
  }
729
768
  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));
769
+ res.cookies.set(accessTokenCookieName, "", getHostCookieOptions(0, secureCookies));
770
+ res.cookies.set(refreshTokenCookieName, "", getHostCookieOptions(0, secureCookies));
771
+ res.cookies.set(idTokenCookieName, "", getHostCookieOptions(0, secureCookies));
772
+ res.cookies.set(stateCookieName, "", getHostCookieOptions(0, secureCookies));
773
+ res.cookies.set(returnUrlCookieName, "", getHostCookieOptions(0, secureCookies));
774
+ res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, "/", secureCookies));
775
+ res.cookies.set(verifierCookieName, "", getSecureCookieOptions(0, callbackPath, secureCookies));
737
776
  return res;
738
777
  };
739
778
  if (redirectToSso) {
740
779
  const discovery = await fetchDiscovery(normalizedBase);
741
- const endSessionEndpoint = discovery?.end_session_endpoint || `${normalizedBase}/logout`;
780
+ const endSessionEndpoint = discovery?.end_session_endpoint || `${normalizedBase}/api/oauth/end-session`;
742
781
  const logoutUrl = new URL(endSessionEndpoint);
743
782
  logoutUrl.searchParams.set("client_id", clientId);
744
783
  logoutUrl.searchParams.set(
@@ -751,11 +790,11 @@ function createLogoutRouteHandler(config) {
751
790
  const logoutState = generateRandomString2(32);
752
791
  logoutUrl.searchParams.set("state", logoutState);
753
792
  const res = clearCookies(NextResponse3.redirect(logoutUrl.toString()));
754
- res.cookies.set(logoutStateCookieName, logoutState, getHostCookieOptions(600));
793
+ res.cookies.set(logoutStateCookieName, logoutState, getHostCookieOptions(600, secureCookies));
755
794
  return res;
756
795
  }
757
796
  return clearCookies(
758
- NextResponse3.redirect(request.nextUrl.origin + "/")
797
+ NextResponse3.redirect(callbackOrigin + "/")
759
798
  );
760
799
  };
761
800
  }
@@ -771,6 +810,7 @@ export {
771
810
  createLogoutRouteHandler,
772
811
  createSsoMiddleware,
773
812
  getHostCookieOptions,
774
- getSecureCookieOptions
813
+ getSecureCookieOptions,
814
+ toInsecureCookieName
775
815
  };
776
816
  //# sourceMappingURL=index.mjs.map