@nihplod/sso-sdk 1.0.1 → 1.1.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/README.md +19 -3
- package/dist/index.d.mts +99 -12
- package/dist/index.d.ts +99 -12
- package/dist/index.js +413 -75
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +408 -74
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +17 -2
- package/dist/next/index.d.ts +17 -2
- package/dist/next/index.js +276 -45
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +274 -45
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +71 -8
- package/dist/react/index.d.ts +71 -8
- package/dist/react/index.js +476 -111
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +476 -111
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -260,6 +260,11 @@ export const middleware = createSsoMiddleware({
|
|
|
260
260
|
publicPaths: ["/", "/public"],
|
|
261
261
|
// Confidential Client (BFF) can pass clientSecret
|
|
262
262
|
// clientSecret: process.env.SSO_CLIENT_SECRET,
|
|
263
|
+
// validateSsoCookie defaults to true: the middleware calls the introspection
|
|
264
|
+
// endpoint to verify the SSO session cookie. Set to false only if you accept
|
|
265
|
+
// "cookie exists = logged in" semantics (lowest latency, but may pass revoked
|
|
266
|
+
// sessions). Either way, the middleware is only a UX gate — always re-verify
|
|
267
|
+
// tokens in Route Handlers / Server Components before serving sensitive data.
|
|
263
268
|
});
|
|
264
269
|
|
|
265
270
|
export const config = {
|
|
@@ -284,21 +289,30 @@ export const GET = createCallbackRouteHandler({
|
|
|
284
289
|
// src/app/api/auth/logout/route.ts
|
|
285
290
|
import { createLogoutRouteHandler } from "@nihplod/sso-sdk/next";
|
|
286
291
|
|
|
287
|
-
|
|
292
|
+
const handler = createLogoutRouteHandler({
|
|
288
293
|
clientId: "...",
|
|
289
294
|
ssoBaseUrl: "https://nihplod.cn",
|
|
290
295
|
redirectUri: "https://yourapp.com/api/auth/callback",
|
|
291
296
|
postLogoutRedirectUri: "https://yourapp.com/",
|
|
292
297
|
redirectToSso: true,
|
|
293
298
|
});
|
|
299
|
+
|
|
300
|
+
// Prefer POST to trigger logout (prevents logout CSRF via cross-site GET);
|
|
301
|
+
// GET is kept for compatibility with plain <a> navigations.
|
|
302
|
+
export const GET = handler;
|
|
303
|
+
export const POST = handler;
|
|
294
304
|
```
|
|
295
305
|
|
|
296
|
-
|
|
306
|
+
Trigger the logout endpoint with a POST request (recommended):
|
|
297
307
|
|
|
298
308
|
```tsx
|
|
299
|
-
<
|
|
309
|
+
<button onClick={() => fetch("/api/auth/logout", { method: "POST" }).then(() => location.assign("/"))}>
|
|
310
|
+
Logout
|
|
311
|
+
</button>
|
|
300
312
|
```
|
|
301
313
|
|
|
314
|
+
A plain `<a href="/api/auth/logout">` still works via GET, but note it can be triggered cross-site (logout CSRF).
|
|
315
|
+
|
|
302
316
|
### Cookie Configuration
|
|
303
317
|
|
|
304
318
|
Default cookie names:
|
|
@@ -319,6 +333,8 @@ Default cookie names:
|
|
|
319
333
|
|
|
320
334
|
By default, the SDK stores tokens in **memory**. Public Clients (SPA / mobile / desktop) should **never** write the `refresh_token` to `localStorage` to prevent XSS from stealing long-lived credentials.
|
|
321
335
|
|
|
336
|
+
Transient OAuth data (PKCE `code_verifier`, `state`, `returnUrl`, popup nonce) is stored separately in **sessionStorage**, because it must survive the full-page redirect to the SSO center and back; it is cleared automatically when the tab closes. In SSR environments without `sessionStorage`, it falls back to an in-memory map.
|
|
337
|
+
|
|
322
338
|
If the sub-project is a **Next.js BFF / Confidential Client**, you can store tokens in `localStorage` for multi-tab sharing:
|
|
323
339
|
|
|
324
340
|
```typescript
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token 存储抽象层
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
4
|
+
* 分两类存储:
|
|
5
|
+
* - Token 数据:默认使用内存存储,Public Client 浏览器中 refresh_token 不落盘,
|
|
6
|
+
* XSS 无法窃取长期凭证。对需要多 Tab 共享 token 或 BFF/Confidential Client 场景,
|
|
7
|
+
* 可通过 setTokenStorage() 注入 localStorage 实现(如 createSecureStorage({ persist: true }))。
|
|
8
|
+
* - 临时数据(PKCE verifier / state / returnUrl / popup nonce):必须跨整页重定向存活
|
|
9
|
+
* (login() 会 302 跳转到 SSO 中心再回来),因此默认写入 sessionStorage;
|
|
10
|
+
* SSR 等无 sessionStorage 环境自动降级为内存 Map。
|
|
7
11
|
*
|
|
8
12
|
* 多 client 隔离:
|
|
9
13
|
* - token / state / return_url 均支持按 clientId 隔离 key
|
|
@@ -27,6 +31,19 @@ interface TokenStorage {
|
|
|
27
31
|
set(key: string, value: string): void;
|
|
28
32
|
remove(key: string): void;
|
|
29
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 创建存储实现
|
|
36
|
+
*
|
|
37
|
+
* @param options.persist 是否持久化到 localStorage。默认 false(内存存储)。
|
|
38
|
+
* ⚠️ 安全警告:persist=true 会将 refresh_token 明文写入 localStorage,
|
|
39
|
+
* 任何 XSS 均可在不被检测的情况下读取。仅在 BFF/Confidential Client
|
|
40
|
+
* 且 refresh_token 不直接暴露给浏览器的场景下使用。
|
|
41
|
+
* - Confidential/BFF 子项目可设为 true。
|
|
42
|
+
* - Public Client 在浏览器中应保持 false(默认)。
|
|
43
|
+
*/
|
|
44
|
+
declare function createSecureStorage(options?: {
|
|
45
|
+
persist?: boolean;
|
|
46
|
+
}): TokenStorage;
|
|
30
47
|
/**
|
|
31
48
|
* 设置自定义存储实现
|
|
32
49
|
*/
|
|
@@ -85,6 +102,8 @@ interface SsoClientConfig {
|
|
|
85
102
|
ssoBaseUrl: string;
|
|
86
103
|
/** 请求的 scope(空格分隔),如 "openid profile phone" */
|
|
87
104
|
scopes?: string;
|
|
105
|
+
/** RP-Initiated Logout 返回地址(可选)。不传时回退到 redirectUri */
|
|
106
|
+
postLogoutRedirectUri?: string;
|
|
88
107
|
}
|
|
89
108
|
/** 用户信息 */
|
|
90
109
|
interface SsoUser {
|
|
@@ -141,13 +160,19 @@ declare class SsoClient {
|
|
|
141
160
|
private _getTokenEndpoint;
|
|
142
161
|
/** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
|
|
143
162
|
private _getUserinfoEndpoint;
|
|
163
|
+
/**
|
|
164
|
+
* 开放重定向防护:仅保存相对路径或与当前页面同源的 returnUrl,
|
|
165
|
+
* 其余(如 https://evil.com)忽略并告警,防止回调后跳转到钓鱼站点。
|
|
166
|
+
*/
|
|
167
|
+
private _saveReturnUrlIfTrusted;
|
|
144
168
|
/**
|
|
145
169
|
* 发起 SSO 登录
|
|
146
170
|
*
|
|
147
171
|
* 生成 PKCE code_verifier/code_challenge 和 state 参数,
|
|
148
172
|
* 构建 authorize URL,通过 302 跳转到 SSO 登录页。
|
|
149
173
|
*
|
|
150
|
-
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage
|
|
174
|
+
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
175
|
+
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
151
176
|
*/
|
|
152
177
|
login(returnUrl?: string): Promise<void>;
|
|
153
178
|
/**
|
|
@@ -156,11 +181,49 @@ declare class SsoClient {
|
|
|
156
181
|
* 适用于需要手动处理跳转的场景。
|
|
157
182
|
*/
|
|
158
183
|
getLoginUrl(returnUrl?: string): Promise<string>;
|
|
184
|
+
/**
|
|
185
|
+
* 弹窗模式 SSO 登录
|
|
186
|
+
*
|
|
187
|
+
* 打开一个小窗口进行登录,认证完成后窗口自动关闭,
|
|
188
|
+
* 主页面不丢失状态(适用于 SPA 中需要保持表单/浏览上下文的场景)。
|
|
189
|
+
*
|
|
190
|
+
* 流程:
|
|
191
|
+
* 1. window.open() 打开授权 URL 到弹窗
|
|
192
|
+
* 2. 用户在弹窗中完成登录
|
|
193
|
+
* 3. 弹窗加载 CallbackPage 时检测到 window.opener,通过 postMessage 回传回调 URL
|
|
194
|
+
* 4. 主页面收到消息后调用 handleCallback() 交换 token
|
|
195
|
+
* 5. 弹窗自动关闭
|
|
196
|
+
*
|
|
197
|
+
* @param options - 弹窗配置
|
|
198
|
+
* @param options.returnUrl - 登录成功后的返回地址
|
|
199
|
+
* @param options.width - 弹窗宽度(默认 480)
|
|
200
|
+
* @param options.height - 弹窗高度(默认 640)
|
|
201
|
+
* @returns TokenData
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* const client = new SsoClient({ clientId: "xxx", redirectUri: "https://myapp.com/callback", ssoBaseUrl: "https://nihplod.cn" });
|
|
206
|
+
* try {
|
|
207
|
+
* const tokenData = await client.loginPopup({ returnUrl: "/dashboard" });
|
|
208
|
+
* console.log("登录成功", tokenData);
|
|
209
|
+
* } catch (err) {
|
|
210
|
+
* if (err instanceof SsoError && err.code === "popup_blocked") {
|
|
211
|
+
* // 弹窗被拦截,回退到同页重定向
|
|
212
|
+
* await client.login("/dashboard");
|
|
213
|
+
* }
|
|
214
|
+
* }
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
loginPopup(options?: {
|
|
218
|
+
returnUrl?: string;
|
|
219
|
+
width?: number;
|
|
220
|
+
height?: number;
|
|
221
|
+
}): Promise<TokenData>;
|
|
159
222
|
/**
|
|
160
223
|
* 处理 OAuth 回调
|
|
161
224
|
*
|
|
162
225
|
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
163
|
-
* 成功后 token 自动保存到
|
|
226
|
+
* 成功后 token 自动保存到 token 存储(默认内存,可通过 setTokenStorage 定制)。
|
|
164
227
|
*
|
|
165
228
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
166
229
|
* @returns TokenData 或 null
|
|
@@ -247,9 +310,16 @@ declare function generateState(): string;
|
|
|
247
310
|
* SSO SDK 错误类型
|
|
248
311
|
*/
|
|
249
312
|
/** SSO 错误码 */
|
|
250
|
-
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "sso_server_error" | "network_error" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
251
|
-
/**
|
|
252
|
-
|
|
313
|
+
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "authorization_code_expired" | "authorization_code_used" | "client_disabled" | "user_denied_authorization" | "account_disabled" | "sso_server_error" | "rate_limited" | "network_error" | "popup_blocked" | "popup_closed" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_hs256_unsupported" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
314
|
+
/**
|
|
315
|
+
* 将 OAuth 2.0 服务端 error 字段映射到 SsoErrorCode
|
|
316
|
+
*
|
|
317
|
+
* @param oauthError 服务端返回的 error 字段
|
|
318
|
+
* @param context 调用上下文:"token_exchange"(授权码换 token)或 "refresh"(刷新)。
|
|
319
|
+
* invalid_grant 在换 token 场景多为授权码过期/已用,在刷新场景多为会话失效,
|
|
320
|
+
* 需按上下文细化映射。
|
|
321
|
+
*/
|
|
322
|
+
declare function mapOAuthErrorToSsoCode(oauthError: string, context?: "token_exchange" | "refresh"): SsoErrorCode;
|
|
253
323
|
/**
|
|
254
324
|
* SSO SDK 自定义错误
|
|
255
325
|
*/
|
|
@@ -260,13 +330,30 @@ declare class SsoError extends Error {
|
|
|
260
330
|
constructor(code: SsoErrorCode, description: string, cause?: unknown);
|
|
261
331
|
}
|
|
262
332
|
/**
|
|
263
|
-
* OAuth 2.0
|
|
333
|
+
* OAuth 2.0 协议层错误(RFC 6749 §5.2)
|
|
334
|
+
*
|
|
335
|
+
* 用于表示服务端原样返回的 OAuth 错误(error / error_description / error_uri),
|
|
336
|
+
* 与 SDK 语义的 SsoError 区分。
|
|
264
337
|
*/
|
|
265
338
|
declare class OAuthError extends Error {
|
|
266
|
-
readonly code:
|
|
339
|
+
readonly code: string;
|
|
267
340
|
readonly description: string;
|
|
268
341
|
readonly uri?: string;
|
|
269
|
-
constructor(code:
|
|
342
|
+
constructor(code: string, description: string, uri?: string);
|
|
270
343
|
}
|
|
271
344
|
|
|
272
|
-
|
|
345
|
+
/**
|
|
346
|
+
* 安全相关小工具:returnUrl 开放重定向校验、常量时间字符串比较
|
|
347
|
+
*/
|
|
348
|
+
/**
|
|
349
|
+
* 校验 returnUrl 是否可信(防开放重定向)。
|
|
350
|
+
* 仅允许:相对路径(且不以 // 开头)或与 currentOrigin 完全同源的绝对 URL。
|
|
351
|
+
*/
|
|
352
|
+
declare function isTrustedReturnUrl(url: string, currentOrigin: string): boolean;
|
|
353
|
+
/**
|
|
354
|
+
* 常量时间字符串比较(避免 state / at_hash / nonce 等机密值的时序侧信道)。
|
|
355
|
+
* 长度不同也执行完整循环,不提前返回。
|
|
356
|
+
*/
|
|
357
|
+
declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
358
|
+
|
|
359
|
+
export { OAuthError, type OidcDiscovery, SsoClient, type SsoClientConfig, SsoError, type SsoErrorCode, type SsoUser, type TokenData, type TokenResponse, type TokenStorage, clearAllSsoData, clearVerifiersForClients, createSecureStorage, generateCodeChallenge, generateCodeVerifier, generateState, getOAuthState, getPkceVerifier, getReturnUrl, getTokenData, getTokenStorage, isTrustedReturnUrl, mapOAuthErrorToSsoCode, removeOAuthState, removePkceVerifier, removeReturnUrl, removeTokenData, saveOAuthState, savePkceVerifier, saveReturnUrl, saveTokenData, setTokenStorage, timingSafeEqualString };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token 存储抽象层
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
4
|
+
* 分两类存储:
|
|
5
|
+
* - Token 数据:默认使用内存存储,Public Client 浏览器中 refresh_token 不落盘,
|
|
6
|
+
* XSS 无法窃取长期凭证。对需要多 Tab 共享 token 或 BFF/Confidential Client 场景,
|
|
7
|
+
* 可通过 setTokenStorage() 注入 localStorage 实现(如 createSecureStorage({ persist: true }))。
|
|
8
|
+
* - 临时数据(PKCE verifier / state / returnUrl / popup nonce):必须跨整页重定向存活
|
|
9
|
+
* (login() 会 302 跳转到 SSO 中心再回来),因此默认写入 sessionStorage;
|
|
10
|
+
* SSR 等无 sessionStorage 环境自动降级为内存 Map。
|
|
7
11
|
*
|
|
8
12
|
* 多 client 隔离:
|
|
9
13
|
* - token / state / return_url 均支持按 clientId 隔离 key
|
|
@@ -27,6 +31,19 @@ interface TokenStorage {
|
|
|
27
31
|
set(key: string, value: string): void;
|
|
28
32
|
remove(key: string): void;
|
|
29
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 创建存储实现
|
|
36
|
+
*
|
|
37
|
+
* @param options.persist 是否持久化到 localStorage。默认 false(内存存储)。
|
|
38
|
+
* ⚠️ 安全警告:persist=true 会将 refresh_token 明文写入 localStorage,
|
|
39
|
+
* 任何 XSS 均可在不被检测的情况下读取。仅在 BFF/Confidential Client
|
|
40
|
+
* 且 refresh_token 不直接暴露给浏览器的场景下使用。
|
|
41
|
+
* - Confidential/BFF 子项目可设为 true。
|
|
42
|
+
* - Public Client 在浏览器中应保持 false(默认)。
|
|
43
|
+
*/
|
|
44
|
+
declare function createSecureStorage(options?: {
|
|
45
|
+
persist?: boolean;
|
|
46
|
+
}): TokenStorage;
|
|
30
47
|
/**
|
|
31
48
|
* 设置自定义存储实现
|
|
32
49
|
*/
|
|
@@ -85,6 +102,8 @@ interface SsoClientConfig {
|
|
|
85
102
|
ssoBaseUrl: string;
|
|
86
103
|
/** 请求的 scope(空格分隔),如 "openid profile phone" */
|
|
87
104
|
scopes?: string;
|
|
105
|
+
/** RP-Initiated Logout 返回地址(可选)。不传时回退到 redirectUri */
|
|
106
|
+
postLogoutRedirectUri?: string;
|
|
88
107
|
}
|
|
89
108
|
/** 用户信息 */
|
|
90
109
|
interface SsoUser {
|
|
@@ -141,13 +160,19 @@ declare class SsoClient {
|
|
|
141
160
|
private _getTokenEndpoint;
|
|
142
161
|
/** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
|
|
143
162
|
private _getUserinfoEndpoint;
|
|
163
|
+
/**
|
|
164
|
+
* 开放重定向防护:仅保存相对路径或与当前页面同源的 returnUrl,
|
|
165
|
+
* 其余(如 https://evil.com)忽略并告警,防止回调后跳转到钓鱼站点。
|
|
166
|
+
*/
|
|
167
|
+
private _saveReturnUrlIfTrusted;
|
|
144
168
|
/**
|
|
145
169
|
* 发起 SSO 登录
|
|
146
170
|
*
|
|
147
171
|
* 生成 PKCE code_verifier/code_challenge 和 state 参数,
|
|
148
172
|
* 构建 authorize URL,通过 302 跳转到 SSO 登录页。
|
|
149
173
|
*
|
|
150
|
-
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage
|
|
174
|
+
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
175
|
+
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
151
176
|
*/
|
|
152
177
|
login(returnUrl?: string): Promise<void>;
|
|
153
178
|
/**
|
|
@@ -156,11 +181,49 @@ declare class SsoClient {
|
|
|
156
181
|
* 适用于需要手动处理跳转的场景。
|
|
157
182
|
*/
|
|
158
183
|
getLoginUrl(returnUrl?: string): Promise<string>;
|
|
184
|
+
/**
|
|
185
|
+
* 弹窗模式 SSO 登录
|
|
186
|
+
*
|
|
187
|
+
* 打开一个小窗口进行登录,认证完成后窗口自动关闭,
|
|
188
|
+
* 主页面不丢失状态(适用于 SPA 中需要保持表单/浏览上下文的场景)。
|
|
189
|
+
*
|
|
190
|
+
* 流程:
|
|
191
|
+
* 1. window.open() 打开授权 URL 到弹窗
|
|
192
|
+
* 2. 用户在弹窗中完成登录
|
|
193
|
+
* 3. 弹窗加载 CallbackPage 时检测到 window.opener,通过 postMessage 回传回调 URL
|
|
194
|
+
* 4. 主页面收到消息后调用 handleCallback() 交换 token
|
|
195
|
+
* 5. 弹窗自动关闭
|
|
196
|
+
*
|
|
197
|
+
* @param options - 弹窗配置
|
|
198
|
+
* @param options.returnUrl - 登录成功后的返回地址
|
|
199
|
+
* @param options.width - 弹窗宽度(默认 480)
|
|
200
|
+
* @param options.height - 弹窗高度(默认 640)
|
|
201
|
+
* @returns TokenData
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* const client = new SsoClient({ clientId: "xxx", redirectUri: "https://myapp.com/callback", ssoBaseUrl: "https://nihplod.cn" });
|
|
206
|
+
* try {
|
|
207
|
+
* const tokenData = await client.loginPopup({ returnUrl: "/dashboard" });
|
|
208
|
+
* console.log("登录成功", tokenData);
|
|
209
|
+
* } catch (err) {
|
|
210
|
+
* if (err instanceof SsoError && err.code === "popup_blocked") {
|
|
211
|
+
* // 弹窗被拦截,回退到同页重定向
|
|
212
|
+
* await client.login("/dashboard");
|
|
213
|
+
* }
|
|
214
|
+
* }
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
loginPopup(options?: {
|
|
218
|
+
returnUrl?: string;
|
|
219
|
+
width?: number;
|
|
220
|
+
height?: number;
|
|
221
|
+
}): Promise<TokenData>;
|
|
159
222
|
/**
|
|
160
223
|
* 处理 OAuth 回调
|
|
161
224
|
*
|
|
162
225
|
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
163
|
-
* 成功后 token 自动保存到
|
|
226
|
+
* 成功后 token 自动保存到 token 存储(默认内存,可通过 setTokenStorage 定制)。
|
|
164
227
|
*
|
|
165
228
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
166
229
|
* @returns TokenData 或 null
|
|
@@ -247,9 +310,16 @@ declare function generateState(): string;
|
|
|
247
310
|
* SSO SDK 错误类型
|
|
248
311
|
*/
|
|
249
312
|
/** SSO 错误码 */
|
|
250
|
-
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "sso_server_error" | "network_error" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
251
|
-
/**
|
|
252
|
-
|
|
313
|
+
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "authorization_code_expired" | "authorization_code_used" | "client_disabled" | "user_denied_authorization" | "account_disabled" | "sso_server_error" | "rate_limited" | "network_error" | "popup_blocked" | "popup_closed" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_hs256_unsupported" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
314
|
+
/**
|
|
315
|
+
* 将 OAuth 2.0 服务端 error 字段映射到 SsoErrorCode
|
|
316
|
+
*
|
|
317
|
+
* @param oauthError 服务端返回的 error 字段
|
|
318
|
+
* @param context 调用上下文:"token_exchange"(授权码换 token)或 "refresh"(刷新)。
|
|
319
|
+
* invalid_grant 在换 token 场景多为授权码过期/已用,在刷新场景多为会话失效,
|
|
320
|
+
* 需按上下文细化映射。
|
|
321
|
+
*/
|
|
322
|
+
declare function mapOAuthErrorToSsoCode(oauthError: string, context?: "token_exchange" | "refresh"): SsoErrorCode;
|
|
253
323
|
/**
|
|
254
324
|
* SSO SDK 自定义错误
|
|
255
325
|
*/
|
|
@@ -260,13 +330,30 @@ declare class SsoError extends Error {
|
|
|
260
330
|
constructor(code: SsoErrorCode, description: string, cause?: unknown);
|
|
261
331
|
}
|
|
262
332
|
/**
|
|
263
|
-
* OAuth 2.0
|
|
333
|
+
* OAuth 2.0 协议层错误(RFC 6749 §5.2)
|
|
334
|
+
*
|
|
335
|
+
* 用于表示服务端原样返回的 OAuth 错误(error / error_description / error_uri),
|
|
336
|
+
* 与 SDK 语义的 SsoError 区分。
|
|
264
337
|
*/
|
|
265
338
|
declare class OAuthError extends Error {
|
|
266
|
-
readonly code:
|
|
339
|
+
readonly code: string;
|
|
267
340
|
readonly description: string;
|
|
268
341
|
readonly uri?: string;
|
|
269
|
-
constructor(code:
|
|
342
|
+
constructor(code: string, description: string, uri?: string);
|
|
270
343
|
}
|
|
271
344
|
|
|
272
|
-
|
|
345
|
+
/**
|
|
346
|
+
* 安全相关小工具:returnUrl 开放重定向校验、常量时间字符串比较
|
|
347
|
+
*/
|
|
348
|
+
/**
|
|
349
|
+
* 校验 returnUrl 是否可信(防开放重定向)。
|
|
350
|
+
* 仅允许:相对路径(且不以 // 开头)或与 currentOrigin 完全同源的绝对 URL。
|
|
351
|
+
*/
|
|
352
|
+
declare function isTrustedReturnUrl(url: string, currentOrigin: string): boolean;
|
|
353
|
+
/**
|
|
354
|
+
* 常量时间字符串比较(避免 state / at_hash / nonce 等机密值的时序侧信道)。
|
|
355
|
+
* 长度不同也执行完整循环,不提前返回。
|
|
356
|
+
*/
|
|
357
|
+
declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
358
|
+
|
|
359
|
+
export { OAuthError, type OidcDiscovery, SsoClient, type SsoClientConfig, SsoError, type SsoErrorCode, type SsoUser, type TokenData, type TokenResponse, type TokenStorage, clearAllSsoData, clearVerifiersForClients, createSecureStorage, generateCodeChallenge, generateCodeVerifier, generateState, getOAuthState, getPkceVerifier, getReturnUrl, getTokenData, getTokenStorage, isTrustedReturnUrl, mapOAuthErrorToSsoCode, removeOAuthState, removePkceVerifier, removeReturnUrl, removeTokenData, saveOAuthState, savePkceVerifier, saveReturnUrl, saveTokenData, setTokenStorage, timingSafeEqualString };
|