@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.
@@ -0,0 +1,301 @@
1
+ import React, { ReactNode, ComponentType } from 'react';
2
+
3
+ /**
4
+ * Token 存储抽象层
5
+ *
6
+ * 默认使用 localStorage,以支持多 Tab 间自动同步 token 并避免并发刷新。
7
+ * 对 XSS 敏感的子项目可通过 setTokenStorage() 注入更安全的自定义实现
8
+ *(如内存存储、Service Worker 封装、或带加密的 storage)。
9
+ *
10
+ * 多 client 隔离:
11
+ * - token / state / return_url 均支持按 clientId 隔离 key
12
+ * - 不传 clientId 时使用全局 key,保持向后兼容
13
+ */
14
+ /** Token 数据 */
15
+ interface TokenData {
16
+ access_token: string;
17
+ token_type: string;
18
+ expires_in: number;
19
+ refresh_token: string;
20
+ id_token?: string;
21
+ /** Token 签发时间(epoch ms),用于计算过期 */
22
+ issued_at: number;
23
+ /** 访问令牌实际过期时间(epoch ms)= issued_at + expires_in * 1000 */
24
+ expires_at: number;
25
+ }
26
+
27
+ /**
28
+ * SSO Client 核心类
29
+ *
30
+ * 封装完整的 OAuth 2.0 授权码 + PKCE S256 流程:
31
+ * - login(): 发起授权请求
32
+ * - handleCallback(): 处理回调,交换 token
33
+ * - refreshToken(): 刷新 access token(原子轮换)
34
+ * - getUserInfo(): 获取用户信息
35
+ * - logout(): 登出
36
+ * - isAuthenticated(): 检查认证状态
37
+ */
38
+
39
+ /** SSO 客户端配置 */
40
+ interface SsoClientConfig {
41
+ /** OAuth Client ID(从管理后台获取) */
42
+ clientId: string;
43
+ /** OAuth Client Secret(从管理后台获取)
44
+ *
45
+ * ⚠️ 安全提醒:client_secret 不应暴露在前端代码中。
46
+ * 对于浏览器端 SPA(Public Client),应省略此字段;SDK 会使用 PKCE 完成授权,
47
+ * 且 token 端点不会发送 client_secret。
48
+ * 对于 BFF/Next.js 等 Confidential Client,可传入 client_secret。
49
+ */
50
+ clientSecret?: string;
51
+ /** 回调 URL(必须与注册的 redirect_uri 完全一致) */
52
+ redirectUri: string;
53
+ /** SSO 中心地址,如 "https://nihplod.cn" */
54
+ ssoBaseUrl: string;
55
+ /** 请求的 scope(空格分隔),如 "openid profile phone" */
56
+ scopes?: string;
57
+ }
58
+ /** 用户信息 */
59
+ interface SsoUser {
60
+ sub: string;
61
+ nickname?: string;
62
+ avatar?: string;
63
+ phone?: string;
64
+ membership_level?: string;
65
+ total_points?: number;
66
+ }
67
+ /** OIDC Discovery 文档 */
68
+ interface OidcDiscovery {
69
+ issuer: string;
70
+ authorization_endpoint: string;
71
+ token_endpoint: string;
72
+ userinfo_endpoint: string;
73
+ jwks_uri: string;
74
+ introspection_endpoint: string;
75
+ revocation_endpoint?: string;
76
+ end_session_endpoint?: string;
77
+ scopes_supported: string[];
78
+ response_types_supported: string[];
79
+ grant_types_supported: string[];
80
+ code_challenge_methods_supported: string[];
81
+ }
82
+ declare class SsoClient {
83
+ readonly config: SsoClientConfig;
84
+ private _discovery;
85
+ private _discoveryFetchedAt;
86
+ private _refreshLock;
87
+ /** Discovery 文档缓存 TTL(5 分钟) */
88
+ private static readonly DISCOVERY_TTL_MS;
89
+ /** Discovery fetch 超时(10 秒) */
90
+ private static readonly DISCOVERY_TIMEOUT_MS;
91
+ constructor(config: SsoClientConfig);
92
+ /**
93
+ * 获取 OIDC Discovery 文档(带缓存 + 超时)
94
+ *
95
+ * 缓存 5 分钟,超时 10 秒。
96
+ * 失败时返回 null(上层调用方回退到硬编码默认端点)。
97
+ */
98
+ private _getDiscovery;
99
+ /** 获取 authorize 端点 URL(优先 Discovery,回退默认) */
100
+ private _getAuthorizeEndpoint;
101
+ /** 获取 token 端点 URL(优先 Discovery,回退默认) */
102
+ private _getTokenEndpoint;
103
+ /** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
104
+ private _getUserinfoEndpoint;
105
+ /**
106
+ * 发起 SSO 登录
107
+ *
108
+ * 生成 PKCE code_verifier/code_challenge 和 state 参数,
109
+ * 构建 authorize URL,通过 302 跳转到 SSO 登录页。
110
+ *
111
+ * @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage)
112
+ */
113
+ login(returnUrl?: string): Promise<void>;
114
+ /**
115
+ * 构建登录 URL(不跳转,返回 URL 字符串)
116
+ *
117
+ * 适用于需要手动处理跳转的场景。
118
+ */
119
+ getLoginUrl(returnUrl?: string): Promise<string>;
120
+ /**
121
+ * 处理 OAuth 回调
122
+ *
123
+ * 解析回调 URL,校验 state 参数,用授权码交换 token。
124
+ * 成功后 token 自动保存到 sessionStorage。
125
+ *
126
+ * @param callbackUrl - 完整的回调 URL(window.location.href)
127
+ * @returns TokenData 或 null
128
+ */
129
+ handleCallback(callbackUrl: string): Promise<TokenData>;
130
+ /**
131
+ * 刷新 Access Token
132
+ *
133
+ * 使用 refresh_token 换取新的 access_token。
134
+ * 采用互斥锁防止并发刷新。
135
+ * 支持 Refresh Token 原子轮换。
136
+ */
137
+ refreshToken(): Promise<TokenData>;
138
+ private _doRefreshToken;
139
+ /**
140
+ * 获取用户信息
141
+ *
142
+ * 若 access_token 已过期则自动刷新后再请求。
143
+ */
144
+ getUserInfo(): Promise<SsoUser>;
145
+ /**
146
+ * 获取当前 access_token
147
+ *
148
+ * 若已过期则自动刷新。用于子项目自行发起 API 请求时获取 Bearer token。
149
+ * 若无 token 返回 null;若刷新失败则抛出错误(与 getUserInfo 行为一致)。
150
+ */
151
+ getAccessToken(): Promise<string | null>;
152
+ /**
153
+ * 检查是否已认证(不发起网络请求)
154
+ *
155
+ * 仅检查本地是否存在未过期的 access_token。
156
+ */
157
+ isAuthenticated(): boolean;
158
+ /**
159
+ * 登出
160
+ *
161
+ * 清除本地所有 token 和临时数据,并尝试撤销服务端 refresh_token。
162
+ * @param redirectToSso - 是否重定向到 SSO 登出页(默认 false)
163
+ */
164
+ logout(redirectToSso?: boolean): Promise<void>;
165
+ /**
166
+ * 获取 OIDC Discovery 文档
167
+ *
168
+ * 用于调试和获取 SSO 中心完整配置。
169
+ * 可能返回 null(当 Discovery 端点不可达且无缓存时)。
170
+ */
171
+ getDiscovery(): Promise<OidcDiscovery | null>;
172
+ }
173
+
174
+ /**
175
+ * SsoProvider - React Context Provider
176
+ *
177
+ * 包裹子项目根组件,提供全局 SSO 认证状态管理:
178
+ * - 自动管理 token 刷新定时器(过期前 60s 静默刷新)
179
+ * - 监听 storage 事件实现跨 Tab 同步
180
+ * - 提供 useSso() hook
181
+ */
182
+
183
+ interface SsoContextValue {
184
+ /** 当前用户信息 */
185
+ user: SsoUser | null;
186
+ /** 是否已认证 */
187
+ isAuthenticated: boolean;
188
+ /** 是否正在加载(初始化/刷新中) */
189
+ isLoading: boolean;
190
+ /** 发起登录 */
191
+ login: (returnUrl?: string) => Promise<void>;
192
+ /** 登出 */
193
+ logout: (redirectToSso?: boolean) => Promise<void>;
194
+ /** 刷新用户信息 */
195
+ refreshUser: () => Promise<void>;
196
+ /** 获取 access_token(自动刷新过期 token) */
197
+ getAccessToken: () => Promise<string | null>;
198
+ /** SsoClient 实例(高级用法) */
199
+ client: SsoClient;
200
+ }
201
+ interface SsoProviderProps {
202
+ /** SSO 客户端配置 */
203
+ config: SsoClientConfig;
204
+ /** 子组件 */
205
+ children: ReactNode;
206
+ /**
207
+ * 自动刷新阈值(秒)
208
+ * access_token 过期前多少秒触发静默刷新。
209
+ * 默认 60 秒。
210
+ */
211
+ refreshThreshold?: number;
212
+ /**
213
+ * API 请求函数(可选)
214
+ * 用于在 token 刷新后自动重试失败的 API 请求。
215
+ */
216
+ onTokenRefreshed?: (token: string) => void;
217
+ }
218
+ declare function SsoProvider({ config, children, refreshThreshold, onTokenRefreshed, }: SsoProviderProps): React.FunctionComponentElement<React.ProviderProps<SsoContextValue | null>>;
219
+ /**
220
+ * useSso Hook
221
+ *
222
+ * 在 SsoProvider 内部使用,获取 SSO 认证状态和操作方法。
223
+ *
224
+ * @example
225
+ * ```tsx
226
+ * function MyComponent() {
227
+ * const { user, isAuthenticated, login, logout } = useSso();
228
+ * if (!isAuthenticated) return <button onClick={() => login()}>登录</button>;
229
+ * return <div>欢迎, {user?.nickname}</div>;
230
+ * }
231
+ * ```
232
+ */
233
+ declare function useSso(): SsoContextValue;
234
+
235
+ /**
236
+ * RequireAuth / withAuth — 路由保护组件
237
+ *
238
+ * 用法:
239
+ * - <RequireAuth> 包裹需要登录才能访问的路由
240
+ * - withAuth(Component) HOC 包装单个页面组件
241
+ */
242
+
243
+ interface RequireAuthProps {
244
+ /** 子组件(受保护的内容) */
245
+ children: React.ReactNode;
246
+ /** 未登录时的回退内容(默认显示加载中) */
247
+ fallback?: React.ReactNode;
248
+ /** 是否在检测到未登录时自动发起登录跳转 */
249
+ autoLogin?: boolean;
250
+ }
251
+ /**
252
+ * 路由保护组件
253
+ *
254
+ * 未登录时自动触发登录跳转或显示回退内容。
255
+ *
256
+ * @example
257
+ * ```tsx
258
+ * <RequireAuth>
259
+ * <Dashboard />
260
+ * </RequireAuth>
261
+ * ```
262
+ */
263
+ declare function RequireAuth({ children, fallback, autoLogin, }: RequireAuthProps): string | number | bigint | true | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.FunctionComponentElement<React.FragmentProps>;
264
+ /**
265
+ * 高阶组件:包装页面组件,要求认证后才能访问
266
+ *
267
+ * @example
268
+ * ```tsx
269
+ * function DashboardPage() { return <div>Dashboard</div>; }
270
+ * export default withAuth(DashboardPage);
271
+ * ```
272
+ */
273
+ declare function withAuth<P extends object>(Component: ComponentType<P>): ComponentType<P>;
274
+
275
+ /**
276
+ * CallbackPage — 通用 OAuth 回调页面组件
277
+ *
278
+ * 子项目只需在回调路由渲染此组件即可完成 code → token 交换。
279
+ * 交换成功后自动跳转到 returnUrl 或首页。
280
+ *
281
+ * @example
282
+ * ```tsx
283
+ * // 在回调页面路由中:
284
+ * import { CallbackPage } from "@nihplod/sso-sdk/react";
285
+ * export default function AuthCallback() {
286
+ * return <CallbackPage />;
287
+ * }
288
+ * ```
289
+ */
290
+
291
+ declare function CallbackPage(): React.DetailedReactHTMLElement<{
292
+ style: {
293
+ display: "flex";
294
+ alignItems: "center";
295
+ justifyContent: "center";
296
+ minHeight: string;
297
+ fontFamily: "system-ui, sans-serif";
298
+ };
299
+ }, HTMLElement> | null;
300
+
301
+ export { CallbackPage, RequireAuth, SsoProvider, useSso, withAuth };