@lark-apaas/auth-sdk 0.1.0-alpha.0 → 0.1.0-alpha.11
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 +81 -249
- package/lib/AuthProvider.d.ts +39 -59
- package/lib/AuthProvider.d.ts.map +1 -1
- package/lib/AuthProvider.js +37 -84
- package/lib/ability-factory.d.ts +3 -3
- package/lib/ability-factory.d.ts.map +1 -1
- package/lib/ability-factory.js +2 -2
- package/lib/index.d.ts +4 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -5
- package/lib/permission-client.d.ts +1 -2
- package/lib/permission-client.d.ts.map +1 -1
- package/lib/permission-client.js +13 -15
- package/lib/types.d.ts +8 -24
- package/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/lib/CanRoute.d.ts +0 -42
- package/lib/CanRoute.d.ts.map +0 -1
- package/lib/CanRoute.js +0 -27
- package/lib/RouteGuard.d.ts +0 -42
- package/lib/RouteGuard.d.ts.map +0 -1
- package/lib/RouteGuard.js +0 -27
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# @lark-apaas/auth-sdk
|
|
2
2
|
|
|
3
3
|
基于 CASL 的前端鉴权 SDK,封装了权限数据获取、Ability 初始化与 React 集成,帮助你在应用中便捷地做「功能级/按钮级/菜单级/页面级」的权限控制。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm i @lark-apaas/auth-sdk
|
|
@@ -10,22 +10,22 @@ npm i @lark-apaas/auth-sdk
|
|
|
10
10
|
yarn add @lark-apaas/auth-sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 快速开始
|
|
14
|
+
|
|
15
|
+
### 模版接入
|
|
14
16
|
|
|
15
17
|
```tsx
|
|
16
18
|
import React from 'react';
|
|
17
|
-
import { AuthProvider,
|
|
19
|
+
import { AuthProvider, CanRole, useAuthAbility } from '@lark-apaas/auth-sdk';
|
|
18
20
|
|
|
19
21
|
export default function App() {
|
|
20
22
|
return (
|
|
21
23
|
<AuthProvider
|
|
22
24
|
config={{
|
|
23
25
|
permissionApi: {
|
|
24
|
-
|
|
25
|
-
endpoint: '/api/users/:userId/permissions',
|
|
26
|
-
// 可选:apiToken、timeout、headers、withCredentials
|
|
26
|
+
timeout: 5000,
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
enable: true,
|
|
29
29
|
onError: (e) => console.error(e),
|
|
30
30
|
}}
|
|
31
31
|
>
|
|
@@ -33,173 +33,104 @@ export default function App() {
|
|
|
33
33
|
</AuthProvider>
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### 开发组件 - 使用 CanRole 组件
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { CanRole } from '@lark-apaas/auth-sdk';
|
|
36
43
|
|
|
37
44
|
function Home() {
|
|
38
|
-
const ability = useAbility();
|
|
39
45
|
return (
|
|
40
46
|
<div>
|
|
41
|
-
<
|
|
42
|
-
<div
|
|
43
|
-
</
|
|
44
|
-
<
|
|
47
|
+
<CanRole roles={['role_admin']}>
|
|
48
|
+
<div>管理员按钮</div>
|
|
49
|
+
</CanRole>
|
|
50
|
+
<CanRole roles={['role_admin', 'role_editor']}>
|
|
51
|
+
<div>编辑按钮</div>
|
|
52
|
+
</CanRole>
|
|
45
53
|
</div>
|
|
46
54
|
);
|
|
47
55
|
}
|
|
48
56
|
```
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## 核心 API
|
|
58
|
+
### 开发组件 - 使用 AbilityContext 处理复杂场景
|
|
53
59
|
|
|
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
60
|
```tsx
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
import { useContext } from 'react';
|
|
62
|
+
import { AbilityContext, ROLE_SUBJECT } from '@lark-apaas/auth-sdk';
|
|
63
|
+
|
|
64
|
+
function Home() {
|
|
65
|
+
const ability = useContext(AbilityContext);
|
|
66
|
+
return (
|
|
67
|
+
<div>
|
|
68
|
+
{ability.can('role_admin', ROLE_SUBJECT) || ability.can('role_editor', ROLE_SUBJECT) ? (
|
|
69
|
+
<div>可见的仪表盘</div>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
80
74
|
```
|
|
81
75
|
|
|
82
|
-
###
|
|
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` 重新拉取
|
|
76
|
+
### 开发组件 - 见 Client Toolkit
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
const { permissions, roles, isLoading, refetch } = useAuth();
|
|
96
|
-
```
|
|
78
|
+
---
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
- **作用**: 获取 CASL `Ability` 实例,使用 `ability.can(action, subject)` 做任意判断。
|
|
80
|
+
## 核心 API - 面向 Agent
|
|
100
81
|
|
|
101
|
-
|
|
102
|
-
const ability = useAbility();
|
|
103
|
-
const canCreate = ability.can('create', 'Task');
|
|
104
|
-
```
|
|
82
|
+
### CanRole 组件 (推荐)
|
|
105
83
|
|
|
106
|
-
|
|
107
|
-
- **作用**: 条件渲染,只有当 `I` 对 `a` 可操作时才渲染子内容;也支持 render prop。
|
|
84
|
+
- **作用**: 条件渲染,只有当角色 id 包含在 `roles` 中时才渲染子内容。
|
|
108
85
|
|
|
109
86
|
```tsx
|
|
110
|
-
<
|
|
87
|
+
<CanRole roles={['role_admin', 'role_editor']}>
|
|
111
88
|
<button>删除任务</button>
|
|
112
|
-
</
|
|
113
|
-
|
|
114
|
-
<Can I="download" a="Report">{(allowed) => (
|
|
115
|
-
<button disabled={!allowed}>下载报表</button>
|
|
116
|
-
)}</Can>
|
|
89
|
+
</CanRole>
|
|
117
90
|
```
|
|
118
91
|
|
|
119
|
-
###
|
|
120
|
-
- `useCanPermission({ permissions, or })`:一次检查多个权限;`or=true` 表示任一满足即可,否则默认全部需要满足。
|
|
121
|
-
- `CanPermission`:同上但以组件方式使用,支持 children 或 render prop。
|
|
92
|
+
### AbilityContext 提供原子化的权限判断能力
|
|
122
93
|
|
|
123
|
-
|
|
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`:以组件形式使用。
|
|
94
|
+
- **作用**: 从 React 上下文获取 CASL `Ability` 实例,使用 `ability.can(action, subject)` 做权限判断。
|
|
142
95
|
|
|
143
96
|
```tsx
|
|
144
|
-
|
|
97
|
+
import { useContext } from 'react';
|
|
98
|
+
import { AbilityContext } from '@lark-apaas/auth-sdk';
|
|
145
99
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</CanRole>
|
|
100
|
+
const ability = useContext(AbilityContext);
|
|
101
|
+
const canCreate = ability.can('role_editor', '@role');
|
|
149
102
|
```
|
|
150
103
|
|
|
151
|
-
|
|
152
|
-
|
|
104
|
+
## 核心 API - 面向接入方
|
|
105
|
+
|
|
106
|
+
### AuthProvider
|
|
107
|
+
|
|
108
|
+
- **作用**: 提供 `Ability` 与权限状态上下文,自动/手动拉取权限。
|
|
153
109
|
- **Props**:
|
|
154
|
-
- `
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
110
|
+
- `config?: AuthSdkConfig`
|
|
111
|
+
- `enable?: boolean` 是否启用权限 SDK,默认 false
|
|
112
|
+
- `permissionApi?: PermissionApiConfig` 拉取权限接口配置
|
|
113
|
+
- `timeout?: number`,默认 5000ms
|
|
114
|
+
- `headers?: Record<string,string>` 自定义请求头
|
|
115
|
+
- `onError?: (error: Error) => void`
|
|
116
|
+
- `onSuccess?: (data: PermissionApiResponse) => void`
|
|
117
|
+
- `children: React.ReactNode`
|
|
118
|
+
|
|
119
|
+
示例:
|
|
161
120
|
|
|
162
121
|
```tsx
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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>
|
|
122
|
+
<AuthProvider config={{
|
|
123
|
+
enable: true,
|
|
124
|
+
}}>
|
|
125
|
+
<App />
|
|
126
|
+
{/* Can/Hook 在其子树中生效 */}
|
|
127
|
+
</AuthProvider>
|
|
200
128
|
```
|
|
201
129
|
|
|
130
|
+
## 核心 API - 面向 SDK 开发者
|
|
131
|
+
|
|
202
132
|
### PermissionClient
|
|
133
|
+
|
|
203
134
|
- **作用**: 独立的权限数据获取客户端,供非 React 场景(或自定义状态管理)使用。
|
|
204
135
|
- **方法**:
|
|
205
136
|
- `constructor(config?: PermissionApiConfig)`
|
|
@@ -215,6 +146,7 @@ const data = await client.fetchPermissions('user-123');
|
|
|
215
146
|
```
|
|
216
147
|
|
|
217
148
|
### 能力工厂(与 CASL 交互)
|
|
149
|
+
|
|
218
150
|
- `createAbility({ permissions?: Permission[], roles?: string[] }): Ability`
|
|
219
151
|
- `updateAbility(ability: Ability, { permissions?: Permission[], roles?: string[] }): void`
|
|
220
152
|
- `convertPermissionsToRules(permissions: Permission[], roles: string[]): CaslRule[]`
|
|
@@ -232,120 +164,20 @@ updateAbility(ability, { permissions: [{ id: 'p1', name: 'Task Read', sub: 'Task
|
|
|
232
164
|
---
|
|
233
165
|
|
|
234
166
|
## 类型与再导出
|
|
235
|
-
- 从本包导出的类型:`Action`、`Subject`、`Permission`、`UserRole`、`PermissionApiResponse`、`PermissionApiConfig`、`AuthSdkConfig`、`CaslRule`。
|
|
236
|
-
- 便捷再导出:`Ability`、`AbilityBuilder`、`AbilityClass`(来自 `@casl/ability`)。
|
|
237
167
|
|
|
238
|
-
|
|
168
|
+
- 从本包导出的类型:`PermissionApiResponse`、`PermissionApiConfig`、`AuthSdkConfig`、`CaslRule`。
|
|
169
|
+
- 最终用户代码中仅需要使用到的 API 有:
|
|
170
|
+
- `CanRole` 组件: 条件渲染,只有当角色 id 包含在 `roles` 中时才渲染子内容。
|
|
171
|
+
- `AbilityContext`: 从 React 上下文获取 CASL `Ability` 实例,用于权限判断。
|
|
172
|
+
- `ROLE_SUBJECT`: 特殊 subject `@role` 常量,用于角色判断。
|
|
239
173
|
|
|
240
174
|
## 集成建议与最佳实践
|
|
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
175
|
|
|
293
|
-
|
|
294
|
-
|
|
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`,请确保项目中已安装。
|
|
176
|
+
- **直接使用组件**:`CanRole` 组件是直接使用的主要入口,可以使用 `AbilityContext` 获取 `Ability` 实例,在特殊场景下手动判断权限。
|
|
177
|
+
- **与路由结合**:页面级的访问控制需要结合路由库(如 `react-router-dom`)和 `AbilityContext` 来自行实现。
|
|
345
178
|
|
|
346
179
|
---
|
|
347
180
|
|
|
348
181
|
## 许可
|
|
349
|
-
MIT
|
|
350
|
-
|
|
351
182
|
|
|
183
|
+
MIT
|
package/lib/AuthProvider.d.ts
CHANGED
|
@@ -1,59 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import type { AuthSdkConfig
|
|
2
|
+
import { MongoAbility } from '@casl/ability';
|
|
3
|
+
import type { AuthSdkConfig } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Ability Context - 用于在组件树中共享 Ability 实例
|
|
6
6
|
*/
|
|
7
|
-
export declare const AbilityContext: React.Context<
|
|
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;
|
|
7
|
+
export declare const AbilityContext: React.Context<MongoAbility<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>>;
|
|
43
8
|
/**
|
|
44
9
|
* Auth 状态 Context 类型定义
|
|
45
10
|
* 存储权限数据和加载状态
|
|
46
11
|
*/
|
|
47
12
|
interface AuthStateContextValue {
|
|
48
|
-
|
|
49
|
-
permissions: Permission[];
|
|
50
|
-
roles: string[];
|
|
51
|
-
userId: string | null;
|
|
52
|
-
setUserId: (userId: string) => void;
|
|
13
|
+
ability: MongoAbility;
|
|
53
14
|
isLoading: boolean;
|
|
54
15
|
error: Error | null;
|
|
55
16
|
fetchPermissions: (userId?: string) => Promise<void>;
|
|
56
|
-
refetch: () => Promise<void>;
|
|
57
17
|
}
|
|
58
18
|
/**
|
|
59
19
|
* Auth Provider Props
|
|
@@ -61,7 +21,6 @@ interface AuthStateContextValue {
|
|
|
61
21
|
export interface AuthProviderProps {
|
|
62
22
|
children: React.ReactNode;
|
|
63
23
|
config?: AuthSdkConfig;
|
|
64
|
-
userId?: string;
|
|
65
24
|
}
|
|
66
25
|
/**
|
|
67
26
|
* Auth Provider 组件
|
|
@@ -87,6 +46,13 @@ export interface AuthProviderProps {
|
|
|
87
46
|
* ```
|
|
88
47
|
*/
|
|
89
48
|
export declare function AuthProvider({ children, config }: AuthProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* 获取 Ability 实例
|
|
51
|
+
*
|
|
52
|
+
* @param permissionApiConfig - 权限 API 配置
|
|
53
|
+
* @returns Ability 实例或错误
|
|
54
|
+
*/
|
|
55
|
+
export declare function getAbility(permissionApiConfig: AuthSdkConfig['permissionApi']): Promise<Error | MongoAbility<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>>;
|
|
90
56
|
/**
|
|
91
57
|
* useAuth Hook - 获取权限数据和加载状态
|
|
92
58
|
*
|
|
@@ -110,14 +76,14 @@ export declare function AuthProvider({ children, config }: AuthProviderProps): i
|
|
|
110
76
|
*/
|
|
111
77
|
export declare function useAuth(): AuthStateContextValue;
|
|
112
78
|
/**
|
|
113
|
-
*
|
|
79
|
+
* useAuthAbility Hook - 获取 Ability 实例
|
|
114
80
|
*
|
|
115
81
|
* @example
|
|
116
82
|
* ```tsx
|
|
117
|
-
* import {
|
|
83
|
+
* import { useAuthAbility } from '@lark-apaas/auth-sdk';
|
|
118
84
|
*
|
|
119
85
|
* function MyComponent() {
|
|
120
|
-
* const ability =
|
|
86
|
+
* const ability = useAuthAbility();
|
|
121
87
|
*
|
|
122
88
|
* return (
|
|
123
89
|
* <button disabled={ability.cannot('create', 'Task')}>
|
|
@@ -127,32 +93,46 @@ export declare function useAuth(): AuthStateContextValue;
|
|
|
127
93
|
* }
|
|
128
94
|
* ```
|
|
129
95
|
*/
|
|
130
|
-
export declare function
|
|
96
|
+
export declare function useAuthAbility(): MongoAbility;
|
|
131
97
|
/**
|
|
132
|
-
*
|
|
98
|
+
* Can Component - 基于 Ability 实例的条件渲染组件
|
|
133
99
|
*
|
|
134
100
|
* @example
|
|
135
101
|
* ```tsx
|
|
102
|
+
* import { Can } from '@lark-apaas/auth-sdk';
|
|
103
|
+
*
|
|
136
104
|
* function MyComponent() {
|
|
137
|
-
*
|
|
138
|
-
*
|
|
105
|
+
* return (
|
|
106
|
+
* <Can I="Admin" a="@role">
|
|
107
|
+
* <TaskList />
|
|
108
|
+
* </Can>
|
|
109
|
+
* );
|
|
139
110
|
* }
|
|
140
111
|
* ```
|
|
141
112
|
*/
|
|
142
|
-
export declare
|
|
113
|
+
export declare const Can: React.FunctionComponent<import("@casl/react").BoundCanProps<MongoAbility<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>>>;
|
|
114
|
+
export declare const useCanRole: ({ roles }: {
|
|
115
|
+
roles: string[];
|
|
116
|
+
}) => boolean;
|
|
143
117
|
/**
|
|
144
|
-
*
|
|
118
|
+
* CanRole Component - 基于 Ability 实例的角色条件渲染组件
|
|
145
119
|
*
|
|
146
120
|
* @example
|
|
147
121
|
* ```tsx
|
|
122
|
+
* import { CanRole } from '@lark-apaas/auth-sdk';
|
|
123
|
+
*
|
|
148
124
|
* function MyComponent() {
|
|
149
|
-
*
|
|
150
|
-
*
|
|
125
|
+
* return (
|
|
126
|
+
* <CanRole roles={['role_admin', 'role_editor']}>
|
|
127
|
+
* <TaskList />
|
|
128
|
+
* </CanRole>
|
|
129
|
+
* );
|
|
151
130
|
* }
|
|
152
131
|
* ```
|
|
153
132
|
*/
|
|
154
|
-
export declare function
|
|
155
|
-
|
|
156
|
-
|
|
133
|
+
export declare function CanRole({ children, roles, }: {
|
|
134
|
+
children: React.ReactNode;
|
|
135
|
+
roles: string[];
|
|
136
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
157
137
|
export {};
|
|
158
138
|
//# sourceMappingURL=AuthProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;GAEG;AACH,eAAO,MAAM,cAAc,uGAE1B,CAAC;AAEF;;;GAGG;AACH,UAAU,qBAAqB;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gBAAgB,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAOD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA2DnE;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,mBAAmB,EAAE,aAAa,CAAC,eAAe,CAAC,2GAiBpD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,OAAO,IAAI,qBAAqB,CAQ/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,IAAI,YAAY,CAE7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,GAAG,sJAA+C,CAAC;AAEhE,eAAO,MAAM,UAAU,GAAa,WAAW;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,KAAG,OASpE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,kDAIA"}
|
package/lib/AuthProvider.js
CHANGED
|
@@ -1,70 +1,25 @@
|
|
|
1
1
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
3
|
-
import { createContextualCan } from "@casl/react";
|
|
4
3
|
import { ROLE_SUBJECT, createAbility, updateAbility } from "./ability-factory.js";
|
|
5
4
|
import { PermissionClient } from "./permission-client.js";
|
|
5
|
+
import { createContextualCan } from "@casl/react";
|
|
6
6
|
const AbilityContext = /*#__PURE__*/ createContext(createAbility({
|
|
7
7
|
permissions: [],
|
|
8
8
|
roles: []
|
|
9
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
10
|
const AuthStateContext = /*#__PURE__*/ createContext(null);
|
|
48
11
|
function AuthProvider({ children, config }) {
|
|
49
12
|
const [ability] = useState(()=>createAbility({}));
|
|
50
|
-
const [permissions, setPermissions] = useState([]);
|
|
51
|
-
const [roles, setRoles] = useState([]);
|
|
52
|
-
const [userId, setUserId] = useState(null);
|
|
53
13
|
const [isLoading, setIsLoading] = useState(false);
|
|
54
14
|
const [error, setError] = useState(null);
|
|
55
15
|
const [client] = useState(()=>new PermissionClient(config?.permissionApi));
|
|
56
|
-
const fetchPermissions = useCallback(async (
|
|
16
|
+
const fetchPermissions = useCallback(async ()=>{
|
|
57
17
|
setIsLoading(true);
|
|
58
18
|
setError(null);
|
|
59
19
|
try {
|
|
60
|
-
const data = await client.fetchPermissions(
|
|
61
|
-
const normalizedRoles = data.roles.map((role)=>'string' == typeof role ? role : role.name);
|
|
62
|
-
setPermissions(data.permissions);
|
|
63
|
-
setRoles(normalizedRoles);
|
|
64
|
-
setUserId(data.userId);
|
|
20
|
+
const data = await client.fetchPermissions();
|
|
65
21
|
updateAbility(ability, {
|
|
66
|
-
|
|
67
|
-
roles: normalizedRoles
|
|
22
|
+
roles: data.roles
|
|
68
23
|
});
|
|
69
24
|
config?.onSuccess?.(data);
|
|
70
25
|
} catch (err) {
|
|
@@ -79,28 +34,17 @@ function AuthProvider({ children, config }) {
|
|
|
79
34
|
client,
|
|
80
35
|
config
|
|
81
36
|
]);
|
|
82
|
-
const refetch = useCallback(async ()=>{
|
|
83
|
-
await fetchPermissions(userId);
|
|
84
|
-
}, [
|
|
85
|
-
userId,
|
|
86
|
-
fetchPermissions
|
|
87
|
-
]);
|
|
88
37
|
useEffect(()=>{
|
|
89
|
-
if (config?.
|
|
38
|
+
if (config?.enable !== false) fetchPermissions();
|
|
90
39
|
}, [
|
|
91
|
-
config?.
|
|
40
|
+
config?.enable,
|
|
92
41
|
fetchPermissions
|
|
93
42
|
]);
|
|
94
43
|
const stateContextValue = {
|
|
95
|
-
|
|
96
|
-
roles,
|
|
97
|
-
userId,
|
|
98
|
-
setUserId,
|
|
99
|
-
noPermissionPath: config?.noPermissionPath,
|
|
44
|
+
ability,
|
|
100
45
|
isLoading,
|
|
101
46
|
error,
|
|
102
|
-
fetchPermissions
|
|
103
|
-
refetch
|
|
47
|
+
fetchPermissions
|
|
104
48
|
};
|
|
105
49
|
return /*#__PURE__*/ jsx(AbilityContext.Provider, {
|
|
106
50
|
value: ability,
|
|
@@ -110,31 +54,40 @@ function AuthProvider({ children, config }) {
|
|
|
110
54
|
})
|
|
111
55
|
});
|
|
112
56
|
}
|
|
57
|
+
async function getAbility(permissionApiConfig) {
|
|
58
|
+
const ability = createAbility({});
|
|
59
|
+
const client = new PermissionClient(permissionApiConfig);
|
|
60
|
+
try {
|
|
61
|
+
const data = await client.fetchPermissions();
|
|
62
|
+
updateAbility(ability, {
|
|
63
|
+
roles: data.roles
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
return ability;
|
|
70
|
+
}
|
|
113
71
|
function useAuth() {
|
|
114
72
|
const context = useContext(AuthStateContext);
|
|
115
73
|
if (!context) throw new Error('useAuth must be used within an AuthProvider');
|
|
116
74
|
return context;
|
|
117
75
|
}
|
|
118
|
-
function
|
|
76
|
+
function useAuthAbility() {
|
|
119
77
|
return useContext(AbilityContext);
|
|
120
78
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
];
|
|
135
|
-
}
|
|
136
|
-
function useNoPermissionPath() {
|
|
137
|
-
const { noPermissionPath } = useAuth();
|
|
138
|
-
return noPermissionPath;
|
|
79
|
+
const Can = createContextualCan(AbilityContext.Consumer);
|
|
80
|
+
const useCanRole = function({ roles }) {
|
|
81
|
+
const ability = useContext(AbilityContext);
|
|
82
|
+
const allowed = !roles || 0 === roles.length || roles.length > 0 && roles.some((role)=>ability.can(role, ROLE_SUBJECT));
|
|
83
|
+
return !!allowed;
|
|
84
|
+
};
|
|
85
|
+
function CanRole({ children, roles }) {
|
|
86
|
+
const allowed = useCanRole({
|
|
87
|
+
roles
|
|
88
|
+
});
|
|
89
|
+
return allowed ? /*#__PURE__*/ jsx(Fragment, {
|
|
90
|
+
children: children
|
|
91
|
+
}) : null;
|
|
139
92
|
}
|
|
140
|
-
export { AbilityContext, AuthProvider, Can,
|
|
93
|
+
export { AbilityContext, AuthProvider, Can, CanRole, getAbility, useAuth, useAuthAbility, useCanRole };
|
package/lib/ability-factory.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ability } from '@casl/ability';
|
|
1
|
+
import { MongoAbility as Ability } from '@casl/ability';
|
|
2
2
|
import type { Permission, CaslRule } from './types';
|
|
3
3
|
export declare const ROLE_SUBJECT = "@role";
|
|
4
4
|
/**
|
|
@@ -6,9 +6,9 @@ export declare const ROLE_SUBJECT = "@role";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function convertPermissionsToRules(permissions: Permission[], roles: string[]): CaslRule[];
|
|
8
8
|
/**
|
|
9
|
-
* 创建
|
|
9
|
+
* 创建 MongoAbility 实例
|
|
10
10
|
*/
|
|
11
|
-
export declare function createAbility({ permissions, roles }: {
|
|
11
|
+
export declare function createAbility({ permissions, roles, }: {
|
|
12
12
|
permissions?: Permission[];
|
|
13
13
|
roles?: string[];
|
|
14
14
|
}): Ability;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ability-factory.d.ts","sourceRoot":"","sources":["../src/ability-factory.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ability-factory.d.ts","sourceRoot":"","sources":["../src/ability-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,IAAI,OAAO,EAGxB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,eAAO,MAAM,YAAY,UAAU,CAAC;AACpC;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,UAAU,EAAE,EACzB,KAAK,EAAE,MAAM,EAAE,GACd,QAAQ,EAAE,CAmBZ;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,WAAW,EACX,KAAK,GACN,EAAE;IACD,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,OAAO,CAkBV;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACvE,IAAI,CAGN"}
|
package/lib/ability-factory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
|
|
2
2
|
const ROLE_SUBJECT = '@role';
|
|
3
3
|
function convertPermissionsToRules(permissions, roles) {
|
|
4
4
|
const rules = [];
|
|
@@ -13,7 +13,7 @@ function convertPermissionsToRules(permissions, roles) {
|
|
|
13
13
|
return rules;
|
|
14
14
|
}
|
|
15
15
|
function createAbility({ permissions, roles }) {
|
|
16
|
-
const { build, can } = new AbilityBuilder(
|
|
16
|
+
const { build, can } = new AbilityBuilder(createMongoAbility);
|
|
17
17
|
const rules = convertPermissionsToRules(permissions || [], roles || []);
|
|
18
18
|
for (const rule of rules)if (Array.isArray(rule.action)) for (const action of rule.action)can(action, rule.subject, rule.fields);
|
|
19
19
|
else can(rule.action, rule.subject, rule.fields);
|
package/lib/index.d.ts
CHANGED
|
@@ -4,12 +4,9 @@
|
|
|
4
4
|
* 基于 CASL 的前端鉴权 SDK
|
|
5
5
|
* 封装了权限数据获取和 Ability 初始化逻辑
|
|
6
6
|
*/
|
|
7
|
-
export type {
|
|
8
|
-
export {
|
|
7
|
+
export type { PermissionApiResponse, PermissionApiConfig, AuthSdkConfig, } from './types';
|
|
8
|
+
export { ROLE_SUBJECT } from './ability-factory';
|
|
9
9
|
export { PermissionClient } from './permission-client';
|
|
10
|
-
export { AuthProvider,
|
|
11
|
-
export {
|
|
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';
|
|
10
|
+
export { AuthProvider, useAuth, CanRole, AbilityContext } from './AuthProvider';
|
|
11
|
+
export type { AuthProviderProps } from './AuthProvider';
|
|
15
12
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhF,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ROLE_SUBJECT } from "./ability-factory.js";
|
|
2
2
|
import { PermissionClient } from "./permission-client.js";
|
|
3
|
-
import { AbilityContext, AuthProvider,
|
|
4
|
-
|
|
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 };
|
|
3
|
+
import { AbilityContext, AuthProvider, CanRole, useAuth } from "./AuthProvider.js";
|
|
4
|
+
export { AbilityContext, AuthProvider, CanRole, PermissionClient, ROLE_SUBJECT, useAuth };
|
|
@@ -7,9 +7,8 @@ export declare class PermissionClient {
|
|
|
7
7
|
constructor(config?: PermissionApiConfig);
|
|
8
8
|
/**
|
|
9
9
|
* 获取用户权限数据
|
|
10
|
-
* @param userId - 用户ID(可选,用于向后兼容。如果端点包含 :userId,则会替换)
|
|
11
10
|
*/
|
|
12
|
-
fetchPermissions(
|
|
11
|
+
fetchPermissions(): Promise<PermissionApiResponse>;
|
|
13
12
|
/**
|
|
14
13
|
* 更新配置
|
|
15
14
|
*/
|
|
@@ -1 +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;
|
|
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;AAU1E;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,CAAC,EAAE,mBAAmB;IAOxC;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IA0DxD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAOxD;;OAEG;IACH,SAAS,IAAI,mBAAmB;CAGjC"}
|
package/lib/permission-client.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const DEFAULT_CONFIG = {
|
|
2
2
|
baseUrl: '',
|
|
3
|
-
|
|
4
|
-
timeout: 5000,
|
|
5
|
-
withCredentials: true
|
|
3
|
+
timeout: 5000
|
|
6
4
|
};
|
|
7
5
|
class PermissionClient {
|
|
8
6
|
constructor(config){
|
|
@@ -11,35 +9,35 @@ class PermissionClient {
|
|
|
11
9
|
...config
|
|
12
10
|
};
|
|
13
11
|
}
|
|
14
|
-
async fetchPermissions(
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
const url =
|
|
12
|
+
async fetchPermissions() {
|
|
13
|
+
const { timeout = DEFAULT_CONFIG.timeout, headers = {} } = this.config;
|
|
14
|
+
const { appId } = window;
|
|
15
|
+
if (!appId) throw new Error('appId is required');
|
|
16
|
+
const url = `/spark/app/${appId}/runtime/api/v1/permissions/roles`;
|
|
19
17
|
const requestHeaders = {
|
|
18
|
+
...headers,
|
|
20
19
|
'Content-Type': 'application/json',
|
|
21
|
-
|
|
20
|
+
'X-Suda-Csrf-Token': window.csrfToken || ''
|
|
22
21
|
};
|
|
23
|
-
if (apiToken) requestHeaders['Authorization'] = `Bearer ${apiToken}`;
|
|
24
22
|
const controller = new AbortController();
|
|
25
23
|
const timeoutId = setTimeout(()=>controller.abort(), timeout);
|
|
26
24
|
try {
|
|
27
25
|
const response = await fetch(url, {
|
|
28
|
-
method: '
|
|
26
|
+
method: 'POST',
|
|
29
27
|
headers: requestHeaders,
|
|
30
28
|
signal: controller.signal,
|
|
31
|
-
credentials:
|
|
29
|
+
credentials: 'include'
|
|
32
30
|
});
|
|
33
31
|
clearTimeout(timeoutId);
|
|
34
32
|
if (!response.ok) throw new Error(`Permission API returned ${response.status}: ${response.statusText}`);
|
|
35
33
|
const data = await response.json();
|
|
36
34
|
return {
|
|
37
|
-
|
|
38
|
-
fetchedAt:
|
|
35
|
+
roles: data.data?.roleList || [],
|
|
36
|
+
fetchedAt: new Date()
|
|
39
37
|
};
|
|
40
38
|
} catch (error) {
|
|
41
39
|
clearTimeout(timeoutId);
|
|
42
|
-
if ('AbortError' === error.name) throw new Error(`Permission API request timeout after ${timeout}ms`);
|
|
40
|
+
if (error instanceof Error && 'AbortError' === error.name) throw new Error(`Permission API request timeout after ${timeout}ms`);
|
|
43
41
|
throw error;
|
|
44
42
|
}
|
|
45
43
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
csrfToken?: string;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
7
|
* 权限操作类型
|
|
3
8
|
*/
|
|
@@ -27,9 +32,7 @@ export interface UserRole {
|
|
|
27
32
|
* 权限 API 响应数据
|
|
28
33
|
*/
|
|
29
34
|
export interface PermissionApiResponse {
|
|
30
|
-
|
|
31
|
-
roles: (string | UserRole)[];
|
|
32
|
-
permissions: Permission[];
|
|
35
|
+
roles: string[];
|
|
33
36
|
fetchedAt?: string | Date;
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
@@ -41,11 +44,6 @@ export interface PermissionApiConfig {
|
|
|
41
44
|
* @default ''
|
|
42
45
|
*/
|
|
43
46
|
baseUrl?: string;
|
|
44
|
-
/**
|
|
45
|
-
* API 端点路径
|
|
46
|
-
* @default '/api/permissions'
|
|
47
|
-
*/
|
|
48
|
-
endpoint?: string;
|
|
49
47
|
/**
|
|
50
48
|
* 请求超时时间(毫秒)
|
|
51
49
|
* @default 5000
|
|
@@ -55,34 +53,20 @@ export interface PermissionApiConfig {
|
|
|
55
53
|
* 自定义请求头
|
|
56
54
|
*/
|
|
57
55
|
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
56
|
}
|
|
68
57
|
/**
|
|
69
58
|
* Auth SDK 配置选项
|
|
70
59
|
*/
|
|
71
60
|
export interface AuthSdkConfig {
|
|
72
|
-
/**
|
|
73
|
-
* 无权限路径
|
|
74
|
-
* @default '/no-access'
|
|
75
|
-
*/
|
|
76
|
-
noPermissionPath?: string;
|
|
77
61
|
/**
|
|
78
62
|
* 权限 API 配置
|
|
79
63
|
*/
|
|
80
64
|
permissionApi?: PermissionApiConfig;
|
|
81
65
|
/**
|
|
82
|
-
*
|
|
66
|
+
* 是否在初始化时使用获取权限
|
|
83
67
|
* @default false
|
|
84
68
|
*/
|
|
85
|
-
|
|
69
|
+
enable?: boolean;
|
|
86
70
|
/**
|
|
87
71
|
* 获取权限失败时的错误处理
|
|
88
72
|
*/
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;CACF;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GACd,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,MAAM,CAAC;AAEX;;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,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,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,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAEpC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;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/package.json
CHANGED
package/lib/CanRoute.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
package/lib/CanRoute.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/lib/CanRoute.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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 };
|
package/lib/RouteGuard.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
package/lib/RouteGuard.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/lib/RouteGuard.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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 };
|