@nihplod/sso-sdk 1.3.1 → 1.4.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 +55 -5
- package/dist/index.d.mts +25 -5
- package/dist/index.d.ts +25 -5
- package/dist/index.js +64 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -4
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +73 -3
- package/dist/next/index.d.ts +73 -3
- package/dist/next/index.js +231 -12
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +230 -12
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +25 -5
- package/dist/react/index.d.ts +25 -5
- package/dist/react/index.js +72 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +72 -4
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -85,6 +85,7 @@ var STATE_KEY = "oauth_state";
|
|
|
85
85
|
var NONCE_KEY = "oidc_nonce";
|
|
86
86
|
var RETURN_URL_KEY = "return_url";
|
|
87
87
|
var LOGOUT_STATE_KEY = "logout_state";
|
|
88
|
+
var SILENT_PROBE_KEY = "silent_probe";
|
|
88
89
|
function buildKey(base, clientId) {
|
|
89
90
|
return clientId ? `${base}:${clientId}` : base;
|
|
90
91
|
}
|
|
@@ -221,6 +222,15 @@ function getReturnUrl(clientId) {
|
|
|
221
222
|
function removeReturnUrl(clientId) {
|
|
222
223
|
_transient.remove(buildKey(RETURN_URL_KEY, clientId));
|
|
223
224
|
}
|
|
225
|
+
function saveSilentProbe(state, clientId) {
|
|
226
|
+
_transient.set(buildKey(SILENT_PROBE_KEY, clientId), state);
|
|
227
|
+
}
|
|
228
|
+
function getSilentProbe(clientId) {
|
|
229
|
+
return _transient.get(buildKey(SILENT_PROBE_KEY, clientId));
|
|
230
|
+
}
|
|
231
|
+
function removeSilentProbe(clientId) {
|
|
232
|
+
_transient.remove(buildKey(SILENT_PROBE_KEY, clientId));
|
|
233
|
+
}
|
|
224
234
|
function clearAllSsoData(clientId) {
|
|
225
235
|
if (clientId) {
|
|
226
236
|
removeTokenData(clientId);
|
|
@@ -228,6 +238,7 @@ function clearAllSsoData(clientId) {
|
|
|
228
238
|
removeOAuthNonce(clientId);
|
|
229
239
|
removeReturnUrl(clientId);
|
|
230
240
|
removeLogoutState(clientId);
|
|
241
|
+
removeSilentProbe(clientId);
|
|
231
242
|
removePkceVerifier(clientId);
|
|
232
243
|
removePkceVerifier(`${clientId}_popup_nonce`);
|
|
233
244
|
return;
|
|
@@ -237,6 +248,7 @@ function clearAllSsoData(clientId) {
|
|
|
237
248
|
removeOAuthNonce();
|
|
238
249
|
removeReturnUrl();
|
|
239
250
|
removeLogoutState();
|
|
251
|
+
removeSilentProbe();
|
|
240
252
|
const prefix = STORAGE_PREFIX + VERIFIER_KEY_PREFIX;
|
|
241
253
|
const stores = [
|
|
242
254
|
typeof sessionStorage !== "undefined" ? sessionStorage : null,
|
|
@@ -501,6 +513,11 @@ async function validateIdToken(idToken, accessToken, expectedIssuer, expectedCli
|
|
|
501
513
|
}
|
|
502
514
|
|
|
503
515
|
// src/core/SsoClient.ts
|
|
516
|
+
var SILENT_PROBE_ERRORS = /* @__PURE__ */ new Set([
|
|
517
|
+
"login_required",
|
|
518
|
+
"consent_required",
|
|
519
|
+
"interaction_required"
|
|
520
|
+
]);
|
|
504
521
|
var _SsoClient = class _SsoClient {
|
|
505
522
|
constructor(config) {
|
|
506
523
|
this._discovery = null;
|
|
@@ -594,12 +611,15 @@ var _SsoClient = class _SsoClient {
|
|
|
594
611
|
*
|
|
595
612
|
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
596
613
|
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
614
|
+
* @param options.prompt - OIDC prompt 参数。传 "none" 为静默探测:
|
|
615
|
+
* 额外在存储中记录探测标记(该次请求的 state),回调无会话时
|
|
616
|
+
* handleCallback() 返回 null 而不抛错。
|
|
597
617
|
*
|
|
598
618
|
* ⚠️ 不要与 getLoginUrl() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
599
619
|
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
600
620
|
* 同一次登录只使用其中一个入口。
|
|
601
621
|
*/
|
|
602
|
-
async login(returnUrl) {
|
|
622
|
+
async login(returnUrl, options = {}) {
|
|
603
623
|
const verifier = generateCodeVerifier();
|
|
604
624
|
const challenge = await generateCodeChallenge(verifier);
|
|
605
625
|
const state = generateState();
|
|
@@ -607,6 +627,11 @@ var _SsoClient = class _SsoClient {
|
|
|
607
627
|
savePkceVerifier(this.config.clientId, verifier);
|
|
608
628
|
saveOAuthState(state, this.config.clientId);
|
|
609
629
|
saveOAuthNonce(nonce, this.config.clientId);
|
|
630
|
+
if (options.prompt === "none") {
|
|
631
|
+
saveSilentProbe(state, this.config.clientId);
|
|
632
|
+
} else {
|
|
633
|
+
removeSilentProbe(this.config.clientId);
|
|
634
|
+
}
|
|
610
635
|
if (returnUrl) {
|
|
611
636
|
this._saveReturnUrlIfTrusted(returnUrl);
|
|
612
637
|
}
|
|
@@ -620,6 +645,9 @@ var _SsoClient = class _SsoClient {
|
|
|
620
645
|
params.set("nonce", nonce);
|
|
621
646
|
params.set("code_challenge", challenge);
|
|
622
647
|
params.set("code_challenge_method", "S256");
|
|
648
|
+
if (options.prompt) {
|
|
649
|
+
params.set("prompt", options.prompt);
|
|
650
|
+
}
|
|
623
651
|
window.location.href = `${authorizeEndpoint}?${params.toString()}`;
|
|
624
652
|
}
|
|
625
653
|
/**
|
|
@@ -631,7 +659,7 @@ var _SsoClient = class _SsoClient {
|
|
|
631
659
|
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
632
660
|
* 同一次登录只使用其中一个入口。
|
|
633
661
|
*/
|
|
634
|
-
async getLoginUrl(returnUrl) {
|
|
662
|
+
async getLoginUrl(returnUrl, options = {}) {
|
|
635
663
|
const verifier = generateCodeVerifier();
|
|
636
664
|
const challenge = await generateCodeChallenge(verifier);
|
|
637
665
|
const state = generateState();
|
|
@@ -639,6 +667,11 @@ var _SsoClient = class _SsoClient {
|
|
|
639
667
|
savePkceVerifier(this.config.clientId, verifier);
|
|
640
668
|
saveOAuthState(state, this.config.clientId);
|
|
641
669
|
saveOAuthNonce(nonce, this.config.clientId);
|
|
670
|
+
if (options.prompt === "none") {
|
|
671
|
+
saveSilentProbe(state, this.config.clientId);
|
|
672
|
+
} else {
|
|
673
|
+
removeSilentProbe(this.config.clientId);
|
|
674
|
+
}
|
|
642
675
|
if (returnUrl) this._saveReturnUrlIfTrusted(returnUrl);
|
|
643
676
|
const authorizeEndpoint = await this._getAuthorizeEndpoint();
|
|
644
677
|
const params = new URLSearchParams();
|
|
@@ -650,6 +683,9 @@ var _SsoClient = class _SsoClient {
|
|
|
650
683
|
params.set("nonce", nonce);
|
|
651
684
|
params.set("code_challenge", challenge);
|
|
652
685
|
params.set("code_challenge_method", "S256");
|
|
686
|
+
if (options.prompt) {
|
|
687
|
+
params.set("prompt", options.prompt);
|
|
688
|
+
}
|
|
653
689
|
return `${authorizeEndpoint}?${params.toString()}`;
|
|
654
690
|
}
|
|
655
691
|
/**
|
|
@@ -762,7 +798,15 @@ var _SsoClient = class _SsoClient {
|
|
|
762
798
|
if (popup && !popup.closed) {
|
|
763
799
|
popup.close();
|
|
764
800
|
}
|
|
765
|
-
this.handleCallback(event.data.callbackUrl).then(
|
|
801
|
+
this.handleCallback(event.data.callbackUrl).then((tokenData) => {
|
|
802
|
+
if (tokenData === null) {
|
|
803
|
+
reject(
|
|
804
|
+
new SsoError("not_authenticated", "SSO \u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF08\u9759\u9ED8\u63A2\u6D4B\u65E0\u4F1A\u8BDD\uFF09")
|
|
805
|
+
);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
resolve(tokenData);
|
|
809
|
+
}).catch(reject);
|
|
766
810
|
};
|
|
767
811
|
const pollTimer = setInterval(() => {
|
|
768
812
|
if (popup.closed) {
|
|
@@ -800,16 +844,29 @@ var _SsoClient = class _SsoClient {
|
|
|
800
844
|
* 成功后 token 自动保存到 token 存储(默认 sessionStorage,可通过 setTokenStorage 定制)。
|
|
801
845
|
*
|
|
802
846
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
803
|
-
* @returns TokenData
|
|
847
|
+
* @returns TokenData;prompt=none 静默探测且无 SSO 会话时返回 null
|
|
848
|
+
* (IdP 回跳 login_required / consent_required / interaction_required
|
|
849
|
+
* 且回调 state 与探测标记匹配),此时临时数据已清理,调用方按"未登录"处理即可
|
|
804
850
|
*/
|
|
805
851
|
async handleCallback(callbackUrl) {
|
|
806
852
|
const url = new URL(callbackUrl);
|
|
807
853
|
const params = url.searchParams;
|
|
808
854
|
const error = params.get("error");
|
|
809
855
|
if (error) {
|
|
856
|
+
const returnedErrorState = params.get("state");
|
|
857
|
+
const probeState = getSilentProbe(this.config.clientId);
|
|
858
|
+
const savedStateForProbe = getOAuthState(this.config.clientId);
|
|
859
|
+
if (SILENT_PROBE_ERRORS.has(error) && probeState && savedStateForProbe && timingSafeEqualString(savedStateForProbe, returnedErrorState ?? "") && timingSafeEqualString(probeState, returnedErrorState ?? "")) {
|
|
860
|
+
removeOAuthState(this.config.clientId);
|
|
861
|
+
removeOAuthNonce(this.config.clientId);
|
|
862
|
+
removePkceVerifier(this.config.clientId);
|
|
863
|
+
removeSilentProbe(this.config.clientId);
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
810
866
|
removeOAuthState(this.config.clientId);
|
|
811
867
|
removeOAuthNonce(this.config.clientId);
|
|
812
868
|
removePkceVerifier(this.config.clientId);
|
|
869
|
+
removeSilentProbe(this.config.clientId);
|
|
813
870
|
const desc = params.get("error_description") || error;
|
|
814
871
|
throw new SsoError(mapOAuthErrorToSsoCode(error), `\u6388\u6743\u5931\u8D25: ${desc}`);
|
|
815
872
|
}
|
|
@@ -819,6 +876,7 @@ var _SsoClient = class _SsoClient {
|
|
|
819
876
|
removeOAuthState(this.config.clientId);
|
|
820
877
|
removeOAuthNonce(this.config.clientId);
|
|
821
878
|
removePkceVerifier(this.config.clientId);
|
|
879
|
+
removeSilentProbe(this.config.clientId);
|
|
822
880
|
throw new SsoError("token_request_failed", "\u56DE\u8C03 URL \u4E2D\u7F3A\u5C11 authorization code");
|
|
823
881
|
}
|
|
824
882
|
const savedState = getOAuthState(this.config.clientId);
|
|
@@ -826,6 +884,7 @@ var _SsoClient = class _SsoClient {
|
|
|
826
884
|
removeOAuthState(this.config.clientId);
|
|
827
885
|
removeOAuthNonce(this.config.clientId);
|
|
828
886
|
removePkceVerifier(this.config.clientId);
|
|
887
|
+
removeSilentProbe(this.config.clientId);
|
|
829
888
|
throw new SsoError(
|
|
830
889
|
"state_mismatch",
|
|
831
890
|
"State \u53C2\u6570\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5B58\u5728 CSRF \u653B\u51FB"
|
|
@@ -889,6 +948,7 @@ var _SsoClient = class _SsoClient {
|
|
|
889
948
|
removeOAuthState(this.config.clientId);
|
|
890
949
|
removeOAuthNonce(this.config.clientId);
|
|
891
950
|
removePkceVerifier(this.config.clientId);
|
|
951
|
+
removeSilentProbe(this.config.clientId);
|
|
892
952
|
const now = Date.now();
|
|
893
953
|
const tokenData = {
|
|
894
954
|
access_token: data.access_token,
|