@nihplod/sso-sdk 1.6.0 → 1.7.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.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +69 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +69 -78
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +50 -1
- package/dist/next/index.d.ts +50 -1
- package/dist/next/index.js +260 -56
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +259 -56
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +7 -2
- package/dist/react/index.d.ts +7 -2
- package/dist/react/index.js +106 -96
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +106 -96
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -90,24 +90,6 @@ var SILENT_PROBE_KEY = "silent_probe";
|
|
|
90
90
|
function buildKey(base, clientId) {
|
|
91
91
|
return clientId ? `${base}:${clientId}` : base;
|
|
92
92
|
}
|
|
93
|
-
function createMemoryStorageAdapter() {
|
|
94
|
-
const store = /* @__PURE__ */ new Map();
|
|
95
|
-
return {
|
|
96
|
-
get(key) {
|
|
97
|
-
return store.get(key) ?? null;
|
|
98
|
-
},
|
|
99
|
-
set(key, value) {
|
|
100
|
-
store.set(key, value);
|
|
101
|
-
},
|
|
102
|
-
remove(key) {
|
|
103
|
-
store.delete(key);
|
|
104
|
-
},
|
|
105
|
-
keys() {
|
|
106
|
-
return [...store.keys()];
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
var memoryStorageAdapter = createMemoryStorageAdapter();
|
|
111
93
|
function createSessionStorageAdapter() {
|
|
112
94
|
const fallback = /* @__PURE__ */ new Map();
|
|
113
95
|
return {
|
|
@@ -375,7 +357,19 @@ async function fetchJwks(baseUrl, options = {}) {
|
|
|
375
357
|
return cachedJwks.jwks;
|
|
376
358
|
}
|
|
377
359
|
const discovery = await fetchDiscoveryDoc(baseUrl);
|
|
378
|
-
|
|
360
|
+
let jwksUri = `${baseUrl}/api/oauth/jwks`;
|
|
361
|
+
if (discovery?.jwks_uri) {
|
|
362
|
+
try {
|
|
363
|
+
if (new URL(discovery.jwks_uri).origin === new URL(baseUrl).origin) {
|
|
364
|
+
jwksUri = discovery.jwks_uri;
|
|
365
|
+
} else {
|
|
366
|
+
console.warn(
|
|
367
|
+
"[SSO SDK] Discovery \u7684 jwks_uri \u4E0E ssoBaseUrl \u4E0D\u540C\u6E90\uFF0C\u5DF2\u5FFD\u7565\u5E76\u4F7F\u7528\u9ED8\u8BA4 JWKS \u7AEF\u70B9"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
379
373
|
try {
|
|
380
374
|
const res = await fetch(jwksUri, {
|
|
381
375
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
@@ -566,11 +560,7 @@ function assertValidTokenResponse(data, requireRefreshToken) {
|
|
|
566
560
|
);
|
|
567
561
|
}
|
|
568
562
|
}
|
|
569
|
-
var SILENT_PROBE_ERRORS = /* @__PURE__ */ new Set([
|
|
570
|
-
"login_required",
|
|
571
|
-
"consent_required",
|
|
572
|
-
"interaction_required"
|
|
573
|
-
]);
|
|
563
|
+
var SILENT_PROBE_ERRORS = /* @__PURE__ */ new Set(["login_required", "consent_required", "interaction_required"]);
|
|
574
564
|
var _SsoClient = class _SsoClient {
|
|
575
565
|
constructor(config) {
|
|
576
566
|
this._discovery = null;
|
|
@@ -636,6 +626,22 @@ var _SsoClient = class _SsoClient {
|
|
|
636
626
|
if (!authorizationOk) bad.push("authorization_endpoint");
|
|
637
627
|
if (!sameOrigin(d.token_endpoint)) bad.push("token_endpoint");
|
|
638
628
|
if (!sameOrigin(d.userinfo_endpoint)) bad.push("userinfo_endpoint");
|
|
629
|
+
if (d.jwks_uri && !sameOrigin(d.jwks_uri)) bad.push("jwks_uri");
|
|
630
|
+
if (d.revocation_endpoint && !sameOrigin(d.revocation_endpoint)) {
|
|
631
|
+
bad.push("revocation_endpoint");
|
|
632
|
+
}
|
|
633
|
+
if (d.introspection_endpoint && !sameOrigin(d.introspection_endpoint)) {
|
|
634
|
+
bad.push("introspection_endpoint");
|
|
635
|
+
}
|
|
636
|
+
if (d.end_session_endpoint) {
|
|
637
|
+
try {
|
|
638
|
+
if (new URL(d.end_session_endpoint).origin !== publicOrigin) {
|
|
639
|
+
bad.push("end_session_endpoint");
|
|
640
|
+
}
|
|
641
|
+
} catch {
|
|
642
|
+
bad.push("end_session_endpoint");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
639
645
|
if (bad.length > 0) {
|
|
640
646
|
if (!this._discoveryOriginWarned) {
|
|
641
647
|
this._discoveryOriginWarned = true;
|
|
@@ -661,16 +667,11 @@ var _SsoClient = class _SsoClient {
|
|
|
661
667
|
const url = `${this._serverBase}/api/oauth/.well-known/openid-configuration`;
|
|
662
668
|
try {
|
|
663
669
|
const controller = new AbortController();
|
|
664
|
-
const timeoutId = setTimeout(
|
|
665
|
-
() => controller.abort(),
|
|
666
|
-
_SsoClient.DISCOVERY_TIMEOUT_MS
|
|
667
|
-
);
|
|
670
|
+
const timeoutId = setTimeout(() => controller.abort(), _SsoClient.DISCOVERY_TIMEOUT_MS);
|
|
668
671
|
const res = await fetch(url, { signal: controller.signal });
|
|
669
672
|
clearTimeout(timeoutId);
|
|
670
673
|
if (!res.ok) {
|
|
671
|
-
console.warn(
|
|
672
|
-
`[SSO SDK] OIDC Discovery \u8BF7\u6C42\u5931\u8D25: HTTP ${res.status}, \u56DE\u9000\u5230\u9ED8\u8BA4\u7AEF\u70B9`
|
|
673
|
-
);
|
|
674
|
+
console.warn(`[SSO SDK] OIDC Discovery \u8BF7\u6C42\u5931\u8D25: HTTP ${res.status}, \u56DE\u9000\u5230\u9ED8\u8BA4\u7AEF\u70B9`);
|
|
674
675
|
return null;
|
|
675
676
|
}
|
|
676
677
|
const doc = await res.json();
|
|
@@ -885,10 +886,7 @@ var _SsoClient = class _SsoClient {
|
|
|
885
886
|
removeOAuthState(this.config.clientId);
|
|
886
887
|
removeOAuthNonce(this.config.clientId);
|
|
887
888
|
removePkceVerifier(`${this.config.clientId}_popup_nonce`);
|
|
888
|
-
throw new SsoError(
|
|
889
|
-
"popup_blocked",
|
|
890
|
-
"\u5F39\u7A97\u88AB\u6D4F\u89C8\u5668\u62E6\u622A\uFF0C\u8BF7\u5141\u8BB8\u5F39\u7A97\u540E\u91CD\u8BD5"
|
|
891
|
-
);
|
|
889
|
+
throw new SsoError("popup_blocked", "\u5F39\u7A97\u88AB\u6D4F\u89C8\u5668\u62E6\u622A\uFF0C\u8BF7\u5141\u8BB8\u5F39\u7A97\u540E\u91CD\u8BD5");
|
|
892
890
|
}
|
|
893
891
|
const redirectUriOrigin = new URL(this.config.redirectUri).origin;
|
|
894
892
|
try {
|
|
@@ -917,9 +915,7 @@ var _SsoClient = class _SsoClient {
|
|
|
917
915
|
}
|
|
918
916
|
this.handleCallback(event.data.callbackUrl).then((tokenData) => {
|
|
919
917
|
if (tokenData === null) {
|
|
920
|
-
reject(
|
|
921
|
-
new SsoError("not_authenticated", "SSO \u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF08\u9759\u9ED8\u63A2\u6D4B\u65E0\u4F1A\u8BDD\uFF09")
|
|
922
|
-
);
|
|
918
|
+
reject(new SsoError("not_authenticated", "SSO \u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF08\u9759\u9ED8\u63A2\u6D4B\u65E0\u4F1A\u8BDD\uFF09"));
|
|
923
919
|
return;
|
|
924
920
|
}
|
|
925
921
|
resolve(tokenData);
|
|
@@ -930,12 +926,7 @@ var _SsoClient = class _SsoClient {
|
|
|
930
926
|
if (!completed) {
|
|
931
927
|
completed = true;
|
|
932
928
|
cleanup();
|
|
933
|
-
reject(
|
|
934
|
-
new SsoError(
|
|
935
|
-
"popup_closed",
|
|
936
|
-
"\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED"
|
|
937
|
-
)
|
|
938
|
-
);
|
|
929
|
+
reject(new SsoError("popup_closed", "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED"));
|
|
939
930
|
}
|
|
940
931
|
}
|
|
941
932
|
}, 500);
|
|
@@ -1002,18 +993,24 @@ var _SsoClient = class _SsoClient {
|
|
|
1002
993
|
removeOAuthNonce(this.config.clientId);
|
|
1003
994
|
removePkceVerifier(this.config.clientId);
|
|
1004
995
|
removeSilentProbe(this.config.clientId);
|
|
1005
|
-
throw new SsoError(
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
996
|
+
throw new SsoError("state_mismatch", "State \u53C2\u6570\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 CSRF \u653B\u51FB");
|
|
997
|
+
}
|
|
998
|
+
const returnedIss = params.get("iss");
|
|
999
|
+
if (returnedIss) {
|
|
1000
|
+
const discovery = await this._getDiscovery();
|
|
1001
|
+
const expectedIssuer = (discovery?.issuer || this.config.ssoBaseUrl).replace(/\/+$/, "");
|
|
1002
|
+
if (returnedIss.replace(/\/+$/, "") !== expectedIssuer) {
|
|
1003
|
+
removeOAuthState(this.config.clientId);
|
|
1004
|
+
removeOAuthNonce(this.config.clientId);
|
|
1005
|
+
removePkceVerifier(this.config.clientId);
|
|
1006
|
+
removeSilentProbe(this.config.clientId);
|
|
1007
|
+
throw new SsoError("issuer_mismatch", "\u6388\u6743\u54CD\u5E94 issuer \u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 IdP \u6DF7\u53E0\u653B\u51FB");
|
|
1008
|
+
}
|
|
1009
1009
|
}
|
|
1010
1010
|
const expectedNonce = getOAuthNonce(this.config.clientId) ?? void 0;
|
|
1011
1011
|
const verifier = getPkceVerifier(this.config.clientId);
|
|
1012
1012
|
if (!verifier) {
|
|
1013
|
-
throw new SsoError(
|
|
1014
|
-
"pkce_required",
|
|
1015
|
-
"code_verifier \u4E0D\u5B58\u5728\uFF08\u53EF\u80FD\u5DF2\u8FC7\u671F\u6216\u6765\u81EA\u5176\u4ED6\u6807\u7B7E\u9875\uFF09"
|
|
1016
|
-
);
|
|
1013
|
+
throw new SsoError("pkce_required", "code_verifier \u4E0D\u5B58\u5728\uFF08\u53EF\u80FD\u5DF2\u8FC7\u671F\u6216\u6765\u81EA\u5176\u4ED6\u6807\u7B7E\u9875\uFF09");
|
|
1017
1014
|
}
|
|
1018
1015
|
const tokenEndpoint = await this._getTokenEndpoint();
|
|
1019
1016
|
const body = new URLSearchParams();
|
|
@@ -1060,10 +1057,7 @@ var _SsoClient = class _SsoClient {
|
|
|
1060
1057
|
removePkceVerifier(this.config.clientId);
|
|
1061
1058
|
removeSilentProbe(this.config.clientId);
|
|
1062
1059
|
removeTokenData(this.config.clientId);
|
|
1063
|
-
throw new SsoError(
|
|
1064
|
-
"id_token_invalid",
|
|
1065
|
-
"Token \u54CD\u5E94\u7F3A\u5C11 id_token\uFF08scope \u542B openid \u65F6\u5FC5\u9700\uFF09"
|
|
1066
|
-
);
|
|
1060
|
+
throw new SsoError("id_token_invalid", "Token \u54CD\u5E94\u7F3A\u5C11 id_token\uFF08scope \u542B openid \u65F6\u5FC5\u9700\uFF09");
|
|
1067
1061
|
}
|
|
1068
1062
|
if (data.id_token) {
|
|
1069
1063
|
try {
|
|
@@ -1128,19 +1122,14 @@ var _SsoClient = class _SsoClient {
|
|
|
1128
1122
|
}
|
|
1129
1123
|
let res = null;
|
|
1130
1124
|
let lastErr;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
break;
|
|
1140
|
-
} catch (err) {
|
|
1141
|
-
lastErr = err;
|
|
1142
|
-
if (attempt < 1) await new Promise((r) => setTimeout(r, 1e3));
|
|
1143
|
-
}
|
|
1125
|
+
try {
|
|
1126
|
+
res = await fetchWithTimeout(tokenEndpoint, {
|
|
1127
|
+
method: "POST",
|
|
1128
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1129
|
+
body: body.toString()
|
|
1130
|
+
});
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
lastErr = err;
|
|
1144
1133
|
}
|
|
1145
1134
|
if (lastErr || !res) {
|
|
1146
1135
|
throw new SsoError("network_error", "\u5237\u65B0 Token \u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25", lastErr);
|
|
@@ -1222,10 +1211,7 @@ var _SsoClient = class _SsoClient {
|
|
|
1222
1211
|
removeTokenData(this.config.clientId);
|
|
1223
1212
|
throw new SsoError("not_authenticated", "Token \u5DF2\u5931\u6548");
|
|
1224
1213
|
}
|
|
1225
|
-
throw new SsoError(
|
|
1226
|
-
"userinfo_failed",
|
|
1227
|
-
`\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25: HTTP ${res.status}`
|
|
1228
|
-
);
|
|
1214
|
+
throw new SsoError("userinfo_failed", `\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25: HTTP ${res.status}`);
|
|
1229
1215
|
}
|
|
1230
1216
|
return await res.json();
|
|
1231
1217
|
}
|
|
@@ -1285,7 +1271,7 @@ var _SsoClient = class _SsoClient {
|
|
|
1285
1271
|
if (this.config.clientSecret) {
|
|
1286
1272
|
revokeBody.set("client_secret", this.config.clientSecret);
|
|
1287
1273
|
}
|
|
1288
|
-
|
|
1274
|
+
const doRevoke = () => fetchWithTimeout(
|
|
1289
1275
|
revokeUrl,
|
|
1290
1276
|
{
|
|
1291
1277
|
method: "POST",
|
|
@@ -1294,6 +1280,14 @@ var _SsoClient = class _SsoClient {
|
|
|
1294
1280
|
},
|
|
1295
1281
|
REVOKE_TIMEOUT_MS
|
|
1296
1282
|
);
|
|
1283
|
+
let res = await doRevoke();
|
|
1284
|
+
if (!res.ok && (res.status >= 500 || res.status === 429)) {
|
|
1285
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1286
|
+
res = await doRevoke();
|
|
1287
|
+
}
|
|
1288
|
+
if (!res.ok) {
|
|
1289
|
+
this.debugLog("logout: \u670D\u52A1\u7AEF\u64A4\u9500 refresh_token \u5931\u8D25", { status: res.status });
|
|
1290
|
+
}
|
|
1297
1291
|
} catch {
|
|
1298
1292
|
}
|
|
1299
1293
|
}
|
|
@@ -1304,10 +1298,7 @@ var _SsoClient = class _SsoClient {
|
|
|
1304
1298
|
logoutUrl.searchParams.set("client_id", this.config.clientId);
|
|
1305
1299
|
const postLogoutUri = this.config.postLogoutRedirectUri || this.config.redirectUri;
|
|
1306
1300
|
if (postLogoutUri) {
|
|
1307
|
-
logoutUrl.searchParams.set(
|
|
1308
|
-
"post_logout_redirect_uri",
|
|
1309
|
-
postLogoutUri
|
|
1310
|
-
);
|
|
1301
|
+
logoutUrl.searchParams.set("post_logout_redirect_uri", postLogoutUri);
|
|
1311
1302
|
}
|
|
1312
1303
|
if (idTokenHint) {
|
|
1313
1304
|
logoutUrl.searchParams.set("id_token_hint", idTokenHint);
|
|
@@ -1537,7 +1528,15 @@ function SsoProvider({
|
|
|
1537
1528
|
const tokenData = getTokenData(client.config.clientId);
|
|
1538
1529
|
if (!tokenData) return;
|
|
1539
1530
|
const remainingSec = (tokenData.expires_at - Date.now()) / 1e3;
|
|
1540
|
-
|
|
1531
|
+
const lifetimeSec = Math.max(
|
|
1532
|
+
1,
|
|
1533
|
+
(tokenData.expires_at - tokenData.issued_at) / 1e3
|
|
1534
|
+
);
|
|
1535
|
+
const effectiveThreshold = Math.max(
|
|
1536
|
+
1,
|
|
1537
|
+
Math.min(refreshThreshold, Math.floor(lifetimeSec / 3))
|
|
1538
|
+
);
|
|
1539
|
+
if (remainingSec <= 0 || remainingSec <= effectiveThreshold) {
|
|
1541
1540
|
refreshTimerRef.current = setTimeout(
|
|
1542
1541
|
() => {
|
|
1543
1542
|
if (active) void attemptRefresh();
|
|
@@ -1546,13 +1545,13 @@ function SsoProvider({
|
|
|
1546
1545
|
);
|
|
1547
1546
|
return;
|
|
1548
1547
|
}
|
|
1549
|
-
const delayMs = Math.max((remainingSec -
|
|
1548
|
+
const delayMs = Math.max((remainingSec - effectiveThreshold) * 1e3, minDelayMs, 1e3);
|
|
1550
1549
|
refreshTimerRef.current = setTimeout(() => {
|
|
1551
1550
|
if (!active) return;
|
|
1552
1551
|
const td = getTokenData(client.config.clientId);
|
|
1553
1552
|
if (!td) return;
|
|
1554
1553
|
const secLeft = (td.expires_at - Date.now()) / 1e3;
|
|
1555
|
-
if (secLeft <=
|
|
1554
|
+
if (secLeft <= effectiveThreshold) {
|
|
1556
1555
|
void attemptRefresh();
|
|
1557
1556
|
} else {
|
|
1558
1557
|
scheduleNextRefresh();
|
|
@@ -1567,7 +1566,7 @@ function SsoProvider({
|
|
|
1567
1566
|
refreshTimerRef.current = null;
|
|
1568
1567
|
}
|
|
1569
1568
|
};
|
|
1570
|
-
}, [client, loadUser, refreshThreshold, onTokenRefreshed]);
|
|
1569
|
+
}, [client, loadUser, refreshThreshold, onTokenRefreshed, markSessionExpired]);
|
|
1571
1570
|
useEffect(() => {
|
|
1572
1571
|
const handleStorageChange = (e) => {
|
|
1573
1572
|
if (e.key && e.key.startsWith("nihplod_sso_") && !e.key.startsWith(REFRESH_LOCK_PREFIX)) {
|
|
@@ -1662,6 +1661,26 @@ function useSso() {
|
|
|
1662
1661
|
|
|
1663
1662
|
// src/react/RequireAuth.tsx
|
|
1664
1663
|
import React2, { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
1664
|
+
function LoginErrorContent({
|
|
1665
|
+
loginError,
|
|
1666
|
+
retry,
|
|
1667
|
+
renderLoginError
|
|
1668
|
+
}) {
|
|
1669
|
+
if (renderLoginError) {
|
|
1670
|
+
return React2.createElement(
|
|
1671
|
+
React2.Fragment,
|
|
1672
|
+
null,
|
|
1673
|
+
renderLoginError(loginError, retry)
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
const message = loginError instanceof Error ? loginError.message : "\u767B\u5F55\u5931\u8D25";
|
|
1677
|
+
return React2.createElement(
|
|
1678
|
+
"div",
|
|
1679
|
+
null,
|
|
1680
|
+
React2.createElement("p", null, `\u767B\u5F55\u672A\u5B8C\u6210\uFF1A${message}`),
|
|
1681
|
+
React2.createElement("button", { onClick: retry }, "\u91CD\u8BD5\u767B\u5F55")
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1665
1684
|
function RequireAuth({
|
|
1666
1685
|
children,
|
|
1667
1686
|
fallback,
|
|
@@ -1707,20 +1726,11 @@ function RequireAuth({
|
|
|
1707
1726
|
}
|
|
1708
1727
|
if (!isAuthenticated) {
|
|
1709
1728
|
if (loginError) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
);
|
|
1716
|
-
}
|
|
1717
|
-
const message = loginError instanceof Error ? loginError.message : "\u767B\u5F55\u5931\u8D25";
|
|
1718
|
-
return React2.createElement(
|
|
1719
|
-
"div",
|
|
1720
|
-
null,
|
|
1721
|
-
React2.createElement("p", null, `\u767B\u5F55\u672A\u5B8C\u6210\uFF1A${message}`),
|
|
1722
|
-
React2.createElement("button", { onClick: retry }, "\u91CD\u8BD5\u767B\u5F55")
|
|
1723
|
-
);
|
|
1729
|
+
return React2.createElement(LoginErrorContent, {
|
|
1730
|
+
loginError,
|
|
1731
|
+
retry,
|
|
1732
|
+
renderLoginError
|
|
1733
|
+
});
|
|
1724
1734
|
}
|
|
1725
1735
|
if (fallback) return React2.createElement(React2.Fragment, null, fallback);
|
|
1726
1736
|
return React2.createElement("div", null, "\u8BF7\u5148\u767B\u5F55");
|