@lark-apaas/auth-sdk 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
8
+ IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
9
+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
11
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,351 @@
1
+ ## @lark-apaas/auth-sdk
2
+
3
+ 基于 CASL 的前端鉴权 SDK,封装了权限数据获取、Ability 初始化与 React 集成,帮助你在应用中便捷地做「功能级/按钮级/菜单级/页面级」的权限控制。
4
+
5
+ ### 安装
6
+
7
+ ```bash
8
+ npm i @lark-apaas/auth-sdk
9
+ # 或者
10
+ yarn add @lark-apaas/auth-sdk
11
+ ```
12
+
13
+ ### 快速开始
14
+
15
+ ```tsx
16
+ import React from 'react';
17
+ import { AuthProvider, Can, useAbility } from '@lark-apaas/auth-sdk';
18
+
19
+ export default function App() {
20
+ return (
21
+ <AuthProvider
22
+ config={{
23
+ permissionApi: {
24
+ baseUrl: 'http://localhost:3000',
25
+ endpoint: '/api/users/:userId/permissions',
26
+ // 可选:apiToken、timeout、headers、withCredentials
27
+ },
28
+ autoFetch: true,
29
+ onError: (e) => console.error(e),
30
+ }}
31
+ >
32
+ <Home />
33
+ </AuthProvider>
34
+ );
35
+ }
36
+
37
+ function Home() {
38
+ const ability = useAbility();
39
+ return (
40
+ <div>
41
+ <Can I="read" a="Dashboard">
42
+ <div>可见的仪表盘</div>
43
+ </Can>
44
+ <button disabled={ability.cannot('create', 'Task')}>创建任务</button>
45
+ </div>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 核心 API
53
+
54
+ ### AuthProvider
55
+ - **作用**: 提供 `Ability` 与权限状态上下文,自动/手动拉取权限。
56
+ - **Props**:
57
+ - `config?: AuthSdkConfig`
58
+ - `noPermissionPath?: string` 无权限时的导航路径
59
+ - `permissionApi?: PermissionApiConfig` 拉取权限接口配置
60
+ - `baseUrl?: string`
61
+ - `endpoint?: string`,支持 `:userId` 占位
62
+ - `timeout?: number`,默认 5000ms
63
+ - `headers?: Record<string,string>`
64
+ - `apiToken?: string` 自动加到 Authorization 头
65
+ - `withCredentials?: boolean`,默认 true
66
+ - `autoFetch?: boolean` 是否初始化自动拉取
67
+ - `onError?: (error: Error) => void`
68
+ - `onSuccess?: (data: PermissionApiResponse) => void`
69
+ - `children: React.ReactNode`
70
+
71
+ 示例:
72
+ ```tsx
73
+ <AuthProvider userId="user-123" config={{
74
+ permissionApi: { endpoint: '/api/permissions' },
75
+ autoFetch: true,
76
+ }}>
77
+ <App />
78
+ {/* Can/Hook 在其子树中生效 */}
79
+ </AuthProvider>
80
+ ```
81
+
82
+ ### useAuth
83
+ - **作用**: 访问权限状态与方法。
84
+ - **返回**:
85
+ - `permissions: Permission[]`
86
+ - `roles: string[]`
87
+ - `userId: string | null`
88
+ - `setUserId(userId: string): void`
89
+ - `isLoading: boolean`
90
+ - `error: Error | null`
91
+ - `fetchPermissions(userId?: string): Promise<void>` 手动拉取
92
+ - `refetch(): Promise<void>` 按当前 `userId` 重新拉取
93
+
94
+ ```tsx
95
+ const { permissions, roles, isLoading, refetch } = useAuth();
96
+ ```
97
+
98
+ ### useAbility
99
+ - **作用**: 获取 CASL `Ability` 实例,使用 `ability.can(action, subject)` 做任意判断。
100
+
101
+ ```tsx
102
+ const ability = useAbility();
103
+ const canCreate = ability.can('create', 'Task');
104
+ ```
105
+
106
+ ### Can 组件(来自 @casl/react 的 Contextual Can)
107
+ - **作用**: 条件渲染,只有当 `I` 对 `a` 可操作时才渲染子内容;也支持 render prop。
108
+
109
+ ```tsx
110
+ <Can I="delete" a="Task">
111
+ <button>删除任务</button>
112
+ </Can>
113
+
114
+ <Can I="download" a="Report">{(allowed) => (
115
+ <button disabled={!allowed}>下载报表</button>
116
+ )}</Can>
117
+ ```
118
+
119
+ ### 批量权限判断:useCanPermission / CanPermission
120
+ - `useCanPermission({ permissions, or })`:一次检查多个权限;`or=true` 表示任一满足即可,否则默认全部需要满足。
121
+ - `CanPermission`:同上但以组件方式使用,支持 children 或 render prop。
122
+
123
+ ```tsx
124
+ const allowed = useCanPermission({
125
+ permissions: [
126
+ { action: 'read', subject: 'Report' },
127
+ { action: 'download', subject: 'Report' },
128
+ ],
129
+ or: false,
130
+ });
131
+
132
+ <CanPermission
133
+ permissions={[{ action: 'manage', subject: 'Task' }, { action: 'delete', subject: 'Task' }]}>
134
+ <BulkActions />
135
+ </CanPermission>
136
+ ```
137
+
138
+ ### 角色判断:useCanRole / CanRole
139
+ - 角色被映射为特殊 subject `@role` 下的 action。
140
+ - `useCanRole({ roles, and })`:是否拥有指定多个角色;`and=true` 表示需要同时具备。
141
+ - `CanRole`:以组件形式使用。
142
+
143
+ ```tsx
144
+ const isAdmin = useCanRole({ roles: ['admin_role'] });
145
+
146
+ <CanRole roles={[ 'admin_role', 'ops_role' ]} and>
147
+ <DangerZone />
148
+ </CanRole>
149
+ ```
150
+
151
+ ### 路由守卫:CanRoute
152
+ - **作用**: 路由级别的权限守卫,支持权限和角色双重检查,无权限时可自动重定向。
153
+ - **Props**:
154
+ - `permissions?: Array<{ action: string; subject: string }>` 需要检查的权限列表
155
+ - `roles?: string[]` 需要检查的角色列表
156
+ - `children: React.ReactNode` 受保护的内容
157
+ - `fallback?: React.ReactNode` 无权限时渲染的内容(优先级最高)
158
+ - `redirectTo?: string` 无权限时重定向的路径(优先级次之)
159
+ - 如果既没有 `fallback` 也没有 `redirectTo`,会尝试使用 `AuthProvider` 配置的 `noPermissionPath`
160
+ - 如果都没有配置,则返回 `null`
161
+
162
+ ```tsx
163
+ import { CanRoute } from '@lark-apaas/auth-sdk';
164
+ import { Routes, Route } from 'react-router-dom';
165
+
166
+ // 基础用法:权限检查
167
+ <CanRoute permissions={[{ action: 'read', subject: 'Dashboard' }]}>
168
+ <Dashboard />
169
+ </CanRoute>
170
+
171
+ // 角色检查
172
+ <CanRoute roles={['admin', 'manager']}>
173
+ <AdminPanel />
174
+ </CanRoute>
175
+
176
+ // 自定义重定向
177
+ <CanRoute
178
+ permissions={[{ action: 'manage', subject: 'User' }]}
179
+ redirectTo="/unauthorized"
180
+ >
181
+ <UserManagement />
182
+ </CanRoute>
183
+
184
+ // 自定义 fallback 内容
185
+ <CanRoute
186
+ roles={['admin']}
187
+ fallback={<div>您没有管理员权限</div>}
188
+ >
189
+ <AdminSettings />
190
+ </CanRoute>
191
+
192
+ // 在路由中使用
193
+ <Routes>
194
+ <Route path="/admin" element={
195
+ <CanRoute permissions={[{ action: 'manage', subject: 'Task' }]}>
196
+ <AdminPage />
197
+ </CanRoute>
198
+ } />
199
+ </Routes>
200
+ ```
201
+
202
+ ### PermissionClient
203
+ - **作用**: 独立的权限数据获取客户端,供非 React 场景(或自定义状态管理)使用。
204
+ - **方法**:
205
+ - `constructor(config?: PermissionApiConfig)`
206
+ - `fetchPermissions(userId?: string): Promise<PermissionApiResponse>`
207
+ - `updateConfig(partial: Partial<PermissionApiConfig>): void`
208
+ - `getConfig(): PermissionApiConfig`
209
+
210
+ ```ts
211
+ import { PermissionClient } from '@lark-apaas/auth-sdk';
212
+
213
+ const client = new PermissionClient({ baseUrl: '/api', endpoint: '/users/:userId/permissions' });
214
+ const data = await client.fetchPermissions('user-123');
215
+ ```
216
+
217
+ ### 能力工厂(与 CASL 交互)
218
+ - `createAbility({ permissions?: Permission[], roles?: string[] }): Ability`
219
+ - `updateAbility(ability: Ability, { permissions?: Permission[], roles?: string[] }): void`
220
+ - `convertPermissionsToRules(permissions: Permission[], roles: string[]): CaslRule[]`
221
+
222
+ 适用于需要在 React 之外构建/更新 `Ability` 的场景,或做 SSR/单元测试。
223
+
224
+ ```ts
225
+ import { createAbility, updateAbility } from '@lark-apaas/auth-sdk';
226
+
227
+ const ability = createAbility({ permissions: [], roles: ['admin_role'] });
228
+ // 后续有新权限
229
+ updateAbility(ability, { permissions: [{ id: 'p1', name: 'Task Read', sub: 'Task', actions: ['read'] }], roles: [] });
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 类型与再导出
235
+ - 从本包导出的类型:`Action`、`Subject`、`Permission`、`UserRole`、`PermissionApiResponse`、`PermissionApiConfig`、`AuthSdkConfig`、`CaslRule`。
236
+ - 便捷再导出:`Ability`、`AbilityBuilder`、`AbilityClass`(来自 `@casl/ability`)。
237
+
238
+ ---
239
+
240
+ ## 集成建议与最佳实践
241
+ - **权限接口返回**:`{ userId, roles: (string|UserRole)[], permissions: Permission[] }`,其中 `roles` 支持字符串或对象;SDK 会标准化为字符串数组。
242
+ - **错误处理**:实现 `onError` 上报或提示;`onSuccess` 可做埋点。
243
+ - **渲染时机**:根据 `useAuth()` 的 `isLoading`/`error` 渲染 Loading/Error 页,避免闪烁。
244
+ - **与路由结合**:使用 `CanRoute` 组件做页面级访问控制,支持自动重定向和自定义 fallback 内容。
245
+ - **路由守卫最佳实践**:
246
+ - 优先使用 `fallback` 属性展示友好的无权限提示
247
+ - 使用 `redirectTo` 重定向到专门的错误页面
248
+ - 在 `AuthProvider` 中配置全局的 `noPermissionPath` 作为兜底
249
+
250
+ ---
251
+
252
+ ## 进阶示例
253
+
254
+ ### 菜单按权限过滤
255
+ ```tsx
256
+ import { useAbility } from '@lark-apaas/auth-sdk';
257
+
258
+ const menus = [
259
+ { name: 'Dashboard', path: '/dashboard', p: { action: 'read', subject: 'Dashboard' } },
260
+ { name: 'Users', path: '/users', p: { action: 'read', subject: 'User' } },
261
+ { name: 'Settings', path: '/settings', p: { action: 'manage', subject: 'Settings' } },
262
+ ];
263
+
264
+ function Nav() {
265
+ const ability = useAbility();
266
+ return (
267
+ <nav>
268
+ {menus.map(m => ability.can(m.p.action, m.p.subject) && (
269
+ <a key={m.path} href={m.path}>{m.name}</a>
270
+ ))}
271
+ </nav>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ### 批量操作区
277
+ ```tsx
278
+ import { BatchCan } from '@lark-apaas/auth-sdk';
279
+
280
+ <BatchCan permissions={[{ action: 'delete', subject: 'Task' }, { action: 'manage', subject: 'Task' }]}>
281
+ <div className="bulk-actions">
282
+ <button>批量删除</button>
283
+ <button>导出全部</button>
284
+ </div>
285
+ </BatchCan>
286
+ ```
287
+
288
+ ### 路由级权限控制
289
+ ```tsx
290
+ import { CanRoute } from '@lark-apaas/auth-sdk';
291
+ import { Routes, Route, Navigate } from 'react-router-dom';
292
+
293
+ function App() {
294
+ return (
295
+ <Routes>
296
+ {/* 公开路由 */}
297
+ <Route path="/" element={<HomePage />} />
298
+ <Route path="/login" element={<LoginPage />} />
299
+
300
+ {/* 受保护的路由 */}
301
+ <Route path="/dashboard" element={
302
+ <CanRoute permissions={[{ action: 'read', subject: 'Dashboard' }]}>
303
+ <Dashboard />
304
+ </CanRoute>
305
+ } />
306
+
307
+ {/* 管理员路由 */}
308
+ <Route path="/admin" element={
309
+ <CanRoute
310
+ roles={['admin']}
311
+ fallback={<Navigate to="/unauthorized" replace />}
312
+ >
313
+ <AdminPanel />
314
+ </CanRoute>
315
+ } />
316
+
317
+ {/* 多权限检查 */}
318
+ <Route path="/reports" element={
319
+ <CanRoute
320
+ permissions={[
321
+ { action: 'read', subject: 'Report' },
322
+ { action: 'export', subject: 'Report' }
323
+ ]}
324
+ redirectTo="/no-access"
325
+ >
326
+ <ReportsPage />
327
+ </CanRoute>
328
+ } />
329
+
330
+ {/* 错误页面 */}
331
+ <Route path="/unauthorized" element={<UnauthorizedPage />} />
332
+ <Route path="/no-access" element={<NoAccessPage />} />
333
+ </Routes>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## 常见问题(FAQ)
341
+ - **如何做角色判断?** 使用 `useCanRole`/`CanRole`;角色被映射为对特殊 subject `@role` 的 action。
342
+ - **如何使用路由守卫?** 使用 `CanRoute` 组件包装需要保护的路由内容,支持权限和角色双重检查。
343
+ - **CanRoute 的重定向优先级?** `fallback` > `redirectTo` > `noPermissionPath`(AuthProvider 配置)> `null`。
344
+ - **CanRoute 需要 react-router-dom 依赖吗?** 是的,因为使用了 `Navigate` 和 `useLocation`,请确保项目中已安装。
345
+
346
+ ---
347
+
348
+ ## 许可
349
+ MIT
350
+
351
+
@@ -0,0 +1,158 @@
1
+ import React from 'react';
2
+ import { Ability } from '@casl/ability';
3
+ import type { AuthSdkConfig, Permission } from './types';
4
+ /**
5
+ * Ability Context - 用于在组件树中共享 Ability 实例
6
+ */
7
+ export declare const AbilityContext: React.Context<Ability<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>>;
8
+ /** 权限点位鉴权相关的 Hooks 和组件 */
9
+ /**
10
+ * Can 组件 - 基于 Context 的条件渲染组件
11
+ * 使用 @casl/react 的 createContextualCan 创建,复用 CASL 的官方实现
12
+ */
13
+ export declare const Can: React.FunctionComponent<import("@casl/react").BoundCanProps<Ability<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>>>;
14
+ export interface UseCanPermissionProps {
15
+ permissions?: Array<{
16
+ action: string;
17
+ subject: string;
18
+ }>;
19
+ or?: boolean;
20
+ }
21
+ export interface CanPermissionProps extends UseCanPermissionProps {
22
+ children: React.ReactNode | ((allowed: boolean) => React.ReactNode);
23
+ }
24
+ export declare const useCanPermission: ({ permissions, or }: UseCanPermissionProps) => boolean;
25
+ export declare function CanPermission({ permissions, children, or }: {
26
+ permissions: Array<{
27
+ action: string;
28
+ subject: string;
29
+ }>;
30
+ children: React.ReactNode | ((allowed: boolean) => React.ReactNode);
31
+ or?: boolean;
32
+ }): import("react/jsx-runtime").JSX.Element | null;
33
+ /** 角色鉴权相关的 Hooks 和组件 */
34
+ export interface UseCanRoleProps {
35
+ roles?: string[];
36
+ and?: boolean;
37
+ }
38
+ export declare const useCanRole: ({ roles, and }: UseCanRoleProps) => boolean;
39
+ export interface CanRoleProps extends UseCanRoleProps {
40
+ children: React.ReactNode | ((allowed: boolean) => React.ReactNode);
41
+ }
42
+ export declare const CanRole: ({ roles, and, children }: CanRoleProps) => import("react/jsx-runtime").JSX.Element | null;
43
+ /**
44
+ * Auth 状态 Context 类型定义
45
+ * 存储权限数据和加载状态
46
+ */
47
+ interface AuthStateContextValue {
48
+ noPermissionPath?: string;
49
+ permissions: Permission[];
50
+ roles: string[];
51
+ userId: string | null;
52
+ setUserId: (userId: string) => void;
53
+ isLoading: boolean;
54
+ error: Error | null;
55
+ fetchPermissions: (userId?: string) => Promise<void>;
56
+ refetch: () => Promise<void>;
57
+ }
58
+ /**
59
+ * Auth Provider Props
60
+ */
61
+ export interface AuthProviderProps {
62
+ children: React.ReactNode;
63
+ config?: AuthSdkConfig;
64
+ userId?: string;
65
+ }
66
+ /**
67
+ * Auth Provider 组件
68
+ *
69
+ * 提供 Ability 实例和权限数据到组件树
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { AuthProvider } from '@lark-apaas/auth-sdk';
74
+ *
75
+ * function App() {
76
+ * return (
77
+ * <AuthProvider userId="user-123" config={{
78
+ * permissionApi: {
79
+ * baseUrl: 'http://localhost:3000',
80
+ * endpoint: '/mock-api/users/:userId/permissions'
81
+ * }
82
+ * }}>
83
+ * <YourApp />
84
+ * </AuthProvider>
85
+ * );
86
+ * }
87
+ * ```
88
+ */
89
+ export declare function AuthProvider({ children, config }: AuthProviderProps): import("react/jsx-runtime").JSX.Element;
90
+ /**
91
+ * useAuth Hook - 获取权限数据和加载状态
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * import { useAuth, useAbility } from '@lark-apaas/auth-sdk';
96
+ *
97
+ * function MyComponent() {
98
+ * const { permissions, isLoading } = useAuth();
99
+ * const ability = useAbility();
100
+ *
101
+ * if (isLoading) return <div>Loading...</div>;
102
+ *
103
+ * return (
104
+ * <div>
105
+ * {ability.can('read', 'Task') && <TaskList />}
106
+ * </div>
107
+ * );
108
+ * }
109
+ * ```
110
+ */
111
+ export declare function useAuth(): AuthStateContextValue;
112
+ /**
113
+ * useAbility Hook - 获取 Ability 实例
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * import { useAbility } from '@lark-apaas/auth-sdk';
118
+ *
119
+ * function MyComponent() {
120
+ * const ability = useAbility();
121
+ *
122
+ * return (
123
+ * <button disabled={ability.cannot('create', 'Task')}>
124
+ * Create Task
125
+ * </button>
126
+ * );
127
+ * }
128
+ * ```
129
+ */
130
+ export declare function useAbility(): Ability;
131
+ /**
132
+ * usePermissions Hook - 获取权限列表
133
+ *
134
+ * @example
135
+ * ```tsx
136
+ * function MyComponent() {
137
+ * const permissions = usePermissions();
138
+ * return <div>You have {permissions.length} permissions</div>;
139
+ * }
140
+ * ```
141
+ */
142
+ export declare function usePermissions(): Permission[];
143
+ /**
144
+ * useRoles Hook - 获取角色列表
145
+ *
146
+ * @example
147
+ * ```tsx
148
+ * function MyComponent() {
149
+ * const roles = useRoles();
150
+ * return <div>Your roles: {roles.join(', ')}</div>;
151
+ * }
152
+ * ```
153
+ */
154
+ export declare function useRoles(): string[];
155
+ export declare function useUserId(): [string | null, (userId: string) => void];
156
+ export declare function useNoPermissionPath(): string | undefined;
157
+ export {};
158
+ //# sourceMappingURL=AuthProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useEffect, useState } from "react";
3
+ import { createContextualCan } from "@casl/react";
4
+ import { ROLE_SUBJECT, createAbility, updateAbility } from "./ability-factory.js";
5
+ import { PermissionClient } from "./permission-client.js";
6
+ const AbilityContext = /*#__PURE__*/ createContext(createAbility({
7
+ permissions: [],
8
+ roles: []
9
+ }));
10
+ const Can = createContextualCan(AbilityContext.Consumer);
11
+ const useCanPermission = function({ permissions, or }) {
12
+ const ability = useAbility();
13
+ let allowed = false;
14
+ allowed = or ? !permissions || !permissions.length || permissions.length > 0 && permissions.some(({ action, subject })=>ability.can(action, subject)) : !permissions || !permissions.length || permissions.length > 0 && permissions.every(({ action, subject })=>ability.can(action, subject));
15
+ return !!allowed;
16
+ };
17
+ function CanPermission({ permissions, children, or }) {
18
+ const allowed = useCanPermission({
19
+ permissions,
20
+ or
21
+ });
22
+ if ('function' == typeof children) return /*#__PURE__*/ jsx(Fragment, {
23
+ children: children(allowed)
24
+ });
25
+ return allowed ? /*#__PURE__*/ jsx(Fragment, {
26
+ children: children
27
+ }) : null;
28
+ }
29
+ const useCanRole = function({ roles, and }) {
30
+ const ability = useAbility();
31
+ let allowed = false;
32
+ allowed = and ? !roles || 0 === roles.length || roles.length > 0 && roles.every((role)=>ability.can(role, ROLE_SUBJECT)) : !roles || 0 === roles.length || roles.length > 0 && roles.some((role)=>ability.can(role, ROLE_SUBJECT));
33
+ return !!allowed;
34
+ };
35
+ const CanRole = function({ roles, and, children }) {
36
+ const allowed = useCanRole({
37
+ roles,
38
+ and
39
+ });
40
+ if ('function' == typeof children) return /*#__PURE__*/ jsx(Fragment, {
41
+ children: children(allowed)
42
+ });
43
+ return allowed ? /*#__PURE__*/ jsx(Fragment, {
44
+ children: children
45
+ }) : null;
46
+ };
47
+ const AuthStateContext = /*#__PURE__*/ createContext(null);
48
+ function AuthProvider({ children, config }) {
49
+ const [ability] = useState(()=>createAbility({}));
50
+ const [permissions, setPermissions] = useState([]);
51
+ const [roles, setRoles] = useState([]);
52
+ const [userId, setUserId] = useState(null);
53
+ const [isLoading, setIsLoading] = useState(false);
54
+ const [error, setError] = useState(null);
55
+ const [client] = useState(()=>new PermissionClient(config?.permissionApi));
56
+ const fetchPermissions = useCallback(async (uid)=>{
57
+ setIsLoading(true);
58
+ setError(null);
59
+ try {
60
+ const data = await client.fetchPermissions(uid);
61
+ const normalizedRoles = data.roles.map((role)=>'string' == typeof role ? role : role.name);
62
+ setPermissions(data.permissions);
63
+ setRoles(normalizedRoles);
64
+ setUserId(data.userId);
65
+ updateAbility(ability, {
66
+ permissions: data.permissions,
67
+ roles: normalizedRoles
68
+ });
69
+ config?.onSuccess?.(data);
70
+ } catch (err) {
71
+ const error = err instanceof Error ? err : new Error(String(err));
72
+ setError(error);
73
+ config?.onError?.(error);
74
+ } finally{
75
+ setIsLoading(false);
76
+ }
77
+ }, [
78
+ ability,
79
+ client,
80
+ config
81
+ ]);
82
+ const refetch = useCallback(async ()=>{
83
+ await fetchPermissions(userId);
84
+ }, [
85
+ userId,
86
+ fetchPermissions
87
+ ]);
88
+ useEffect(()=>{
89
+ if (config?.autoFetch !== false) fetchPermissions();
90
+ }, [
91
+ config?.autoFetch,
92
+ fetchPermissions
93
+ ]);
94
+ const stateContextValue = {
95
+ permissions,
96
+ roles,
97
+ userId,
98
+ setUserId,
99
+ noPermissionPath: config?.noPermissionPath,
100
+ isLoading,
101
+ error,
102
+ fetchPermissions,
103
+ refetch
104
+ };
105
+ return /*#__PURE__*/ jsx(AbilityContext.Provider, {
106
+ value: ability,
107
+ children: /*#__PURE__*/ jsx(AuthStateContext.Provider, {
108
+ value: stateContextValue,
109
+ children: children
110
+ })
111
+ });
112
+ }
113
+ function useAuth() {
114
+ const context = useContext(AuthStateContext);
115
+ if (!context) throw new Error('useAuth must be used within an AuthProvider');
116
+ return context;
117
+ }
118
+ function useAbility() {
119
+ return useContext(AbilityContext);
120
+ }
121
+ function usePermissions() {
122
+ const { permissions } = useAuth();
123
+ return permissions;
124
+ }
125
+ function useRoles() {
126
+ const { roles } = useAuth();
127
+ return roles;
128
+ }
129
+ function useUserId() {
130
+ const { userId, setUserId } = useAuth();
131
+ return [
132
+ userId,
133
+ setUserId
134
+ ];
135
+ }
136
+ function useNoPermissionPath() {
137
+ const { noPermissionPath } = useAuth();
138
+ return noPermissionPath;
139
+ }
140
+ export { AbilityContext, AuthProvider, Can, CanPermission, CanRole, useAbility, useAuth, useCanPermission, useCanRole, useNoPermissionPath, usePermissions, useRoles, useUserId };
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * CanRoute 组件 Props
4
+ */
5
+ export interface CanRouteProps {
6
+ permissions?: Array<{
7
+ action: string;
8
+ subject: string;
9
+ }>;
10
+ roles?: string[];
11
+ children: ReactNode;
12
+ redirectTo?: string;
13
+ fallback?: ReactNode;
14
+ }
15
+ /**
16
+ * CanRoute 组件 - 路由级别的权限守卫
17
+ *
18
+ * 基于权限和角色进行路由级别的访问控制
19
+ * 当权限或角色检查失败时,会渲染 fallback 内容或返回 null
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { CanRoute } from '@lark-apaas/auth-sdk';
24
+ * import { Navigate, useLocation } from 'react-router-dom';
25
+ *
26
+ * function ProtectedRoute() {
27
+ * const location = useLocation();
28
+ *
29
+ * return (
30
+ * <CanRoute
31
+ * permissions={[{ action: 'read', subject: 'Task' }]}
32
+ * roles={['admin']}
33
+ * fallback={<Navigate to="/forbidden" replace state={{ from: location }} />}
34
+ * >
35
+ * <TaskList />
36
+ * </CanRoute>
37
+ * );
38
+ * }
39
+ * ```
40
+ */
41
+ export declare function CanRoute({ permissions, roles, children, fallback, redirectTo }: CanRouteProps): import("react/jsx-runtime").JSX.Element | null;
42
+ //# sourceMappingURL=CanRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanRoute.d.ts","sourceRoot":"","sources":["../src/CanRoute.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAItC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAe,EAAE,UAAe,EAAE,EAAE,aAAa,kDAezG"}
@@ -0,0 +1,27 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import { Navigate, useLocation } from "react-router-dom";
3
+ import { useCanPermission, useCanRole, useNoPermissionPath } from "./AuthProvider.js";
4
+ function CanRoute({ permissions, roles, children, fallback = null, redirectTo = '' }) {
5
+ const noPermissionPath = useNoPermissionPath();
6
+ const location = useLocation();
7
+ let allPass = true;
8
+ if (!useCanPermission({
9
+ permissions
10
+ })) allPass = false;
11
+ if (!useCanRole({
12
+ roles
13
+ })) allPass = false;
14
+ if (!allPass) return fallback ? /*#__PURE__*/ jsx(Fragment, {
15
+ children: fallback
16
+ }) : redirectTo || noPermissionPath ? /*#__PURE__*/ jsx(Navigate, {
17
+ to: redirectTo || noPermissionPath,
18
+ replace: true,
19
+ state: {
20
+ from: location
21
+ }
22
+ }) : null;
23
+ return /*#__PURE__*/ jsx(Fragment, {
24
+ children: children
25
+ });
26
+ }
27
+ export { CanRoute };
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * RouteGuard 组件 Props
4
+ */
5
+ export interface RouteGuardProps {
6
+ permissions?: Array<{
7
+ action: string;
8
+ subject: string;
9
+ }>;
10
+ roles?: string[];
11
+ children: ReactNode;
12
+ redirectTo?: string;
13
+ fallback?: ReactNode;
14
+ }
15
+ /**
16
+ * RouteGuard 组件 - 路由级别的权限守卫
17
+ *
18
+ * 基于权限和角色进行路由级别的访问控制
19
+ * 当权限或角色检查失败时,会渲染 fallback 内容或返回 null
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { RouteGuard } from '@lark-apaas/auth-sdk';
24
+ * import { Navigate, useLocation } from 'react-router-dom';
25
+ *
26
+ * function ProtectedRoute() {
27
+ * const location = useLocation();
28
+ *
29
+ * return (
30
+ * <RouteGuard
31
+ * permissions={[{ action: 'read', subject: 'Task' }]}
32
+ * roles={['admin']}
33
+ * fallback={<Navigate to="/forbidden" replace state={{ from: location }} />}
34
+ * >
35
+ * <TaskList />
36
+ * </RouteGuard>
37
+ * );
38
+ * }
39
+ * ```
40
+ */
41
+ export declare function RouteGuard({ permissions, roles, children, fallback, redirectTo }: RouteGuardProps): import("react/jsx-runtime").JSX.Element | null;
42
+ //# sourceMappingURL=RouteGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouteGuard.d.ts","sourceRoot":"","sources":["../src/RouteGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGtC;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAe,EAAE,UAAe,EAAE,EAAE,eAAe,kDAe7G"}
@@ -0,0 +1,27 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import { Navigate, useLocation } from "react-router-dom";
3
+ import { useCanPermission, useCanRole, useNoPermissionPath } from "./AuthProvider.js";
4
+ function RouteGuard({ permissions, roles, children, fallback = null, redirectTo = '' }) {
5
+ const noPermissionPath = useNoPermissionPath();
6
+ const location = useLocation();
7
+ let allPass = true;
8
+ if (!useCanPermission({
9
+ permissions
10
+ })) allPass = false;
11
+ if (!useCanRole({
12
+ roles
13
+ })) allPass = false;
14
+ if (!allPass) return fallback ? /*#__PURE__*/ jsx(Fragment, {
15
+ children: fallback
16
+ }) : redirectTo || noPermissionPath ? /*#__PURE__*/ jsx(Navigate, {
17
+ to: redirectTo || noPermissionPath,
18
+ replace: true,
19
+ state: {
20
+ from: location
21
+ }
22
+ }) : null;
23
+ return /*#__PURE__*/ jsx(Fragment, {
24
+ children: children
25
+ });
26
+ }
27
+ export { RouteGuard };
@@ -0,0 +1,22 @@
1
+ import { Ability } from '@casl/ability';
2
+ import type { Permission, CaslRule } from './types';
3
+ export declare const ROLE_SUBJECT = "@role";
4
+ /**
5
+ * 将权限数据转换为 CASL 规则
6
+ */
7
+ export declare function convertPermissionsToRules(permissions: Permission[], roles: string[]): CaslRule[];
8
+ /**
9
+ * 创建 Ability 实例
10
+ */
11
+ export declare function createAbility({ permissions, roles }: {
12
+ permissions?: Permission[];
13
+ roles?: string[];
14
+ }): Ability;
15
+ /**
16
+ * 更新已存在的 Ability 实例
17
+ */
18
+ export declare function updateAbility(ability: Ability, { permissions, roles }: {
19
+ permissions?: Permission[];
20
+ roles?: string[];
21
+ }): void;
22
+ //# sourceMappingURL=ability-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ability-factory.d.ts","sourceRoot":"","sources":["../src/ability-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAgC,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,eAAO,MAAM,YAAY,UAAU,CAAC;AACpC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAoBhG;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAC,WAAW,EAAE,KAAK,EAAC,EAAE;IAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAC,GAAG,OAAO,CAkB3G;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,EAAC,WAAW,EAAE,KAAK,EAAC,EAAE;IAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAC,GAAG,IAAI,CAG1H"}
@@ -0,0 +1,26 @@
1
+ import { Ability, AbilityBuilder } from "@casl/ability";
2
+ const ROLE_SUBJECT = '@role';
3
+ function convertPermissionsToRules(permissions, roles) {
4
+ const rules = [];
5
+ for (const permission of permissions)for (const action of permission.actions)rules.push({
6
+ action,
7
+ subject: permission.sub
8
+ });
9
+ for (const role of roles)rules.push({
10
+ action: role,
11
+ subject: ROLE_SUBJECT
12
+ });
13
+ return rules;
14
+ }
15
+ function createAbility({ permissions, roles }) {
16
+ const { build, can } = new AbilityBuilder(Ability);
17
+ const rules = convertPermissionsToRules(permissions || [], roles || []);
18
+ for (const rule of rules)if (Array.isArray(rule.action)) for (const action of rule.action)can(action, rule.subject, rule.fields);
19
+ else can(rule.action, rule.subject, rule.fields);
20
+ return build();
21
+ }
22
+ function updateAbility(ability, { permissions, roles }) {
23
+ const rules = convertPermissionsToRules(permissions || [], roles || []);
24
+ ability.update(rules);
25
+ }
26
+ export { ROLE_SUBJECT, convertPermissionsToRules, createAbility, updateAbility };
package/lib/index.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @lark-apaas/auth-sdk
3
+ *
4
+ * 基于 CASL 的前端鉴权 SDK
5
+ * 封装了权限数据获取和 Ability 初始化逻辑
6
+ */
7
+ export type { Action, Subject, Permission, UserRole, PermissionApiResponse, PermissionApiConfig, AuthSdkConfig, CaslRule, } from './types';
8
+ export { createAbility, updateAbility, convertPermissionsToRules, } from './ability-factory';
9
+ export { PermissionClient } from './permission-client';
10
+ export { AuthProvider, Can, useCanPermission, CanPermission, useCanRole, CanRole, useAuth, useAbility, usePermissions, useRoles, AbilityContext, useUserId, useNoPermissionPath, } from './AuthProvider';
11
+ export { CanRoute } from './CanRoute';
12
+ export type { AuthProviderProps, UseCanPermissionProps, UseCanRoleProps, CanPermissionProps, CanRoleProps, } from './AuthProvider';
13
+ export type { CanRouteProps } from './CanRoute';
14
+ export { Ability, AbilityBuilder, type AbilityClass } from '@casl/ability';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,MAAM,EACN,OAAO,EACP,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,QAAQ,GACT,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,aAAa,EACb,aAAa,EACb,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EACL,YAAY,EACZ,GAAG,EACH,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,OAAO,EACP,OAAO,EACP,UAAU,EACV,cAAc,EACd,QAAQ,EACR,cAAc,EACd,SAAS,EACT,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { convertPermissionsToRules, createAbility, updateAbility } from "./ability-factory.js";
2
+ import { PermissionClient } from "./permission-client.js";
3
+ import { AbilityContext, AuthProvider, Can, CanPermission, CanRole, useAbility, useAuth, useCanPermission, useCanRole, useNoPermissionPath, usePermissions, useRoles, useUserId } from "./AuthProvider.js";
4
+ import { CanRoute } from "./CanRoute.js";
5
+ import { Ability, AbilityBuilder } from "@casl/ability";
6
+ export { Ability, AbilityBuilder, AbilityContext, AuthProvider, Can, CanPermission, CanRole, CanRoute, PermissionClient, convertPermissionsToRules, createAbility, updateAbility, useAbility, useAuth, useCanPermission, useCanRole, useNoPermissionPath, usePermissions, useRoles, useUserId };
@@ -0,0 +1,22 @@
1
+ import type { PermissionApiConfig, PermissionApiResponse } from './types';
2
+ /**
3
+ * 权限数据获取客户端
4
+ */
5
+ export declare class PermissionClient {
6
+ private config;
7
+ constructor(config?: PermissionApiConfig);
8
+ /**
9
+ * 获取用户权限数据
10
+ * @param userId - 用户ID(可选,用于向后兼容。如果端点包含 :userId,则会替换)
11
+ */
12
+ fetchPermissions(userId?: string): Promise<PermissionApiResponse>;
13
+ /**
14
+ * 更新配置
15
+ */
16
+ updateConfig(config: Partial<PermissionApiConfig>): void;
17
+ /**
18
+ * 获取当前配置
19
+ */
20
+ getConfig(): PermissionApiConfig;
21
+ }
22
+ //# sourceMappingURL=permission-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-client.d.ts","sourceRoot":"","sources":["../src/permission-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAY1E;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,CAAC,EAAE,mBAAmB;IAOxC;;;OAGG;IACG,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAsEvE;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAOxD;;OAEG;IACH,SAAS,IAAI,mBAAmB;CAGjC"}
@@ -0,0 +1,58 @@
1
+ const DEFAULT_CONFIG = {
2
+ baseUrl: '',
3
+ endpoint: '/api/permissions',
4
+ timeout: 5000,
5
+ withCredentials: true
6
+ };
7
+ class PermissionClient {
8
+ constructor(config){
9
+ this.config = {
10
+ ...DEFAULT_CONFIG,
11
+ ...config
12
+ };
13
+ }
14
+ async fetchPermissions(userId) {
15
+ const { baseUrl = DEFAULT_CONFIG.baseUrl, endpoint = DEFAULT_CONFIG.endpoint, timeout = DEFAULT_CONFIG.timeout, headers = {}, apiToken, withCredentials = DEFAULT_CONFIG.withCredentials } = this.config;
16
+ let finalEndpoint = endpoint;
17
+ if (userId) finalEndpoint = endpoint.includes(':userId') ? endpoint.replace(':userId', userId) : `${endpoint}?mockUserId=${userId}`;
18
+ const url = `${baseUrl}${finalEndpoint}`;
19
+ const requestHeaders = {
20
+ 'Content-Type': 'application/json',
21
+ ...headers
22
+ };
23
+ if (apiToken) requestHeaders['Authorization'] = `Bearer ${apiToken}`;
24
+ const controller = new AbortController();
25
+ const timeoutId = setTimeout(()=>controller.abort(), timeout);
26
+ try {
27
+ const response = await fetch(url, {
28
+ method: 'GET',
29
+ headers: requestHeaders,
30
+ signal: controller.signal,
31
+ credentials: withCredentials ? 'include' : 'same-origin'
32
+ });
33
+ clearTimeout(timeoutId);
34
+ if (!response.ok) throw new Error(`Permission API returned ${response.status}: ${response.statusText}`);
35
+ const data = await response.json();
36
+ return {
37
+ ...data,
38
+ fetchedAt: data.fetchedAt || new Date().toISOString()
39
+ };
40
+ } catch (error) {
41
+ clearTimeout(timeoutId);
42
+ if ('AbortError' === error.name) throw new Error(`Permission API request timeout after ${timeout}ms`);
43
+ throw error;
44
+ }
45
+ }
46
+ updateConfig(config) {
47
+ this.config = {
48
+ ...this.config,
49
+ ...config
50
+ };
51
+ }
52
+ getConfig() {
53
+ return {
54
+ ...this.config
55
+ };
56
+ }
57
+ }
58
+ export { PermissionClient };
package/lib/types.d.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 权限操作类型
3
+ */
4
+ export type Action = 'create' | 'read' | 'update' | 'delete' | 'manage' | string;
5
+ /**
6
+ * 权限主体(资源)类型
7
+ */
8
+ export type Subject = string;
9
+ /**
10
+ * 单个权限定义
11
+ */
12
+ export interface Permission {
13
+ id: string;
14
+ name: string;
15
+ sub: Subject;
16
+ actions: Action[];
17
+ }
18
+ /**
19
+ * 用户角色定义
20
+ */
21
+ export interface UserRole {
22
+ id: string;
23
+ name: string;
24
+ description?: string;
25
+ }
26
+ /**
27
+ * 权限 API 响应数据
28
+ */
29
+ export interface PermissionApiResponse {
30
+ userId: string;
31
+ roles: (string | UserRole)[];
32
+ permissions: Permission[];
33
+ fetchedAt?: string | Date;
34
+ }
35
+ /**
36
+ * 权限 API 配置
37
+ */
38
+ export interface PermissionApiConfig {
39
+ /**
40
+ * API 基础 URL
41
+ * @default ''
42
+ */
43
+ baseUrl?: string;
44
+ /**
45
+ * API 端点路径
46
+ * @default '/api/permissions'
47
+ */
48
+ endpoint?: string;
49
+ /**
50
+ * 请求超时时间(毫秒)
51
+ * @default 5000
52
+ */
53
+ timeout?: number;
54
+ /**
55
+ * 自定义请求头
56
+ */
57
+ headers?: Record<string, string>;
58
+ /**
59
+ * API Token,会自动添加到 Authorization header
60
+ */
61
+ apiToken?: string;
62
+ /**
63
+ * 是否携带凭证(cookies)
64
+ * @default true
65
+ */
66
+ withCredentials?: boolean;
67
+ }
68
+ /**
69
+ * Auth SDK 配置选项
70
+ */
71
+ export interface AuthSdkConfig {
72
+ /**
73
+ * 无权限路径
74
+ * @default '/no-access'
75
+ */
76
+ noPermissionPath?: string;
77
+ /**
78
+ * 权限 API 配置
79
+ */
80
+ permissionApi?: PermissionApiConfig;
81
+ /**
82
+ * 是否在初始化时自动获取权限
83
+ * @default false
84
+ */
85
+ autoFetch?: boolean;
86
+ /**
87
+ * 获取权限失败时的错误处理
88
+ */
89
+ onError?: (error: Error) => void;
90
+ /**
91
+ * 权限获取成功的回调
92
+ */
93
+ onSuccess?: (data: PermissionApiResponse) => void;
94
+ }
95
+ /**
96
+ * CASL 规则定义
97
+ */
98
+ export interface CaslRule {
99
+ action: Action | Action[];
100
+ subject: Subject | Subject[];
101
+ conditions?: Record<string, unknown>;
102
+ fields?: string[];
103
+ inverted?: boolean;
104
+ reason?: string;
105
+ }
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjF;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;IAC7B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAEpC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACnD;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
package/lib/types.js ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@lark-apaas/auth-sdk",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "基于 CASL 的前端鉴权 SDK",
5
+ "types": "./lib/index.d.ts",
6
+ "main": "./lib/index.js",
7
+ "module": "./lib/index.js",
8
+ "files": [
9
+ "lib",
10
+ "README.md"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "build": "rslib build",
17
+ "dev": "rslib build --watch",
18
+ "type-check": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "@casl/ability": "^6.7.1",
22
+ "@casl/react": "^4.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@rsbuild/core": "~1.4.13",
26
+ "@rsbuild/plugin-react": "^1.3.4",
27
+ "@rslib/core": "^0.15.0",
28
+ "@types/node": "^22.10.2",
29
+ "@types/react": "^18.3.23",
30
+ "typescript": "^5.9.2"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=16.14.0",
34
+ "react-dom": ">=16.14.0",
35
+ "react-router": ">=6.0.0",
36
+ "react-router-dom": ">=6.0.0"
37
+ },
38
+ "keywords": [
39
+ "casl",
40
+ "authorization",
41
+ "permission",
42
+ "auth",
43
+ "react"
44
+ ],
45
+ "license": "MIT"
46
+ }