@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.mjs CHANGED
@@ -89,24 +89,6 @@ var SILENT_PROBE_KEY = "silent_probe";
89
89
  function buildKey(base, clientId) {
90
90
  return clientId ? `${base}:${clientId}` : base;
91
91
  }
92
- function createMemoryStorageAdapter() {
93
- const store = /* @__PURE__ */ new Map();
94
- return {
95
- get(key) {
96
- return store.get(key) ?? null;
97
- },
98
- set(key, value) {
99
- store.set(key, value);
100
- },
101
- remove(key) {
102
- store.delete(key);
103
- },
104
- keys() {
105
- return [...store.keys()];
106
- }
107
- };
108
- }
109
- var memoryStorageAdapter = createMemoryStorageAdapter();
110
92
  var localStorageAdapter = {
111
93
  get(key) {
112
94
  try {
@@ -428,7 +410,19 @@ async function fetchJwks(baseUrl, options = {}) {
428
410
  return cachedJwks.jwks;
429
411
  }
430
412
  const discovery = await fetchDiscoveryDoc(baseUrl);
431
- const jwksUri = discovery?.jwks_uri || `${baseUrl}/api/oauth/jwks`;
413
+ let jwksUri = `${baseUrl}/api/oauth/jwks`;
414
+ if (discovery?.jwks_uri) {
415
+ try {
416
+ if (new URL(discovery.jwks_uri).origin === new URL(baseUrl).origin) {
417
+ jwksUri = discovery.jwks_uri;
418
+ } else {
419
+ console.warn(
420
+ "[SSO SDK] Discovery \u7684 jwks_uri \u4E0E ssoBaseUrl \u4E0D\u540C\u6E90\uFF0C\u5DF2\u5FFD\u7565\u5E76\u4F7F\u7528\u9ED8\u8BA4 JWKS \u7AEF\u70B9"
421
+ );
422
+ }
423
+ } catch {
424
+ }
425
+ }
432
426
  try {
433
427
  const res = await fetch(jwksUri, {
434
428
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
@@ -619,11 +613,7 @@ function assertValidTokenResponse(data, requireRefreshToken) {
619
613
  );
620
614
  }
621
615
  }
622
- var SILENT_PROBE_ERRORS = /* @__PURE__ */ new Set([
623
- "login_required",
624
- "consent_required",
625
- "interaction_required"
626
- ]);
616
+ var SILENT_PROBE_ERRORS = /* @__PURE__ */ new Set(["login_required", "consent_required", "interaction_required"]);
627
617
  var _SsoClient = class _SsoClient {
628
618
  constructor(config) {
629
619
  this._discovery = null;
@@ -689,6 +679,22 @@ var _SsoClient = class _SsoClient {
689
679
  if (!authorizationOk) bad.push("authorization_endpoint");
690
680
  if (!sameOrigin(d.token_endpoint)) bad.push("token_endpoint");
691
681
  if (!sameOrigin(d.userinfo_endpoint)) bad.push("userinfo_endpoint");
682
+ if (d.jwks_uri && !sameOrigin(d.jwks_uri)) bad.push("jwks_uri");
683
+ if (d.revocation_endpoint && !sameOrigin(d.revocation_endpoint)) {
684
+ bad.push("revocation_endpoint");
685
+ }
686
+ if (d.introspection_endpoint && !sameOrigin(d.introspection_endpoint)) {
687
+ bad.push("introspection_endpoint");
688
+ }
689
+ if (d.end_session_endpoint) {
690
+ try {
691
+ if (new URL(d.end_session_endpoint).origin !== publicOrigin) {
692
+ bad.push("end_session_endpoint");
693
+ }
694
+ } catch {
695
+ bad.push("end_session_endpoint");
696
+ }
697
+ }
692
698
  if (bad.length > 0) {
693
699
  if (!this._discoveryOriginWarned) {
694
700
  this._discoveryOriginWarned = true;
@@ -714,16 +720,11 @@ var _SsoClient = class _SsoClient {
714
720
  const url = `${this._serverBase}/api/oauth/.well-known/openid-configuration`;
715
721
  try {
716
722
  const controller = new AbortController();
717
- const timeoutId = setTimeout(
718
- () => controller.abort(),
719
- _SsoClient.DISCOVERY_TIMEOUT_MS
720
- );
723
+ const timeoutId = setTimeout(() => controller.abort(), _SsoClient.DISCOVERY_TIMEOUT_MS);
721
724
  const res = await fetch(url, { signal: controller.signal });
722
725
  clearTimeout(timeoutId);
723
726
  if (!res.ok) {
724
- console.warn(
725
- `[SSO SDK] OIDC Discovery \u8BF7\u6C42\u5931\u8D25: HTTP ${res.status}, \u56DE\u9000\u5230\u9ED8\u8BA4\u7AEF\u70B9`
726
- );
727
+ console.warn(`[SSO SDK] OIDC Discovery \u8BF7\u6C42\u5931\u8D25: HTTP ${res.status}, \u56DE\u9000\u5230\u9ED8\u8BA4\u7AEF\u70B9`);
727
728
  return null;
728
729
  }
729
730
  const doc = await res.json();
@@ -938,10 +939,7 @@ var _SsoClient = class _SsoClient {
938
939
  removeOAuthState(this.config.clientId);
939
940
  removeOAuthNonce(this.config.clientId);
940
941
  removePkceVerifier(`${this.config.clientId}_popup_nonce`);
941
- throw new SsoError(
942
- "popup_blocked",
943
- "\u5F39\u7A97\u88AB\u6D4F\u89C8\u5668\u62E6\u622A\uFF0C\u8BF7\u5141\u8BB8\u5F39\u7A97\u540E\u91CD\u8BD5"
944
- );
942
+ throw new SsoError("popup_blocked", "\u5F39\u7A97\u88AB\u6D4F\u89C8\u5668\u62E6\u622A\uFF0C\u8BF7\u5141\u8BB8\u5F39\u7A97\u540E\u91CD\u8BD5");
945
943
  }
946
944
  const redirectUriOrigin = new URL(this.config.redirectUri).origin;
947
945
  try {
@@ -970,9 +968,7 @@ var _SsoClient = class _SsoClient {
970
968
  }
971
969
  this.handleCallback(event.data.callbackUrl).then((tokenData) => {
972
970
  if (tokenData === null) {
973
- reject(
974
- new SsoError("not_authenticated", "SSO \u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF08\u9759\u9ED8\u63A2\u6D4B\u65E0\u4F1A\u8BDD\uFF09")
975
- );
971
+ reject(new SsoError("not_authenticated", "SSO \u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF08\u9759\u9ED8\u63A2\u6D4B\u65E0\u4F1A\u8BDD\uFF09"));
976
972
  return;
977
973
  }
978
974
  resolve(tokenData);
@@ -983,12 +979,7 @@ var _SsoClient = class _SsoClient {
983
979
  if (!completed) {
984
980
  completed = true;
985
981
  cleanup();
986
- reject(
987
- new SsoError(
988
- "popup_closed",
989
- "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED"
990
- )
991
- );
982
+ reject(new SsoError("popup_closed", "\u767B\u5F55\u7A97\u53E3\u5DF2\u5173\u95ED"));
992
983
  }
993
984
  }
994
985
  }, 500);
@@ -1055,18 +1046,24 @@ var _SsoClient = class _SsoClient {
1055
1046
  removeOAuthNonce(this.config.clientId);
1056
1047
  removePkceVerifier(this.config.clientId);
1057
1048
  removeSilentProbe(this.config.clientId);
1058
- throw new SsoError(
1059
- "state_mismatch",
1060
- "State \u53C2\u6570\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 CSRF \u653B\u51FB"
1061
- );
1049
+ throw new SsoError("state_mismatch", "State \u53C2\u6570\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 CSRF \u653B\u51FB");
1050
+ }
1051
+ const returnedIss = params.get("iss");
1052
+ if (returnedIss) {
1053
+ const discovery = await this._getDiscovery();
1054
+ const expectedIssuer = (discovery?.issuer || this.config.ssoBaseUrl).replace(/\/+$/, "");
1055
+ if (returnedIss.replace(/\/+$/, "") !== expectedIssuer) {
1056
+ removeOAuthState(this.config.clientId);
1057
+ removeOAuthNonce(this.config.clientId);
1058
+ removePkceVerifier(this.config.clientId);
1059
+ removeSilentProbe(this.config.clientId);
1060
+ throw new SsoError("issuer_mismatch", "\u6388\u6743\u54CD\u5E94 issuer \u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 IdP \u6DF7\u53E0\u653B\u51FB");
1061
+ }
1062
1062
  }
1063
1063
  const expectedNonce = getOAuthNonce(this.config.clientId) ?? void 0;
1064
1064
  const verifier = getPkceVerifier(this.config.clientId);
1065
1065
  if (!verifier) {
1066
- throw new SsoError(
1067
- "pkce_required",
1068
- "code_verifier \u4E0D\u5B58\u5728\uFF08\u53EF\u80FD\u5DF2\u8FC7\u671F\u6216\u6765\u81EA\u5176\u4ED6\u6807\u7B7E\u9875\uFF09"
1069
- );
1066
+ 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");
1070
1067
  }
1071
1068
  const tokenEndpoint = await this._getTokenEndpoint();
1072
1069
  const body = new URLSearchParams();
@@ -1113,10 +1110,7 @@ var _SsoClient = class _SsoClient {
1113
1110
  removePkceVerifier(this.config.clientId);
1114
1111
  removeSilentProbe(this.config.clientId);
1115
1112
  removeTokenData(this.config.clientId);
1116
- throw new SsoError(
1117
- "id_token_invalid",
1118
- "Token \u54CD\u5E94\u7F3A\u5C11 id_token\uFF08scope \u542B openid \u65F6\u5FC5\u9700\uFF09"
1119
- );
1113
+ throw new SsoError("id_token_invalid", "Token \u54CD\u5E94\u7F3A\u5C11 id_token\uFF08scope \u542B openid \u65F6\u5FC5\u9700\uFF09");
1120
1114
  }
1121
1115
  if (data.id_token) {
1122
1116
  try {
@@ -1181,19 +1175,14 @@ var _SsoClient = class _SsoClient {
1181
1175
  }
1182
1176
  let res = null;
1183
1177
  let lastErr;
1184
- for (let attempt = 0; attempt < 2; attempt++) {
1185
- try {
1186
- res = await fetchWithTimeout(tokenEndpoint, {
1187
- method: "POST",
1188
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1189
- body: body.toString()
1190
- });
1191
- lastErr = null;
1192
- break;
1193
- } catch (err) {
1194
- lastErr = err;
1195
- if (attempt < 1) await new Promise((r) => setTimeout(r, 1e3));
1196
- }
1178
+ try {
1179
+ res = await fetchWithTimeout(tokenEndpoint, {
1180
+ method: "POST",
1181
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1182
+ body: body.toString()
1183
+ });
1184
+ } catch (err) {
1185
+ lastErr = err;
1197
1186
  }
1198
1187
  if (lastErr || !res) {
1199
1188
  throw new SsoError("network_error", "\u5237\u65B0 Token \u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25", lastErr);
@@ -1275,10 +1264,7 @@ var _SsoClient = class _SsoClient {
1275
1264
  removeTokenData(this.config.clientId);
1276
1265
  throw new SsoError("not_authenticated", "Token \u5DF2\u5931\u6548");
1277
1266
  }
1278
- throw new SsoError(
1279
- "userinfo_failed",
1280
- `\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25: HTTP ${res.status}`
1281
- );
1267
+ throw new SsoError("userinfo_failed", `\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25: HTTP ${res.status}`);
1282
1268
  }
1283
1269
  return await res.json();
1284
1270
  }
@@ -1338,7 +1324,7 @@ var _SsoClient = class _SsoClient {
1338
1324
  if (this.config.clientSecret) {
1339
1325
  revokeBody.set("client_secret", this.config.clientSecret);
1340
1326
  }
1341
- await fetchWithTimeout(
1327
+ const doRevoke = () => fetchWithTimeout(
1342
1328
  revokeUrl,
1343
1329
  {
1344
1330
  method: "POST",
@@ -1347,6 +1333,14 @@ var _SsoClient = class _SsoClient {
1347
1333
  },
1348
1334
  REVOKE_TIMEOUT_MS
1349
1335
  );
1336
+ let res = await doRevoke();
1337
+ if (!res.ok && (res.status >= 500 || res.status === 429)) {
1338
+ await new Promise((resolve) => setTimeout(resolve, 300));
1339
+ res = await doRevoke();
1340
+ }
1341
+ if (!res.ok) {
1342
+ this.debugLog("logout: \u670D\u52A1\u7AEF\u64A4\u9500 refresh_token \u5931\u8D25", { status: res.status });
1343
+ }
1350
1344
  } catch {
1351
1345
  }
1352
1346
  }
@@ -1357,10 +1351,7 @@ var _SsoClient = class _SsoClient {
1357
1351
  logoutUrl.searchParams.set("client_id", this.config.clientId);
1358
1352
  const postLogoutUri = this.config.postLogoutRedirectUri || this.config.redirectUri;
1359
1353
  if (postLogoutUri) {
1360
- logoutUrl.searchParams.set(
1361
- "post_logout_redirect_uri",
1362
- postLogoutUri
1363
- );
1354
+ logoutUrl.searchParams.set("post_logout_redirect_uri", postLogoutUri);
1364
1355
  }
1365
1356
  if (idTokenHint) {
1366
1357
  logoutUrl.searchParams.set("id_token_hint", idTokenHint);