@jcoder-stack/abp-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/application-configuration-DhOZRqtz.d.ts +170 -0
- package/dist/auth.d.ts +56 -0
- package/dist/auth.js +58 -0
- package/dist/chunk-HWT6PBBS.js +155 -0
- package/dist/chunk-OK6PUP2E.js +21 -0
- package/dist/chunk-QQFH53SC.js +700 -0
- package/dist/chunk-UDMHSDXZ.js +161 -0
- package/dist/chunk-XXJLSSG3.js +116 -0
- package/dist/core.d.ts +65 -0
- package/dist/core.js +24 -0
- package/dist/i18n.d.ts +18 -0
- package/dist/i18n.js +12 -0
- package/dist/is-granted-C0-1wvoW.d.ts +10 -0
- package/dist/logger-BSnS65IC.d.ts +60 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.js +26 -0
- package/dist/oidc-7fu5kKVF.d.ts +202 -0
- package/dist/permissions.d.ts +18 -0
- package/dist/permissions.js +8 -0
- package/dist/proxy.d.ts +201 -0
- package/dist/proxy.js +535 -0
- package/dist/react.d.ts +125 -0
- package/dist/react.js +236 -0
- package/dist/router.d.ts +39 -0
- package/dist/router.js +41 -0
- package/dist/translator-B3hyoZmK.d.ts +40 -0
- package/dist/types-Bj0MpXtI.d.ts +97 -0
- package/package.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 the abp-react-start authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @jcoder-stack/abp-react
|
|
2
|
+
|
|
3
|
+
`abp-react-start` 的运行时内核——从零自研的纯 React ABP 前端框架,总览见仓库根 README。
|
|
4
|
+
|
|
5
|
+
各域按**子路径**导出,没有把所有东西糊在一起的根导出:一个根导出会让纯前端的 bundle 拖进 `proxy`/`auth` 这类服务端代码。
|
|
6
|
+
|
|
7
|
+
| 子路径 | 职责 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `@jcoder-stack/abp-react/logger` | 同构日志:作用域、字段绑定、默认脱敏、env 开关 |
|
|
10
|
+
| `@jcoder-stack/abp-react/core` | ABP 线上格式归一:application-configuration 的类型 + zod(容错解析)、`PagedResult<T>`、错误信封(`HttpError`/`toHttpError`) |
|
|
11
|
+
| `@jcoder-stack/abp-react/auth` | 授权认证核心:登录策略层(OIDC/password)+ 会话层(加密分块 cookie、刷新、登出);宿主无关、后端无关 |
|
|
12
|
+
| `@jcoder-stack/abp-react/proxy` | ABP 代理网关与调用层:贴 Bearer 转发、401→刷新→重放、幂等重试、超时;策略头组装、会话代调、身份派生;ABP auth 运行时工厂 `createAbpAuthRuntime` 与登录/回调/登出/文化/租户 handler |
|
|
13
|
+
| `@jcoder-stack/abp-react/permissions` | 权限判定原语 `isGranted` + 变参 checker |
|
|
14
|
+
| `@jcoder-stack/abp-react/i18n` | 两层合并 translator(后端 ABP 资源覆盖前端词库),可注入 interpolate/plural |
|
|
15
|
+
| `@jcoder-stack/abp-react/react` | `AppConfigProvider` / `SessionProvider` + hooks(用户/权限/设置/特性/本地化/菜单)+ `<PermissionGuard>`/`<FeatureGuard>` |
|
|
16
|
+
| `@jcoder-stack/abp-react/router` | TanStack Router beforeLoad 路由守卫 `requireAuth` / `requirePermission` |
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add @jcoder-stack/abp-react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
多数项目是配合 `@jcoder-stack/cli` 使用的——`jc-abp init` 会把接线代码落进你的项目,之后这些文件归你维护。手工接线见下。
|
|
25
|
+
|
|
26
|
+
## peerDependencies
|
|
27
|
+
|
|
28
|
+
`react`、`@tanstack/react-router`、`zod` 三者全部声明为 **optional** peer:纯 BFF 消费者只用 `/proxy`、`/auth`,不该被逼装 React 与 router;纯前端消费者只用 `/react`、`/i18n`,也不该被逼装 zod。装哪几个由你实际用到的子路径决定,缺的那些包管理器不会报警。
|
|
29
|
+
|
|
30
|
+
## 用法
|
|
31
|
+
|
|
32
|
+
### 服务端:auth 运行时
|
|
33
|
+
|
|
34
|
+
`createAbpAuthRuntime` 从环境变量读配置(`AUTH_ISSUER`、`AUTH_CLIENT_ID`、`AUTH_SESSION_SECRET`、`AUTH_REDIRECT_URI`、`AUTH_ABP_BASE_URL`),返回登录/回调/登出所需的一切。每个覆盖项都有默认值:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createAbpAuthRuntime } from "@jcoder-stack/abp-react/proxy";
|
|
38
|
+
|
|
39
|
+
export const createRuntime = () =>
|
|
40
|
+
createAbpAuthRuntime(process.env, {
|
|
41
|
+
// cookies: { session: { name: "myapp_session", maxAge: 60 * 60 * 24 * 14 } },
|
|
42
|
+
// strategies: { password: false },
|
|
43
|
+
// proxy: { timeoutMs: 10_000, retries: 1 },
|
|
44
|
+
// session: { skewSeconds: 30, coalesceTtlMs: 5_000 },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`AUTH_SESSION_SECRET` 至少 32 字符——会话 cookie 用它经 HKDF 派生 AES-GCM 密钥,短于此长度会直接抛错而非静默产出弱密钥。
|
|
49
|
+
|
|
50
|
+
### 客户端:Provider 与 hooks
|
|
51
|
+
|
|
52
|
+
两个 Provider 分开挂:配置(本地化/设置/特性)与身份的失效时机不同,合成一个会让身份刷新连带重建 translator。
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { AppConfigProvider, SessionProvider } from "@jcoder-stack/abp-react/react";
|
|
56
|
+
|
|
57
|
+
<AppConfigProvider config={appState.config} messages={messages} fallbackCulture="en">
|
|
58
|
+
<SessionProvider identity={appState.identity} fetchIdentity={fetchIdentity}>
|
|
59
|
+
{children}
|
|
60
|
+
</SessionProvider>
|
|
61
|
+
</AppConfigProvider>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`messages` 需引用稳定(模块常量或 `useMemo`)——它参与 translator 的重建判定。回调 props(`onMissingKey`、`createTranslator`)经 ref 读取,写成内联箭头函数不会导致 context 重建。
|
|
65
|
+
|
|
66
|
+
### 路由守卫
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { requireAuth, requirePermission } from "@jcoder-stack/abp-react/router";
|
|
70
|
+
|
|
71
|
+
export const Route = createFileRoute("/_layout/_authed")({
|
|
72
|
+
beforeLoad: requireAuth(),
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
守卫是**纯 UX**——真正的安全判定在 ABP 服务端。`requirePermission` 缺权限时跳 `/forbidden`;匿名用户没有任何 grantedPolicies,默认也会落到 403,传 `loginPath` 可让未认证访客先去登录:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
beforeLoad: requirePermission(IdentityPermissions.Users.Default, {
|
|
80
|
+
loginPath: "/api/auth/login",
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
推荐的做法仍是把受保护页面挂在一个跑 `requireAuth()` 的父路由下,`loginPath` 用于单独使用 `requirePermission` 的场景。
|
|
85
|
+
|
|
86
|
+
两者都要求祖先路由的 `beforeLoad` 已把 `identity` 放进 route context;没放会抛出带指引的错误而不是静默放行。
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const currentUserSchema: z.ZodObject<{
|
|
4
|
+
isAuthenticated: z.ZodBoolean;
|
|
5
|
+
id: z.ZodNullable<z.ZodString>;
|
|
6
|
+
userName: z.ZodNullable<z.ZodString>;
|
|
7
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
8
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
9
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
10
|
+
surName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
emailVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
12
|
+
phoneNumber: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
|
+
phoneNumberVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
14
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
15
|
+
impersonatorUserId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
16
|
+
impersonatorTenantId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
17
|
+
impersonatorUserName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
18
|
+
impersonatorTenantName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
19
|
+
roles: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>, z.ZodTransform<string[], string[] | null | undefined>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
type CurrentUser = z.infer<typeof currentUserSchema>;
|
|
22
|
+
declare const currentTenantSchema: z.ZodObject<{
|
|
23
|
+
id: z.ZodNullable<z.ZodString>;
|
|
24
|
+
name: z.ZodNullable<z.ZodString>;
|
|
25
|
+
isAvailable: z.ZodBoolean;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
type CurrentTenant = z.infer<typeof currentTenantSchema>;
|
|
28
|
+
declare const localizationSchema: z.ZodCatch<z.ZodObject<{
|
|
29
|
+
currentCulture: z.ZodCatch<z.ZodObject<{
|
|
30
|
+
name: z.ZodString;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
defaultResourceName: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
33
|
+
languages: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodCatch<z.ZodNullable<z.ZodObject<{
|
|
34
|
+
cultureName: z.ZodString;
|
|
35
|
+
displayName: z.ZodString;
|
|
36
|
+
}, z.core.$strip>>>>, z.ZodTransform<{
|
|
37
|
+
cultureName: string;
|
|
38
|
+
displayName: string;
|
|
39
|
+
}[], ({
|
|
40
|
+
cultureName: string;
|
|
41
|
+
displayName: string;
|
|
42
|
+
} | null)[]>>>;
|
|
43
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
type Localization = z.infer<typeof localizationSchema>;
|
|
46
|
+
declare const applicationConfigurationShape: {
|
|
47
|
+
currentUser: z.ZodObject<{
|
|
48
|
+
isAuthenticated: z.ZodBoolean;
|
|
49
|
+
id: z.ZodNullable<z.ZodString>;
|
|
50
|
+
userName: z.ZodNullable<z.ZodString>;
|
|
51
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
52
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
53
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
54
|
+
surName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
55
|
+
emailVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
56
|
+
phoneNumber: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
57
|
+
phoneNumberVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
58
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
impersonatorUserId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
impersonatorTenantId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
61
|
+
impersonatorUserName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
62
|
+
impersonatorTenantName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
63
|
+
roles: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>, z.ZodTransform<string[], string[] | null | undefined>>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
auth: z.ZodPipe<z.ZodNullable<z.ZodObject<{
|
|
66
|
+
grantedPolicies: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodBoolean>>>, z.ZodTransform<Record<string, boolean>, Record<string, boolean> | null | undefined>>;
|
|
67
|
+
}, z.core.$strip>>, z.ZodTransform<{
|
|
68
|
+
grantedPolicies: Record<string, boolean>;
|
|
69
|
+
}, {
|
|
70
|
+
grantedPolicies: Record<string, boolean>;
|
|
71
|
+
} | null>>;
|
|
72
|
+
setting: z.ZodCatch<z.ZodObject<{
|
|
73
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>>>;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
localization: z.ZodCatch<z.ZodObject<{
|
|
76
|
+
currentCulture: z.ZodCatch<z.ZodObject<{
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
}, z.core.$strip>>;
|
|
79
|
+
defaultResourceName: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
80
|
+
languages: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodCatch<z.ZodNullable<z.ZodObject<{
|
|
81
|
+
cultureName: z.ZodString;
|
|
82
|
+
displayName: z.ZodString;
|
|
83
|
+
}, z.core.$strip>>>>, z.ZodTransform<{
|
|
84
|
+
cultureName: string;
|
|
85
|
+
displayName: string;
|
|
86
|
+
}[], ({
|
|
87
|
+
cultureName: string;
|
|
88
|
+
displayName: string;
|
|
89
|
+
} | null)[]>>>;
|
|
90
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
91
|
+
}, z.core.$strip>>;
|
|
92
|
+
currentTenant: z.ZodObject<{
|
|
93
|
+
id: z.ZodNullable<z.ZodString>;
|
|
94
|
+
name: z.ZodNullable<z.ZodString>;
|
|
95
|
+
isAvailable: z.ZodBoolean;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
features: z.ZodCatch<z.ZodObject<{
|
|
98
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>>>;
|
|
99
|
+
}, z.core.$strip>>;
|
|
100
|
+
};
|
|
101
|
+
/** Runtime schema preserves unknown top-level keys (extraProperties / custom contributors); the exported type omits that index signature so server-fn return values stay serializable. */
|
|
102
|
+
declare const applicationConfigurationSchema: z.ZodObject<{
|
|
103
|
+
currentUser: z.ZodObject<{
|
|
104
|
+
isAuthenticated: z.ZodBoolean;
|
|
105
|
+
id: z.ZodNullable<z.ZodString>;
|
|
106
|
+
userName: z.ZodNullable<z.ZodString>;
|
|
107
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
108
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
109
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
110
|
+
surName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
111
|
+
emailVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
112
|
+
phoneNumber: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
113
|
+
phoneNumberVerified: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
114
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
115
|
+
impersonatorUserId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
116
|
+
impersonatorTenantId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
117
|
+
impersonatorUserName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
118
|
+
impersonatorTenantName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
119
|
+
roles: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>, z.ZodTransform<string[], string[] | null | undefined>>;
|
|
120
|
+
}, z.core.$strip>;
|
|
121
|
+
auth: z.ZodPipe<z.ZodNullable<z.ZodObject<{
|
|
122
|
+
grantedPolicies: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodBoolean>>>, z.ZodTransform<Record<string, boolean>, Record<string, boolean> | null | undefined>>;
|
|
123
|
+
}, z.core.$strip>>, z.ZodTransform<{
|
|
124
|
+
grantedPolicies: Record<string, boolean>;
|
|
125
|
+
}, {
|
|
126
|
+
grantedPolicies: Record<string, boolean>;
|
|
127
|
+
} | null>>;
|
|
128
|
+
setting: z.ZodCatch<z.ZodObject<{
|
|
129
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>>>;
|
|
130
|
+
}, z.core.$strip>>;
|
|
131
|
+
localization: z.ZodCatch<z.ZodObject<{
|
|
132
|
+
currentCulture: z.ZodCatch<z.ZodObject<{
|
|
133
|
+
name: z.ZodString;
|
|
134
|
+
}, z.core.$strip>>;
|
|
135
|
+
defaultResourceName: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
136
|
+
languages: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodCatch<z.ZodNullable<z.ZodObject<{
|
|
137
|
+
cultureName: z.ZodString;
|
|
138
|
+
displayName: z.ZodString;
|
|
139
|
+
}, z.core.$strip>>>>, z.ZodTransform<{
|
|
140
|
+
cultureName: string;
|
|
141
|
+
displayName: string;
|
|
142
|
+
}[], ({
|
|
143
|
+
cultureName: string;
|
|
144
|
+
displayName: string;
|
|
145
|
+
} | null)[]>>>;
|
|
146
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
147
|
+
}, z.core.$strip>>;
|
|
148
|
+
currentTenant: z.ZodObject<{
|
|
149
|
+
id: z.ZodNullable<z.ZodString>;
|
|
150
|
+
name: z.ZodNullable<z.ZodString>;
|
|
151
|
+
isAvailable: z.ZodBoolean;
|
|
152
|
+
}, z.core.$strip>;
|
|
153
|
+
features: z.ZodCatch<z.ZodObject<{
|
|
154
|
+
values: z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>>>;
|
|
155
|
+
}, z.core.$strip>>;
|
|
156
|
+
}, z.core.$loose>;
|
|
157
|
+
type ApplicationConfiguration = z.infer<z.ZodObject<typeof applicationConfigurationShape>>;
|
|
158
|
+
/** Parse ABP application-configuration.
|
|
159
|
+
*
|
|
160
|
+
* Unknown top-level keys pass through. The setting, localization and features subtrees degrade
|
|
161
|
+
* per leaf on shape drift, so one bad language entry or setting value never empties its table.
|
|
162
|
+
* A null auth subtree (or null grantedPolicies) reads as nothing granted.
|
|
163
|
+
*
|
|
164
|
+
* Degradations go to `opts.onError`. A hard failure (a broken currentUser, say) still throws,
|
|
165
|
+
* but only after `opts.onError` has seen it. */
|
|
166
|
+
declare function parseApplicationConfiguration(raw: unknown, opts?: {
|
|
167
|
+
onError?: (error: z.ZodError) => void;
|
|
168
|
+
}): ApplicationConfiguration;
|
|
169
|
+
|
|
170
|
+
export { type ApplicationConfiguration as A, type CurrentTenant as C, type Localization as L, type CurrentUser as a, applicationConfigurationSchema as b, currentTenantSchema as c, currentUserSchema as d, localizationSchema as l, parseApplicationConfiguration as p };
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { a as CookieOptions, T as TokenClient } from './oidc-7fu5kKVF.js';
|
|
2
|
+
export { A as Auth, b as COOKIE_CHUNK_SIZE, C as Codec, c as CodecSchema, d as OidcMetadata, O as OidcStrategy, S as SessionManager, e as TokenClientConfig, f as TokenGrant, g as chunkCookieValue, h as clearChunkedCookie, i as clearCookie, j as createAuth, k as createCodec, l as createSessionManager, m as createTokenClient, n as discoverMetadata, o as oidcMetadataSchema, p as oidcStrategy, q as parseCookieHeader, r as readChunkedCookie, s as serializeCookie, t as toTokenResult } from './oidc-7fu5kKVF.js';
|
|
3
|
+
import { e as Logger } from './logger-BSnS65IC.js';
|
|
4
|
+
import { S as SessionStore, b as AuthStrategy } from './types-Bj0MpXtI.js';
|
|
5
|
+
export { A as AuthSession, B as BeginInput, C as CompleteInput, F as FetchFn, H as Handshake, I as Identity, c as IdentityContext, a as IdentityResolver, T as TokenResult, d as authSessionSchema, e as authTokensSchema, h as handshakeSchema } from './types-Bj0MpXtI.js';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
/** 默认 SessionStore:把整个 AuthSession 密封进(超长时分块的)加密 cookie,自包含无后端。 */
|
|
9
|
+
declare function createCookieSessionStore(opts: {
|
|
10
|
+
secret: string;
|
|
11
|
+
cookieName: string;
|
|
12
|
+
maxAge: number;
|
|
13
|
+
cookieOptions?: CookieOptions;
|
|
14
|
+
logger?: Logger;
|
|
15
|
+
}): SessionStore;
|
|
16
|
+
|
|
17
|
+
/** 解析 ASP.NET Core 文化 cookie 值(`c=zh-Hans|uic=zh-Hans`)为文化名,无则 null。 */
|
|
18
|
+
declare function parseCultureCookie(value: string | undefined): string | null;
|
|
19
|
+
/** 组 ASP.NET Core 文化 cookie 值。 */
|
|
20
|
+
declare function formatCultureCookie(culture: string): string;
|
|
21
|
+
|
|
22
|
+
type AuthErrorCode =
|
|
23
|
+
/** 调用方给策略喂了它不受理的输入。属于编程错误,不该当成用户可修复的失败渲染。 */
|
|
24
|
+
"invalid_input" | "invalid_state"
|
|
25
|
+
/** 握手密文超过服务端允许的寿命。 */
|
|
26
|
+
| "handshake_expired" | "invalid_nonce" | "exchange_failed" | "invalid_credentials" | "provider_denied" | "refresh_failed" | "session_open_failed" | "discovery_failed";
|
|
27
|
+
/** 策略/会话层唯一的错误类型;适配层按 code 映射为 302 ?error= 或 { ok:false, error }。 */
|
|
28
|
+
declare class AuthError extends Error {
|
|
29
|
+
readonly code: AuthErrorCode;
|
|
30
|
+
constructor(code: AuthErrorCode, message?: string, opts?: {
|
|
31
|
+
cause?: unknown;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 解出 id_token 的 payload 段(不验签,签名验证属于 IdP↔BFF 的 TLS 信道 + code 交换信任)。 */
|
|
36
|
+
declare function decodeIdTokenClaims(idToken: string): Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
/** CSPRNG 随机串(base64url);用于 state/nonce/PKCE verifier。 */
|
|
39
|
+
declare function generateRandomString(byteLength?: number): string;
|
|
40
|
+
/** PKCE S256 对:verifier + BASE64URL(SHA-256(verifier))。 */
|
|
41
|
+
declare function generatePkce(): Promise<{
|
|
42
|
+
verifier: string;
|
|
43
|
+
challenge: string;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
/** 把用户提供的 returnUrl 限制为同源绝对路径;协议相对("//"、"/\\")、绝对 URL、含控制字符或空白的值回退为 "/"。 */
|
|
47
|
+
declare function sanitizeReturnUrl(value: string | null | undefined): string;
|
|
48
|
+
|
|
49
|
+
/** password grant(ROPC)策略:自绘登录页一步换 token,不经重定向。 */
|
|
50
|
+
declare function passwordStrategy(cfg: {
|
|
51
|
+
tokenClient: TokenClient;
|
|
52
|
+
now?: () => number;
|
|
53
|
+
logger?: Logger;
|
|
54
|
+
}): AuthStrategy;
|
|
55
|
+
|
|
56
|
+
export { AuthError, type AuthErrorCode, AuthStrategy, CookieOptions, SessionStore, TokenClient, createCookieSessionStore, decodeIdTokenClaims, formatCultureCookie, generatePkce, generateRandomString, parseCultureCookie, passwordStrategy, sanitizeReturnUrl };
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthError,
|
|
3
|
+
COOKIE_CHUNK_SIZE,
|
|
4
|
+
authSessionSchema,
|
|
5
|
+
authTokensSchema,
|
|
6
|
+
chunkCookieValue,
|
|
7
|
+
clearChunkedCookie,
|
|
8
|
+
clearCookie,
|
|
9
|
+
createAuth,
|
|
10
|
+
createCodec,
|
|
11
|
+
createCookieSessionStore,
|
|
12
|
+
createSessionManager,
|
|
13
|
+
createTokenClient,
|
|
14
|
+
decodeIdTokenClaims,
|
|
15
|
+
discoverMetadata,
|
|
16
|
+
formatCultureCookie,
|
|
17
|
+
generatePkce,
|
|
18
|
+
generateRandomString,
|
|
19
|
+
handshakeSchema,
|
|
20
|
+
oidcMetadataSchema,
|
|
21
|
+
oidcStrategy,
|
|
22
|
+
parseCookieHeader,
|
|
23
|
+
parseCultureCookie,
|
|
24
|
+
passwordStrategy,
|
|
25
|
+
readChunkedCookie,
|
|
26
|
+
sanitizeReturnUrl,
|
|
27
|
+
serializeCookie,
|
|
28
|
+
toTokenResult
|
|
29
|
+
} from "./chunk-QQFH53SC.js";
|
|
30
|
+
export {
|
|
31
|
+
AuthError,
|
|
32
|
+
COOKIE_CHUNK_SIZE,
|
|
33
|
+
authSessionSchema,
|
|
34
|
+
authTokensSchema,
|
|
35
|
+
chunkCookieValue,
|
|
36
|
+
clearChunkedCookie,
|
|
37
|
+
clearCookie,
|
|
38
|
+
createAuth,
|
|
39
|
+
createCodec,
|
|
40
|
+
createCookieSessionStore,
|
|
41
|
+
createSessionManager,
|
|
42
|
+
createTokenClient,
|
|
43
|
+
decodeIdTokenClaims,
|
|
44
|
+
discoverMetadata,
|
|
45
|
+
formatCultureCookie,
|
|
46
|
+
generatePkce,
|
|
47
|
+
generateRandomString,
|
|
48
|
+
handshakeSchema,
|
|
49
|
+
oidcMetadataSchema,
|
|
50
|
+
oidcStrategy,
|
|
51
|
+
parseCookieHeader,
|
|
52
|
+
parseCultureCookie,
|
|
53
|
+
passwordStrategy,
|
|
54
|
+
readChunkedCookie,
|
|
55
|
+
sanitizeReturnUrl,
|
|
56
|
+
serializeCookie,
|
|
57
|
+
toTokenResult
|
|
58
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/core/abp-boolean.ts
|
|
2
|
+
function isAbpTrue(value) {
|
|
3
|
+
return value?.toLowerCase() === "true";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/core/application-configuration.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var currentUserSchema = z.object({
|
|
9
|
+
isAuthenticated: z.boolean(),
|
|
10
|
+
id: z.string().nullable(),
|
|
11
|
+
userName: z.string().nullable(),
|
|
12
|
+
tenantId: z.string().nullable(),
|
|
13
|
+
email: z.string().nullable().optional(),
|
|
14
|
+
name: z.string().nullish(),
|
|
15
|
+
// `surName` is ABP's own casing on CurrentUserDto (the identity module's own DTOs use
|
|
16
|
+
// `surname`); mirroring the wire keeps the parse a pure shape check.
|
|
17
|
+
surName: z.string().nullish(),
|
|
18
|
+
emailVerified: z.boolean().nullish(),
|
|
19
|
+
phoneNumber: z.string().nullish(),
|
|
20
|
+
phoneNumberVerified: z.boolean().nullish(),
|
|
21
|
+
sessionId: z.string().nullish(),
|
|
22
|
+
impersonatorUserId: z.string().nullish(),
|
|
23
|
+
impersonatorTenantId: z.string().nullish(),
|
|
24
|
+
impersonatorUserName: z.string().nullish(),
|
|
25
|
+
impersonatorTenantName: z.string().nullish(),
|
|
26
|
+
// swagger marks CurrentUserDto.roles nullable, and ABP does send `null`; `.default([])` only
|
|
27
|
+
// covers the absent case, so a null would otherwise fail the whole config parse.
|
|
28
|
+
roles: z.array(z.string()).nullish().transform((roles) => roles ?? [])
|
|
29
|
+
});
|
|
30
|
+
var currentTenantSchema = z.object({
|
|
31
|
+
id: z.string().nullable(),
|
|
32
|
+
name: z.string().nullable(),
|
|
33
|
+
isAvailable: z.boolean()
|
|
34
|
+
});
|
|
35
|
+
var activeReporter = null;
|
|
36
|
+
function degradeTo(fallback) {
|
|
37
|
+
return (ctx) => {
|
|
38
|
+
activeReporter?.(new z.ZodError(ctx.error.issues));
|
|
39
|
+
return fallback;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var languageSchema = z.object({ cultureName: z.string(), displayName: z.string() });
|
|
43
|
+
var localizationSchema = z.object({
|
|
44
|
+
currentCulture: z.object({ name: z.string() }).catch(degradeTo({ name: "en" })),
|
|
45
|
+
defaultResourceName: z.string().nullish().catch(degradeTo(void 0)),
|
|
46
|
+
// A single drifted entry is dropped on its own, so it cannot take the rest of the list
|
|
47
|
+
// (and with it the whole localization subtree) down with it.
|
|
48
|
+
languages: z.array(languageSchema.nullable().catch(degradeTo(null))).transform((entries) => entries.filter((entry) => entry !== null)).catch(degradeTo([])),
|
|
49
|
+
values: z.record(z.string(), z.record(z.string(), z.string())).catch(degradeTo({}))
|
|
50
|
+
}).catch(degradeTo({ currentCulture: { name: "en" }, languages: [], values: {} }));
|
|
51
|
+
var abpValueSchema = z.string().nullish().transform((value) => value ?? void 0).catch(degradeTo(void 0));
|
|
52
|
+
var abpValueTableSchema = z.object({ values: z.record(z.string(), abpValueSchema).catch(degradeTo({})) }).catch(degradeTo({ values: {} }));
|
|
53
|
+
var applicationConfigurationShape = {
|
|
54
|
+
currentUser: currentUserSchema,
|
|
55
|
+
// swagger marks grantedPolicies nullable and the subtree can arrive null for an anonymous
|
|
56
|
+
// caller; a null converges to "nothing granted" (fail-closed) rather than throwing a white
|
|
57
|
+
// screen, while any other drift still fails fast per this file's contract.
|
|
58
|
+
auth: z.object({
|
|
59
|
+
grantedPolicies: z.record(z.string(), z.boolean()).nullish().transform((policies) => policies ?? {})
|
|
60
|
+
}).nullable().transform((auth) => auth ?? { grantedPolicies: {} }),
|
|
61
|
+
setting: abpValueTableSchema,
|
|
62
|
+
localization: localizationSchema,
|
|
63
|
+
currentTenant: currentTenantSchema,
|
|
64
|
+
features: abpValueTableSchema
|
|
65
|
+
};
|
|
66
|
+
var applicationConfigurationSchema = z.looseObject(applicationConfigurationShape);
|
|
67
|
+
function parseApplicationConfiguration(raw, opts) {
|
|
68
|
+
const outerReporter = activeReporter;
|
|
69
|
+
activeReporter = opts?.onError ?? null;
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = applicationConfigurationSchema.safeParse(raw);
|
|
73
|
+
} finally {
|
|
74
|
+
activeReporter = outerReporter;
|
|
75
|
+
}
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
opts?.onError?.(result.error);
|
|
78
|
+
throw result.error;
|
|
79
|
+
}
|
|
80
|
+
return result.data;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/core/errors.ts
|
|
84
|
+
import { z as z2 } from "zod";
|
|
85
|
+
var HttpError = class extends Error {
|
|
86
|
+
status;
|
|
87
|
+
code;
|
|
88
|
+
validationErrors;
|
|
89
|
+
body;
|
|
90
|
+
constructor(status, message, options) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "HttpError";
|
|
93
|
+
this.status = status;
|
|
94
|
+
this.code = options?.code;
|
|
95
|
+
this.validationErrors = options?.validationErrors;
|
|
96
|
+
this.body = options?.body;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function isAbpEnvelope(body) {
|
|
100
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return false;
|
|
101
|
+
const error = body.error;
|
|
102
|
+
return typeof error === "object" && error !== null;
|
|
103
|
+
}
|
|
104
|
+
var validationErrorsSchema = z2.array(
|
|
105
|
+
z2.object({
|
|
106
|
+
message: z2.string(),
|
|
107
|
+
members: z2.array(z2.string()).nullish().transform((members) => members ?? void 0)
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
function toValidationErrors(value) {
|
|
111
|
+
const parsed = validationErrorsSchema.safeParse(value);
|
|
112
|
+
return parsed.success ? parsed.data : void 0;
|
|
113
|
+
}
|
|
114
|
+
function toHttpError(status, body) {
|
|
115
|
+
if (isAbpEnvelope(body)) {
|
|
116
|
+
const error = body.error;
|
|
117
|
+
return new HttpError(status, error.message ?? `HTTP ${status}`, {
|
|
118
|
+
code: error.code,
|
|
119
|
+
validationErrors: toValidationErrors(error.validationErrors),
|
|
120
|
+
body
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return new HttpError(status, `HTTP ${status}`, { body });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/core/paged.ts
|
|
127
|
+
function toPagedResult(dto) {
|
|
128
|
+
return { items: dto?.items ?? [], totalCount: dto?.totalCount ?? 0 };
|
|
129
|
+
}
|
|
130
|
+
function toAbpListParams(state) {
|
|
131
|
+
const pageSize = Math.max(1, state.pageSize);
|
|
132
|
+
const params = {
|
|
133
|
+
SkipCount: Math.max(0, state.pageIndex) * pageSize,
|
|
134
|
+
MaxResultCount: pageSize
|
|
135
|
+
};
|
|
136
|
+
if (state.sorting !== void 0 && state.sorting.length > 0) {
|
|
137
|
+
params.Sorting = state.sorting.map((s) => s.desc ? `${s.id} desc` : s.id).join(",");
|
|
138
|
+
}
|
|
139
|
+
const filter = state.filter?.trim();
|
|
140
|
+
if (filter) params.Filter = filter;
|
|
141
|
+
return params;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
isAbpTrue,
|
|
146
|
+
currentUserSchema,
|
|
147
|
+
currentTenantSchema,
|
|
148
|
+
localizationSchema,
|
|
149
|
+
applicationConfigurationSchema,
|
|
150
|
+
parseApplicationConfiguration,
|
|
151
|
+
HttpError,
|
|
152
|
+
toHttpError,
|
|
153
|
+
toPagedResult,
|
|
154
|
+
toAbpListParams
|
|
155
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/permissions/is-granted.ts
|
|
2
|
+
function isGranted(policies, policy, opts) {
|
|
3
|
+
const list = Array.isArray(policy) ? policy : [policy];
|
|
4
|
+
const strategy = opts?.strategy ?? "all";
|
|
5
|
+
return strategy === "all" ? list.every((p) => policies[p] === true) : list.some((p) => policies[p] === true);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/permissions/checker.ts
|
|
9
|
+
function createPermissionChecker(policies) {
|
|
10
|
+
const check = (list, strategy) => list.length > 0 && isGranted(policies, list, { strategy });
|
|
11
|
+
const can = ((policy) => check(Array.isArray(policy) ? policy : [policy], "all"));
|
|
12
|
+
can.all = (...ps) => check(ps.flat(), "all");
|
|
13
|
+
can.any = (...ps) => check(ps.flat(), "any");
|
|
14
|
+
can.not = (policy) => !isGranted(policies, policy);
|
|
15
|
+
return can;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
isGranted,
|
|
20
|
+
createPermissionChecker
|
|
21
|
+
};
|