@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.
- package/README.md +73 -6
- package/dist/index.d.mts +51 -10
- package/dist/index.d.ts +51 -10
- package/dist/index.js +68 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -9
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +38 -5
- package/dist/next/index.d.ts +38 -5
- package/dist/next/index.js +90 -49
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +88 -48
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +113 -10
- package/dist/react/index.d.ts +113 -10
- package/dist/react/index.js +166 -55
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +169 -59
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/next/index.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
607
|
-
const response = NextResponse2.redirect(new URL(returnUrl,
|
|
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
|
-
|
|
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(
|
|
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}/
|
|
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(
|
|
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
|