@nihplod/sso-sdk 1.0.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 +360 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.js +788 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +740 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next/index.d.mts +201 -0
- package/dist/next/index.d.ts +201 -0
- package/dist/next/index.js +583 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/index.mjs +547 -0
- package/dist/next/index.mjs.map +1 -0
- package/dist/react/index.d.mts +301 -0
- package/dist/react/index.d.ts +301 -0
- package/dist/react/index.js +1037 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1003 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token 存储抽象层
|
|
3
|
+
*
|
|
4
|
+
* 默认使用 localStorage,以支持多 Tab 间自动同步 token 并避免并发刷新。
|
|
5
|
+
* 对 XSS 敏感的子项目可通过 setTokenStorage() 注入更安全的自定义实现
|
|
6
|
+
*(如内存存储、Service Worker 封装、或带加密的 storage)。
|
|
7
|
+
*
|
|
8
|
+
* 多 client 隔离:
|
|
9
|
+
* - token / state / return_url 均支持按 clientId 隔离 key
|
|
10
|
+
* - 不传 clientId 时使用全局 key,保持向后兼容
|
|
11
|
+
*/
|
|
12
|
+
/** Token 数据 */
|
|
13
|
+
interface TokenData {
|
|
14
|
+
access_token: string;
|
|
15
|
+
token_type: string;
|
|
16
|
+
expires_in: number;
|
|
17
|
+
refresh_token: string;
|
|
18
|
+
id_token?: string;
|
|
19
|
+
/** Token 签发时间(epoch ms),用于计算过期 */
|
|
20
|
+
issued_at: number;
|
|
21
|
+
/** 访问令牌实际过期时间(epoch ms)= issued_at + expires_in * 1000 */
|
|
22
|
+
expires_at: number;
|
|
23
|
+
}
|
|
24
|
+
/** 存储接口 */
|
|
25
|
+
interface TokenStorage {
|
|
26
|
+
get(key: string): string | null;
|
|
27
|
+
set(key: string, value: string): void;
|
|
28
|
+
remove(key: string): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 设置自定义存储实现
|
|
32
|
+
*/
|
|
33
|
+
declare function setTokenStorage(storage: TokenStorage): void;
|
|
34
|
+
/**
|
|
35
|
+
* 获取当前存储实现
|
|
36
|
+
*/
|
|
37
|
+
declare function getTokenStorage(): TokenStorage;
|
|
38
|
+
declare function saveTokenData(data: TokenData, clientId?: string): void;
|
|
39
|
+
declare function getTokenData(clientId?: string): TokenData | null;
|
|
40
|
+
declare function removeTokenData(clientId?: string): void;
|
|
41
|
+
declare function savePkceVerifier(clientId: string, verifier: string): void;
|
|
42
|
+
declare function getPkceVerifier(clientId: string): string | null;
|
|
43
|
+
declare function removePkceVerifier(clientId: string): void;
|
|
44
|
+
declare function saveOAuthState(state: string, clientId?: string): void;
|
|
45
|
+
declare function getOAuthState(clientId?: string): string | null;
|
|
46
|
+
declare function removeOAuthState(clientId?: string): void;
|
|
47
|
+
declare function saveReturnUrl(url: string, clientId?: string): void;
|
|
48
|
+
declare function getReturnUrl(clientId?: string): string | null;
|
|
49
|
+
declare function removeReturnUrl(clientId?: string): void;
|
|
50
|
+
declare function clearAllSsoData(clientId?: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* 清理指定 clientId 列表的 PKCE verifier
|
|
53
|
+
*
|
|
54
|
+
* 适用于非浏览器环境或已知 clientId 场景。
|
|
55
|
+
*/
|
|
56
|
+
declare function clearVerifiersForClients(clientIds: string[]): void;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* SSO Client 核心类
|
|
60
|
+
*
|
|
61
|
+
* 封装完整的 OAuth 2.0 授权码 + PKCE S256 流程:
|
|
62
|
+
* - login(): 发起授权请求
|
|
63
|
+
* - handleCallback(): 处理回调,交换 token
|
|
64
|
+
* - refreshToken(): 刷新 access token(原子轮换)
|
|
65
|
+
* - getUserInfo(): 获取用户信息
|
|
66
|
+
* - logout(): 登出
|
|
67
|
+
* - isAuthenticated(): 检查认证状态
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/** SSO 客户端配置 */
|
|
71
|
+
interface SsoClientConfig {
|
|
72
|
+
/** OAuth Client ID(从管理后台获取) */
|
|
73
|
+
clientId: string;
|
|
74
|
+
/** OAuth Client Secret(从管理后台获取)
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ 安全提醒:client_secret 不应暴露在前端代码中。
|
|
77
|
+
* 对于浏览器端 SPA(Public Client),应省略此字段;SDK 会使用 PKCE 完成授权,
|
|
78
|
+
* 且 token 端点不会发送 client_secret。
|
|
79
|
+
* 对于 BFF/Next.js 等 Confidential Client,可传入 client_secret。
|
|
80
|
+
*/
|
|
81
|
+
clientSecret?: string;
|
|
82
|
+
/** 回调 URL(必须与注册的 redirect_uri 完全一致) */
|
|
83
|
+
redirectUri: string;
|
|
84
|
+
/** SSO 中心地址,如 "https://nihplod.cn" */
|
|
85
|
+
ssoBaseUrl: string;
|
|
86
|
+
/** 请求的 scope(空格分隔),如 "openid profile phone" */
|
|
87
|
+
scopes?: string;
|
|
88
|
+
}
|
|
89
|
+
/** 用户信息 */
|
|
90
|
+
interface SsoUser {
|
|
91
|
+
sub: string;
|
|
92
|
+
nickname?: string;
|
|
93
|
+
avatar?: string;
|
|
94
|
+
phone?: string;
|
|
95
|
+
membership_level?: string;
|
|
96
|
+
total_points?: number;
|
|
97
|
+
}
|
|
98
|
+
/** Token 响应 */
|
|
99
|
+
interface TokenResponse {
|
|
100
|
+
access_token: string;
|
|
101
|
+
token_type: string;
|
|
102
|
+
expires_in: number;
|
|
103
|
+
refresh_token: string;
|
|
104
|
+
id_token?: string;
|
|
105
|
+
}
|
|
106
|
+
/** OIDC Discovery 文档 */
|
|
107
|
+
interface OidcDiscovery {
|
|
108
|
+
issuer: string;
|
|
109
|
+
authorization_endpoint: string;
|
|
110
|
+
token_endpoint: string;
|
|
111
|
+
userinfo_endpoint: string;
|
|
112
|
+
jwks_uri: string;
|
|
113
|
+
introspection_endpoint: string;
|
|
114
|
+
revocation_endpoint?: string;
|
|
115
|
+
end_session_endpoint?: string;
|
|
116
|
+
scopes_supported: string[];
|
|
117
|
+
response_types_supported: string[];
|
|
118
|
+
grant_types_supported: string[];
|
|
119
|
+
code_challenge_methods_supported: string[];
|
|
120
|
+
}
|
|
121
|
+
declare class SsoClient {
|
|
122
|
+
readonly config: SsoClientConfig;
|
|
123
|
+
private _discovery;
|
|
124
|
+
private _discoveryFetchedAt;
|
|
125
|
+
private _refreshLock;
|
|
126
|
+
/** Discovery 文档缓存 TTL(5 分钟) */
|
|
127
|
+
private static readonly DISCOVERY_TTL_MS;
|
|
128
|
+
/** Discovery fetch 超时(10 秒) */
|
|
129
|
+
private static readonly DISCOVERY_TIMEOUT_MS;
|
|
130
|
+
constructor(config: SsoClientConfig);
|
|
131
|
+
/**
|
|
132
|
+
* 获取 OIDC Discovery 文档(带缓存 + 超时)
|
|
133
|
+
*
|
|
134
|
+
* 缓存 5 分钟,超时 10 秒。
|
|
135
|
+
* 失败时返回 null(上层调用方回退到硬编码默认端点)。
|
|
136
|
+
*/
|
|
137
|
+
private _getDiscovery;
|
|
138
|
+
/** 获取 authorize 端点 URL(优先 Discovery,回退默认) */
|
|
139
|
+
private _getAuthorizeEndpoint;
|
|
140
|
+
/** 获取 token 端点 URL(优先 Discovery,回退默认) */
|
|
141
|
+
private _getTokenEndpoint;
|
|
142
|
+
/** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
|
|
143
|
+
private _getUserinfoEndpoint;
|
|
144
|
+
/**
|
|
145
|
+
* 发起 SSO 登录
|
|
146
|
+
*
|
|
147
|
+
* 生成 PKCE code_verifier/code_challenge 和 state 参数,
|
|
148
|
+
* 构建 authorize URL,通过 302 跳转到 SSO 登录页。
|
|
149
|
+
*
|
|
150
|
+
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage)
|
|
151
|
+
*/
|
|
152
|
+
login(returnUrl?: string): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* 构建登录 URL(不跳转,返回 URL 字符串)
|
|
155
|
+
*
|
|
156
|
+
* 适用于需要手动处理跳转的场景。
|
|
157
|
+
*/
|
|
158
|
+
getLoginUrl(returnUrl?: string): Promise<string>;
|
|
159
|
+
/**
|
|
160
|
+
* 处理 OAuth 回调
|
|
161
|
+
*
|
|
162
|
+
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
163
|
+
* 成功后 token 自动保存到 sessionStorage。
|
|
164
|
+
*
|
|
165
|
+
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
166
|
+
* @returns TokenData 或 null
|
|
167
|
+
*/
|
|
168
|
+
handleCallback(callbackUrl: string): Promise<TokenData>;
|
|
169
|
+
/**
|
|
170
|
+
* 刷新 Access Token
|
|
171
|
+
*
|
|
172
|
+
* 使用 refresh_token 换取新的 access_token。
|
|
173
|
+
* 采用互斥锁防止并发刷新。
|
|
174
|
+
* 支持 Refresh Token 原子轮换。
|
|
175
|
+
*/
|
|
176
|
+
refreshToken(): Promise<TokenData>;
|
|
177
|
+
private _doRefreshToken;
|
|
178
|
+
/**
|
|
179
|
+
* 获取用户信息
|
|
180
|
+
*
|
|
181
|
+
* 若 access_token 已过期则自动刷新后再请求。
|
|
182
|
+
*/
|
|
183
|
+
getUserInfo(): Promise<SsoUser>;
|
|
184
|
+
/**
|
|
185
|
+
* 获取当前 access_token
|
|
186
|
+
*
|
|
187
|
+
* 若已过期则自动刷新。用于子项目自行发起 API 请求时获取 Bearer token。
|
|
188
|
+
* 若无 token 返回 null;若刷新失败则抛出错误(与 getUserInfo 行为一致)。
|
|
189
|
+
*/
|
|
190
|
+
getAccessToken(): Promise<string | null>;
|
|
191
|
+
/**
|
|
192
|
+
* 检查是否已认证(不发起网络请求)
|
|
193
|
+
*
|
|
194
|
+
* 仅检查本地是否存在未过期的 access_token。
|
|
195
|
+
*/
|
|
196
|
+
isAuthenticated(): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* 登出
|
|
199
|
+
*
|
|
200
|
+
* 清除本地所有 token 和临时数据,并尝试撤销服务端 refresh_token。
|
|
201
|
+
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false)
|
|
202
|
+
*/
|
|
203
|
+
logout(redirectToSso?: boolean): Promise<void>;
|
|
204
|
+
/**
|
|
205
|
+
* 获取 OIDC Discovery 文档
|
|
206
|
+
*
|
|
207
|
+
* 用于调试和获取 SSO 中心完整配置。
|
|
208
|
+
* 可能返回 null(当 Discovery 端点不可达且无缓存时)。
|
|
209
|
+
*/
|
|
210
|
+
getDiscovery(): Promise<OidcDiscovery | null>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* PKCE (Proof Key for Code Exchange) 工具函数
|
|
215
|
+
*
|
|
216
|
+
* 实现 RFC 7636 规范:
|
|
217
|
+
* - code_verifier: 43-128 字符的随机字符串
|
|
218
|
+
* - code_challenge: SHA-256(code_verifier) 的 base64url 编码
|
|
219
|
+
*
|
|
220
|
+
* 仅支持 S256 模式,不提供 plain 模式。
|
|
221
|
+
*/
|
|
222
|
+
/**
|
|
223
|
+
* 生成 code_verifier
|
|
224
|
+
*
|
|
225
|
+
* 生成 43-128 字符之间的安全随机字符串,
|
|
226
|
+
* 使用 `crypto.getRandomValues` 保证密码学安全性。
|
|
227
|
+
*
|
|
228
|
+
* @param length - 长度(默认 64),范围 43-128
|
|
229
|
+
*/
|
|
230
|
+
declare function generateCodeVerifier(length?: number): string;
|
|
231
|
+
/**
|
|
232
|
+
* 计算 code_challenge (S256)
|
|
233
|
+
*
|
|
234
|
+
* 对 code_verifier 进行 SHA-256 哈希,然后进行 base64url 编码。
|
|
235
|
+
*
|
|
236
|
+
* @param codeVerifier - PKCE code_verifier
|
|
237
|
+
*/
|
|
238
|
+
declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
|
|
239
|
+
/**
|
|
240
|
+
* 生成 state 参数
|
|
241
|
+
*
|
|
242
|
+
* 32 字节 hex 随机字符串,用于 CSRF 防护。
|
|
243
|
+
*/
|
|
244
|
+
declare function generateState(): string;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* SSO SDK 错误类型
|
|
248
|
+
*/
|
|
249
|
+
/** 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
|
+
/** OAuth 2.0 标准错误码 */
|
|
252
|
+
type OAuthErrorCode = "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope" | "access_denied" | "server_error" | "rate_limited";
|
|
253
|
+
/**
|
|
254
|
+
* SSO SDK 自定义错误
|
|
255
|
+
*/
|
|
256
|
+
declare class SsoError extends Error {
|
|
257
|
+
readonly code: SsoErrorCode;
|
|
258
|
+
readonly description: string;
|
|
259
|
+
readonly cause?: unknown;
|
|
260
|
+
constructor(code: SsoErrorCode, description: string, cause?: unknown);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* OAuth 2.0 服务端返回的错误
|
|
264
|
+
*/
|
|
265
|
+
declare class OAuthError extends Error {
|
|
266
|
+
readonly code: OAuthErrorCode;
|
|
267
|
+
readonly description: string;
|
|
268
|
+
readonly uri?: string;
|
|
269
|
+
constructor(code: OAuthErrorCode, description: string, uri?: string);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { OAuthError, type OAuthErrorCode, type OidcDiscovery, SsoClient, type SsoClientConfig, SsoError, type SsoErrorCode, type SsoUser, type TokenData, type TokenResponse, type TokenStorage, clearAllSsoData, clearVerifiersForClients, generateCodeChallenge, generateCodeVerifier, generateState, getOAuthState, getPkceVerifier, getReturnUrl, getTokenData, getTokenStorage, removeOAuthState, removePkceVerifier, removeReturnUrl, removeTokenData, saveOAuthState, savePkceVerifier, saveReturnUrl, saveTokenData, setTokenStorage };
|