@h-ai/serv 0.1.0-alpha.59 → 0.1.0-alpha.60
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.en.md +8 -0
- package/README.ja.md +8 -0
- package/README.md +8 -0
- package/dist/features/iam.d.ts +164 -1
- package/dist/features/iam.js +4 -4
- package/dist/features/iam.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/README.en.md
CHANGED
|
@@ -593,6 +593,14 @@ const app = serv.createApp({
|
|
|
593
593
|
|
|
594
594
|
In production, empty or `*` `_serv.yml` `cors.origin` fails startup. Web and Capacitor/Tauri origins use exact normalized full-Origin matching to prevent suffix spoofing. Production also enforces Secure refresh cookies and disables OpenAPI/docs; configuration controls nonproduction endpoints.
|
|
595
595
|
|
|
596
|
+
## Configuration driven authentication
|
|
597
|
+
|
|
598
|
+
Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
|
|
599
|
+
|
|
600
|
+
createIamProcedures({ iam }) implements public config, password/LDAP/OTP login, registration and recovery directly. LDAP uses the existing refresh Cookie/native transport policy. Administrator creation uses iam.user.createUser and remains available when public registration is disabled; existing permission guards apply.
|
|
601
|
+
|
|
602
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.en.md)
|
|
603
|
+
|
|
596
604
|
## Error handling
|
|
597
605
|
|
|
598
606
|
Ordinary procedures return authentication, authorization, business failures and caught handler exceptions as HaiResult over HTTP 200 by default. `error.httpStatus` is domain-error metadata and does not change the response status. Clients must check `success` and `error.code`: an unauthenticated failure has `error.httpStatus=401`, while database query failures or INTERNAL_ERROR have 500.
|
package/README.ja.md
CHANGED
|
@@ -593,6 +593,14 @@ const app = serv.createApp({
|
|
|
593
593
|
|
|
594
594
|
本番では `_serv.yml` の `cors.origin` が空・`*` なら起動に失敗します。Web・Capacitor/Tauri は正規化済み Origin 全体を厳密照合し、末尾偽装を防ぎます。本番は Secure refresh cookie を強制し OpenAPI/docs を無効化します。非本番の endpoint は設定ファイルで決めます。
|
|
595
595
|
|
|
596
|
+
## 設定による認証画面
|
|
597
|
+
|
|
598
|
+
規約は設定の userAgreementMarkdown/privacyPolicyMarkdown または URL を使用できます。同じ規約は Markdown 本文を優先して安全な Modal で描画します。
|
|
599
|
+
|
|
600
|
+
createIamProcedures({ iam }) は公開設定、LDAP/OTP/パスワード、登録、復旧を直接実装します。LDAP も既存の Cookie/ネイティブ Token 方針を使用し、自助登録が無効でも管理者のユーザー作成は権限検証の下で利用できます。
|
|
601
|
+
|
|
602
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
|
|
603
|
+
|
|
596
604
|
## エラー処理
|
|
597
605
|
|
|
598
606
|
通常の procedure は認証・認可・業務の失敗と捕捉した handler 例外を HaiResult として既定で HTTP 200 で返します。`error.httpStatus` はドメインエラーのメタデータで、HTTP 応答ステータスを変更しません。クライアントは `success` と `error.code` を確認してください。未認証では `error.httpStatus=401`、DB クエリー失敗や INTERNAL_ERROR では 500 です。
|
package/README.md
CHANGED
|
@@ -593,6 +593,14 @@ const app = serv.createApp({
|
|
|
593
593
|
|
|
594
594
|
生产环境下 `_serv.yml` 的 `cors.origin` 为空或为 `*` 会在启动时失败;Web 与 Capacitor/Tauri Origin 均按规范化后的完整 Origin 精确匹配,避免后缀伪造。生产环境同时强制 Secure refresh cookie 并关闭 OpenAPI/docs,配置文件仍决定非生产环境具体挂载哪些端点。
|
|
595
595
|
|
|
596
|
+
## 配置驱动认证页面
|
|
597
|
+
|
|
598
|
+
协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
|
|
599
|
+
|
|
600
|
+
`createIamProcedures({ iam })` 直接实现公开配置、密码/LDAP/OTP、注册与密码重置,无需应用复制 procedure。LDAP 登录沿用现有浏览器 httpOnly refresh Cookie 和可信原生 Token 传输。管理端创建调用 iam.user.createUser,自助注册关闭时管理功能仍可用;授权仍由既有 permission guard 执行。
|
|
601
|
+
|
|
602
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.md)
|
|
603
|
+
|
|
596
604
|
## 错误处理
|
|
597
605
|
|
|
598
606
|
普通 procedure 的认证、授权、业务失败和被捕获的 handler 异常都作为 `HaiResult` 输出,默认通过 HTTP 200 返回;`error.httpStatus` 是领域错误元数据,不会自动改写响应状态。客户端必须检查 `success` 与 `error.code`。例如未登录为 `error.httpStatus=401`,数据库查询失败或 INTERNAL_ERROR 为 500。
|
package/dist/features/iam.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as _orpc_contract from '@orpc/contract';
|
|
2
1
|
import * as _orpc_server from '@orpc/server';
|
|
3
2
|
import * as zod_v4_core from 'zod/v4/core';
|
|
4
3
|
import * as zod from 'zod';
|
|
4
|
+
import * as _orpc_contract from '@orpc/contract';
|
|
5
5
|
import { IamFunctions } from '@h-ai/iam';
|
|
6
6
|
export { TokenPair } from '@h-ai/iam';
|
|
7
7
|
import { S as ServContext } from '../serv-context-CkbS0M9G.js';
|
|
@@ -14,6 +14,163 @@ interface IamProcedureDeps {
|
|
|
14
14
|
/** 创建 IAM 默认 procedures。 */
|
|
15
15
|
declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
16
16
|
auth: {
|
|
17
|
+
publicConfig: _orpc_server.Procedure<ServContext & {}, any, _orpc_contract.Schema<unknown, unknown>, zod.ZodDiscriminatedUnion<[zod.ZodObject<{
|
|
18
|
+
success: zod.ZodLiteral<true>;
|
|
19
|
+
data: zod.ZodNullable<zod.ZodObject<{
|
|
20
|
+
password: zod.ZodObject<{
|
|
21
|
+
minLength: zod.ZodNumber;
|
|
22
|
+
maxLength: zod.ZodNumber;
|
|
23
|
+
requireUppercase: zod.ZodBoolean;
|
|
24
|
+
requireLowercase: zod.ZodBoolean;
|
|
25
|
+
requireNumber: zod.ZodBoolean;
|
|
26
|
+
requireSpecialChar: zod.ZodBoolean;
|
|
27
|
+
expirationDays: zod.ZodNumber;
|
|
28
|
+
}, zod_v4_core.$strip>;
|
|
29
|
+
register: zod.ZodObject<{
|
|
30
|
+
enabled: zod.ZodBoolean;
|
|
31
|
+
}, zod_v4_core.$strip>;
|
|
32
|
+
login: zod.ZodObject<{
|
|
33
|
+
password: zod.ZodBoolean;
|
|
34
|
+
otp: zod.ZodBoolean;
|
|
35
|
+
ldap: zod.ZodBoolean;
|
|
36
|
+
email: zod.ZodBoolean;
|
|
37
|
+
sms: zod.ZodBoolean;
|
|
38
|
+
}, zod_v4_core.$strip>;
|
|
39
|
+
passwordReset: zod.ZodObject<{
|
|
40
|
+
enabled: zod.ZodBoolean;
|
|
41
|
+
mode: zod.ZodEnum<{
|
|
42
|
+
email: "email";
|
|
43
|
+
phone: "phone";
|
|
44
|
+
}>;
|
|
45
|
+
}, zod_v4_core.$strip>;
|
|
46
|
+
otp: zod.ZodObject<{
|
|
47
|
+
length: zod.ZodNumber;
|
|
48
|
+
resendInterval: zod.ZodNumber;
|
|
49
|
+
}, zod_v4_core.$strip>;
|
|
50
|
+
agreements: zod.ZodObject<{
|
|
51
|
+
userAgreementMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
52
|
+
privacyPolicyMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
53
|
+
userAgreementUrl: zod.ZodOptional<zod.ZodString>;
|
|
54
|
+
privacyPolicyUrl: zod.ZodOptional<zod.ZodString>;
|
|
55
|
+
showOnRegister: zod.ZodBoolean;
|
|
56
|
+
showOnLogin: zod.ZodBoolean;
|
|
57
|
+
}, zod_v4_core.$strip>;
|
|
58
|
+
ui: zod.ZodObject<{
|
|
59
|
+
variant: zod.ZodEnum<{
|
|
60
|
+
split: "split";
|
|
61
|
+
card: "card";
|
|
62
|
+
}>;
|
|
63
|
+
title: zod.ZodOptional<zod.ZodString>;
|
|
64
|
+
subtitle: zod.ZodOptional<zod.ZodString>;
|
|
65
|
+
brandTitle: zod.ZodOptional<zod.ZodString>;
|
|
66
|
+
brandText: zod.ZodOptional<zod.ZodString>;
|
|
67
|
+
logoUrl: zod.ZodOptional<zod.ZodString>;
|
|
68
|
+
illustrationUrl: zod.ZodOptional<zod.ZodString>;
|
|
69
|
+
}, zod_v4_core.$strip>;
|
|
70
|
+
session: zod.ZodObject<{
|
|
71
|
+
maxAge: zod.ZodNumber;
|
|
72
|
+
}, zod_v4_core.$strip>;
|
|
73
|
+
}, zod_v4_core.$strip>>;
|
|
74
|
+
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
75
|
+
success: zod.ZodLiteral<false>;
|
|
76
|
+
error: zod.ZodObject<{
|
|
77
|
+
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
78
|
+
message: zod.ZodString;
|
|
79
|
+
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
80
|
+
system: zod.ZodOptional<zod.ZodString>;
|
|
81
|
+
module: zod.ZodOptional<zod.ZodString>;
|
|
82
|
+
cause: zod.ZodOptional<zod.ZodUnknown>;
|
|
83
|
+
suggestion: zod.ZodOptional<zod.ZodString>;
|
|
84
|
+
ext: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
85
|
+
}, zod_v4_core.$strip>;
|
|
86
|
+
}, zod_v4_core.$strip>], "success">, Record<never, never>, Record<never, never>>;
|
|
87
|
+
loginWithLdap: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
|
|
88
|
+
username: zod.ZodString;
|
|
89
|
+
password: zod.ZodString;
|
|
90
|
+
}, zod_v4_core.$strip>, zod.ZodDiscriminatedUnion<[zod.ZodObject<{
|
|
91
|
+
success: zod.ZodLiteral<true>;
|
|
92
|
+
data: zod.ZodObject<{
|
|
93
|
+
user: zod.ZodObject<{
|
|
94
|
+
id: zod.ZodString;
|
|
95
|
+
username: zod.ZodString;
|
|
96
|
+
email: zod.ZodOptional<zod.ZodString>;
|
|
97
|
+
phone: zod.ZodOptional<zod.ZodString>;
|
|
98
|
+
displayName: zod.ZodOptional<zod.ZodString>;
|
|
99
|
+
avatarUrl: zod.ZodOptional<zod.ZodString>;
|
|
100
|
+
enabled: zod.ZodBoolean;
|
|
101
|
+
emailVerified: zod.ZodOptional<zod.ZodBoolean>;
|
|
102
|
+
phoneVerified: zod.ZodOptional<zod.ZodBoolean>;
|
|
103
|
+
createdAt: zod.ZodCoercedDate<unknown>;
|
|
104
|
+
updatedAt: zod.ZodCoercedDate<unknown>;
|
|
105
|
+
metadata: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
106
|
+
}, zod_v4_core.$strip>;
|
|
107
|
+
tokens: zod.ZodObject<{
|
|
108
|
+
accessToken: zod.ZodString;
|
|
109
|
+
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
110
|
+
expiresIn: zod.ZodNumber;
|
|
111
|
+
tokenType: zod.ZodLiteral<"Bearer">;
|
|
112
|
+
}, zod_v4_core.$strip>;
|
|
113
|
+
roles: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
114
|
+
permissions: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
115
|
+
agreements: zod.ZodOptional<zod.ZodObject<{
|
|
116
|
+
userAgreementMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
117
|
+
privacyPolicyMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
118
|
+
userAgreementUrl: zod.ZodOptional<zod.ZodString>;
|
|
119
|
+
privacyPolicyUrl: zod.ZodOptional<zod.ZodString>;
|
|
120
|
+
showOnRegister: zod.ZodBoolean;
|
|
121
|
+
showOnLogin: zod.ZodBoolean;
|
|
122
|
+
}, zod_v4_core.$strip>>;
|
|
123
|
+
}, zod_v4_core.$strip>;
|
|
124
|
+
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
125
|
+
success: zod.ZodLiteral<false>;
|
|
126
|
+
error: zod.ZodObject<{
|
|
127
|
+
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
128
|
+
message: zod.ZodString;
|
|
129
|
+
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
130
|
+
system: zod.ZodOptional<zod.ZodString>;
|
|
131
|
+
module: zod.ZodOptional<zod.ZodString>;
|
|
132
|
+
cause: zod.ZodOptional<zod.ZodUnknown>;
|
|
133
|
+
suggestion: zod.ZodOptional<zod.ZodString>;
|
|
134
|
+
ext: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
135
|
+
}, zod_v4_core.$strip>;
|
|
136
|
+
}, zod_v4_core.$strip>], "success">, Record<never, never>, Record<never, never>>;
|
|
137
|
+
requestPasswordReset: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
|
|
138
|
+
identifier: zod.ZodString;
|
|
139
|
+
}, zod_v4_core.$strip>, zod.ZodDiscriminatedUnion<[zod.ZodObject<{
|
|
140
|
+
success: zod.ZodLiteral<true>;
|
|
141
|
+
data: zod.ZodVoid;
|
|
142
|
+
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
143
|
+
success: zod.ZodLiteral<false>;
|
|
144
|
+
error: zod.ZodObject<{
|
|
145
|
+
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
146
|
+
message: zod.ZodString;
|
|
147
|
+
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
148
|
+
system: zod.ZodOptional<zod.ZodString>;
|
|
149
|
+
module: zod.ZodOptional<zod.ZodString>;
|
|
150
|
+
cause: zod.ZodOptional<zod.ZodUnknown>;
|
|
151
|
+
suggestion: zod.ZodOptional<zod.ZodString>;
|
|
152
|
+
ext: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
153
|
+
}, zod_v4_core.$strip>;
|
|
154
|
+
}, zod_v4_core.$strip>], "success">, Record<never, never>, Record<never, never>>;
|
|
155
|
+
confirmPasswordReset: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
|
|
156
|
+
token: zod.ZodString;
|
|
157
|
+
newPassword: zod.ZodString;
|
|
158
|
+
}, zod_v4_core.$strip>, zod.ZodDiscriminatedUnion<[zod.ZodObject<{
|
|
159
|
+
success: zod.ZodLiteral<true>;
|
|
160
|
+
data: zod.ZodVoid;
|
|
161
|
+
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
162
|
+
success: zod.ZodLiteral<false>;
|
|
163
|
+
error: zod.ZodObject<{
|
|
164
|
+
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
165
|
+
message: zod.ZodString;
|
|
166
|
+
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
167
|
+
system: zod.ZodOptional<zod.ZodString>;
|
|
168
|
+
module: zod.ZodOptional<zod.ZodString>;
|
|
169
|
+
cause: zod.ZodOptional<zod.ZodUnknown>;
|
|
170
|
+
suggestion: zod.ZodOptional<zod.ZodString>;
|
|
171
|
+
ext: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
|
|
172
|
+
}, zod_v4_core.$strip>;
|
|
173
|
+
}, zod_v4_core.$strip>], "success">, Record<never, never>, Record<never, never>>;
|
|
17
174
|
login: _orpc_server.Procedure<ServContext & {}, any, zod.ZodObject<{
|
|
18
175
|
identifier: zod.ZodString;
|
|
19
176
|
password: zod.ZodString;
|
|
@@ -44,6 +201,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
|
44
201
|
roles: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
45
202
|
permissions: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
46
203
|
agreements: zod.ZodOptional<zod.ZodObject<{
|
|
204
|
+
userAgreementMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
205
|
+
privacyPolicyMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
47
206
|
userAgreementUrl: zod.ZodOptional<zod.ZodString>;
|
|
48
207
|
privacyPolicyUrl: zod.ZodOptional<zod.ZodString>;
|
|
49
208
|
showOnRegister: zod.ZodBoolean;
|
|
@@ -92,6 +251,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
|
92
251
|
roles: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
93
252
|
permissions: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
94
253
|
agreements: zod.ZodOptional<zod.ZodObject<{
|
|
254
|
+
userAgreementMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
255
|
+
privacyPolicyMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
95
256
|
userAgreementUrl: zod.ZodOptional<zod.ZodString>;
|
|
96
257
|
privacyPolicyUrl: zod.ZodOptional<zod.ZodString>;
|
|
97
258
|
showOnRegister: zod.ZodBoolean;
|
|
@@ -238,6 +399,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
|
|
|
238
399
|
roles: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
239
400
|
permissions: zod.ZodDefault<zod.ZodArray<zod.ZodString>>;
|
|
240
401
|
agreements: zod.ZodOptional<zod.ZodObject<{
|
|
402
|
+
userAgreementMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
403
|
+
privacyPolicyMarkdown: zod.ZodOptional<zod.ZodString>;
|
|
241
404
|
userAgreementUrl: zod.ZodOptional<zod.ZodString>;
|
|
242
405
|
privacyPolicyUrl: zod.ZodOptional<zod.ZodString>;
|
|
243
406
|
showOnRegister: zod.ZodBoolean;
|
package/dist/features/iam.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { mapHaiResult } from '../chunk-27PCPE37.js';
|
|
2
2
|
import { implement, servM } from '../chunk-R354OMWL.js';
|
|
3
3
|
import { apiContract } from '@h-ai/api-contract';
|
|
4
|
-
import { core, err, HaiCommonError
|
|
4
|
+
import { core, ok, err, HaiCommonError } from '@h-ai/core';
|
|
5
5
|
|
|
6
6
|
var iamContract = apiContract.iam;
|
|
7
7
|
var iamFeatureLogger = core.logger.child({ module: "serv", scope: "feature-iam" });
|
|
8
8
|
function createIamProcedures(deps) {
|
|
9
9
|
const { iam } = deps;
|
|
10
|
-
return implement(iamContract).context().route("auth.login", ({ input }) => iam.auth.login(input)).route("auth.loginWithOtp", ({ input }) => iam.auth.loginWithOtp(input)).route("auth.logout").auth().handle(({ input, context }) => {
|
|
10
|
+
return implement(iamContract).context().route("auth.login", ({ input }) => iam.auth.login(input)).route("auth.loginWithOtp", ({ input }) => iam.auth.loginWithOtp(input)).route("auth.loginWithLdap", ({ input }) => iam.auth.loginWithLdap(input)).route("auth.publicConfig", () => ok(iam.publicConfig)).route("auth.requestPasswordReset", ({ input }) => iam.user.requestPasswordReset(input.identifier)).route("auth.confirmPasswordReset", ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword)).route("auth.logout").auth().handle(({ input, context }) => {
|
|
11
11
|
const token = input.accessToken ?? context.accessToken;
|
|
12
12
|
if (!token)
|
|
13
13
|
return err(HaiCommonError.UNAUTHORIZED, servM("serv_errorUnauthorized", { locale: context.locale }));
|
|
@@ -46,7 +46,7 @@ function createIamProcedures(deps) {
|
|
|
46
46
|
}).route("roles.delete").permission("iam.roles.write").handle(({ input }) => iam.authz.deleteRole(input.id)).route("permissions.list").permission("iam.permissions.read").handle(({ input }) => iam.authz.getAllPermissions(input)).route("permissions.get").permission("iam.permissions.read").handle(({ input }) => iam.authz.getPermission(input.id)).route("permissions.create").permission("iam.permissions.write").handle(({ input }) => iam.authz.createPermission(input)).route("permissions.delete").permission("iam.permissions.write").handle(({ input }) => iam.authz.deletePermission(input.id)).build();
|
|
47
47
|
}
|
|
48
48
|
async function createUser(iam, input) {
|
|
49
|
-
const registerResult = await iam.user.
|
|
49
|
+
const registerResult = await iam.user.createUser(input);
|
|
50
50
|
if (!registerResult.success)
|
|
51
51
|
return registerResult;
|
|
52
52
|
const userId = registerResult.data.user.id;
|
|
@@ -55,7 +55,7 @@ async function createUser(iam, input) {
|
|
|
55
55
|
if (!deleteResult.success)
|
|
56
56
|
iamFeatureLogger.error("createUser rollback failed, orphan user may exist", { userId, reason, error: deleteResult.error });
|
|
57
57
|
}
|
|
58
|
-
if (input.roleIds
|
|
58
|
+
if (input.roleIds !== void 0) {
|
|
59
59
|
const rolesResult = await iam.authz.syncRoles(userId, input.roleIds);
|
|
60
60
|
if (!rolesResult.success) {
|
|
61
61
|
await rollback("syncRoles failed");
|
package/dist/features/iam.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/** IAM 默认 procedures 依赖。 */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/** 创建 IAM 默认 procedures。 */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.login', ({ input }) => iam.auth.login(input))\n .route('auth.loginWithOtp', ({ input }) => iam.auth.loginWithOtp(input))\n\n .route('auth.logout')\n .auth()\n .handle(({ input, context }) => {\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier))\n .route('auth.register', ({ input }) => iam.auth.registerAndLogin(input))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.updateCurrentUser(token, input)\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, { include: ['roles'] }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input }) => createUser(iam, input))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input }) => updateUser(iam, input))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return iam.user.deleteUser(input.id)\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input }) => iam.user.adminResetPassword(input.id, input.newPassword))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input }) => iam.authz.createRole(input))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input }) => {\n const { id, ...data } = input\n return iam.authz.updateRole(id, data)\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input }) => iam.authz.deleteRole(input.id))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input }) => iam.authz.createPermission(input))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input }) => iam.authz.deletePermission(input.id))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput): Promise<HaiResult<User>> {\n const registerResult = await iam.user.register(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds?.length) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n if (input.enabled === false) {\n const updateResult = await iam.user.updateUser(userId, { enabled: false })\n if (!updateResult.success) {\n await rollback('initial disable failed')\n return updateResult\n }\n return updateResult\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":";;;;;AAqBA,IAAM,cAAc,WAAA,CAAY,GAAA;AAEhC,IAAM,gBAAA,GAAmB,KAAK,MAAA,CAAO,KAAA,CAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,KAAA,EAAO,aAAA,EAAe,CAAA;AAQ5E,SAAS,oBAAoB,IAAA,EAAwB;AAC1D,EAAA,MAAM,EAAE,KAAI,GAAI,IAAA;AAEhB,EAAA,OAAO,UAAU,WAAW,CAAA,CACzB,SAAqB,CAErB,KAAA,CAAM,cAAc,CAAC,EAAE,KAAA,EAAM,KAAM,IAAI,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA,CACxD,MAAM,mBAAA,EAAqB,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,IAAA,CAAK,aAAa,KAAK,CAAC,EACtE,KAAA,CAAM,oBAAA,EAAsB,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,IAAA,CAAK,cAAc,KAAK,CAAC,EACxE,KAAA,CAAM,mBAAA,EAAqB,MAAM,EAAA,CAAG,IAAI,YAAY,CAAC,EACrD,KAAA,CAAM,2BAAA,EAA6B,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,IAAA,CAAK,qBAAqB,KAAA,CAAM,UAAU,CAAC,CAAA,CACjG,KAAA,CAAM,6BAA6B,CAAC,EAAE,KAAA,EAAM,KAAM,IAAI,IAAA,CAAK,oBAAA,CAAqB,MAAM,KAAA,EAAO,KAAA,CAAM,WAAW,CAAC,CAAA,CAE/G,MAAM,aAAa,CAAA,CACnB,MAAK,CACL,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAC9B,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,WAAA,IAAe,OAAA,CAAQ,WAAA;AAC3C,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAAA,EAC9B,CAAC,CAAA,CAEA,KAAA,CAAM,kBAAkB,CAAA,CACxB,IAAA,EAAK,CACL,MAAA,CAAO,OAAO,EAAE,OAAA,EAAQ,KAAM;AAC7B,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAErG,IAAA,OAAO,YAAA;AAAA,MACL,MAAM,GAAA,CAAI,IAAA,CAAK,cAAA,CAAe,KAAK,CAAA;AAAA,MACnC,CAAA,IAAA,MAAS;AAAA,QACP,GAAG,IAAA;AAAA,QACH,KAAA,EAAO,QAAQ,OAAA,CAAQ,KAAA;AAAA,QACvB,WAAA,EAAa,QAAQ,OAAA,CAAQ;AAAA,OAC/B;AAAA,KACF;AAAA,EACF,CAAC,CAAA,CAEA,KAAA,CAAM,gBAAgB,OAAO,EAAE,OAAM,KAAM;AAC1C,IAAA,OAAO,YAAA,CAAa,MAAM,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,YAAY,CAAA,EAAG,CAAA,MAAA,MAAW,EAAE,MAAA,EAAO,CAAE,CAAA;AAAA,EAC3F,CAAC,CAAA,CACA,KAAA,CAAM,cAAA,EAAgB,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,KAAA,CAAM,UAAU,CAAC,CAAA,CACvE,MAAM,eAAA,EAAiB,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,IAAA,CAAK,gBAAA,CAAiB,KAAK,CAAC,CAAA,CAEtE,KAAA,CAAM,qBAAqB,CAAA,CAC3B,MAAK,CACL,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAC9B,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,IAAI,IAAA,CAAK,yBAAA,CAA0B,OAAO,KAAA,CAAM,WAAA,EAAa,MAAM,WAAW,CAAA;AAAA,EACvF,CAAC,CAAA,CAEA,KAAA,CAAM,wBAAwB,CAAA,CAC9B,IAAA,EAAK,CACL,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,OAAA,EAAQ,KAAM;AAC9B,IAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,YAAA,EAAc,KAAA,CAAM,wBAAA,EAA0B,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AACrG,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,iBAAA,CAAkB,KAAA,EAAO,KAAK,CAAA;AAAA,EAChD,CAAC,CAAA,CAEA,KAAA,CAAM,YAAY,CAAA,CAClB,UAAA,CAAW,gBAAgB,CAAA,CAC3B,MAAA,CAAO,CAAC,EAAE,OAAM,KAAM,GAAA,CAAI,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAE/C,KAAA,CAAM,WAAW,CAAA,CACjB,WAAW,gBAAgB,CAAA,CAC3B,OAAO,CAAC,EAAE,OAAM,KAAM,GAAA,CAAI,KAAK,OAAA,CAAQ,KAAA,CAAM,IAAI,EAAE,OAAA,EAAS,CAAC,OAAO,CAAA,EAAG,CAAC,CAAA,CAExE,KAAA,CAAM,cAAc,EACpB,UAAA,CAAW,iBAAiB,EAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,UAAA,CAAW,GAAA,EAAK,KAAK,CAAC,CAAA,CAE5C,MAAM,cAAc,CAAA,CACpB,WAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,WAAW,GAAA,EAAK,KAAK,CAAC,CAAA,CAE5C,KAAA,CAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,SAAQ,KAAM;AAE9B,IAAA,IAAI,OAAA,CAAQ,OAAA,CAAQ,MAAA,KAAW,KAAA,CAAM,EAAA;AACnC,MAAA,OAAO,GAAA,CAAI,cAAA,CAAe,SAAA,EAAW,KAAA,CAAM,iCAAA,EAAmC,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA;AAC3G,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,KAAA,CAAM,EAAE,CAAA;AAAA,EACrC,CAAC,CAAA,CAEA,KAAA,CAAM,qBAAqB,CAAA,CAC3B,WAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,IAAI,IAAA,CAAK,kBAAA,CAAmB,MAAM,EAAA,EAAI,KAAA,CAAM,WAAW,CAAC,EAE9E,KAAA,CAAM,YAAY,CAAA,CAClB,UAAA,CAAW,gBAAgB,CAAA,CAC3B,MAAA,CAAO,MAAM,GAAA,CAAI,MAAM,WAAA,EAAa,EAEpC,KAAA,CAAM,WAAW,EACjB,UAAA,CAAW,gBAAgB,CAAA,CAC3B,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,GAAA,CAAI,MAAM,OAAA,CAAQ,KAAA,CAAM,EAAE,CAAC,EAEjD,KAAA,CAAM,cAAc,EACpB,UAAA,CAAW,iBAAiB,EAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,KAAA,CAAM,UAAA,CAAW,KAAK,CAAC,CAAA,CAEjD,KAAA,CAAM,cAAc,CAAA,CACpB,WAAW,iBAAiB,CAAA,CAC5B,OAAO,CAAC,EAAE,OAAM,KAAM;AACrB,IAAA,MAAM,EAAE,EAAA,EAAI,GAAG,IAAA,EAAK,GAAI,KAAA;AACxB,IAAA,OAAO,GAAA,CAAI,KAAA,CAAM,UAAA,CAAW,EAAA,EAAI,IAAI,CAAA;AAAA,EACtC,CAAC,CAAA,CAEA,KAAA,CAAM,cAAc,CAAA,CACpB,UAAA,CAAW,iBAAiB,CAAA,CAC5B,MAAA,CAAO,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,KAAA,CAAM,WAAW,KAAA,CAAM,EAAE,CAAC,CAAA,CAEpD,KAAA,CAAM,kBAAkB,CAAA,CACxB,UAAA,CAAW,sBAAsB,CAAA,CACjC,MAAA,CAAO,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,KAAA,CAAM,kBAAkB,KAAK,CAAC,EAExD,KAAA,CAAM,iBAAiB,EACvB,UAAA,CAAW,sBAAsB,EACjC,MAAA,CAAO,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,KAAA,CAAM,cAAc,KAAA,CAAM,EAAE,CAAC,CAAA,CAEvD,KAAA,CAAM,oBAAoB,CAAA,CAC1B,UAAA,CAAW,uBAAuB,CAAA,CAClC,MAAA,CAAO,CAAC,EAAE,KAAA,OAAY,GAAA,CAAI,KAAA,CAAM,iBAAiB,KAAK,CAAC,EAEvD,KAAA,CAAM,oBAAoB,EAC1B,UAAA,CAAW,uBAAuB,EAClC,MAAA,CAAO,CAAC,EAAE,KAAA,EAAM,KAAM,IAAI,KAAA,CAAM,gBAAA,CAAiB,MAAM,EAAE,CAAC,EAE1D,KAAA,EAAM;AACX;AAEA,eAAe,UAAA,CAAW,KAAmB,KAAA,EAA0D;AACrG,EAAA,MAAM,cAAA,GAAiB,MAAM,GAAA,CAAI,IAAA,CAAK,WAAW,KAAK,CAAA;AACtD,EAAA,IAAI,CAAC,cAAA,CAAe,OAAA;AAClB,IAAA,OAAO,cAAA;AAET,EAAA,MAAM,MAAA,GAAS,cAAA,CAAe,IAAA,CAAK,IAAA,CAAK,EAAA;AAGxC,EAAA,eAAe,SAAS,MAAA,EAA+B;AACrD,IAAA,MAAM,YAAA,GAAe,MAAM,GAAA,CAAI,IAAA,CAAK,WAAW,MAAM,CAAA;AACrD,IAAA,IAAI,CAAC,YAAA,CAAa,OAAA;AAChB,MAAA,gBAAA,CAAiB,KAAA,CAAM,qDAAqD,EAAE,MAAA,EAAQ,QAAQ,KAAA,EAAO,YAAA,CAAa,OAAO,CAAA;AAAA,EAC7H;AAEA,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAC/B,IAAA,MAAM,cAAc,MAAM,GAAA,CAAI,MAAM,SAAA,CAAU,MAAA,EAAQ,MAAM,OAAO,CAAA;AACnE,IAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AACxB,MAAA,MAAM,SAAS,kBAAkB,CAAA;AACjC,MAAA,OAAO,WAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,IAAI,KAAA,CAAM,YAAY,KAAA,EAAO;AAC3B,IAAA,MAAM,YAAA,GAAe,MAAM,GAAA,CAAI,IAAA,CAAK,WAAW,MAAA,EAAQ,EAAE,OAAA,EAAS,KAAA,EAAO,CAAA;AACzE,IAAA,IAAI,CAAC,aAAa,OAAA,EAAS;AACzB,MAAA,MAAM,SAAS,wBAAwB,CAAA;AACvC,MAAA,OAAO,YAAA;AAAA,IACT;AACA,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,OAAO,EAAA,CAAG,cAAA,CAAe,IAAA,CAAK,IAAI,CAAA;AACpC;AAEA,eAAe,UAAA,CAAW,KAAmB,KAAA,EAA0D;AACrG,EAAA,MAAM,EAAE,EAAA,EAAI,OAAA,EAAS,GAAG,MAAK,GAAI,KAAA;AACjC,EAAA,MAAM,eAAe,MAAM,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,IAAI,IAAI,CAAA;AACvD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAA;AAChB,IAAA,OAAO,YAAA;AAET,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,cAAc,MAAM,GAAA,CAAI,KAAA,CAAM,SAAA,CAAU,IAAI,OAAO,CAAA;AACzD,IAAA,IAAI,CAAC,WAAA,CAAY,OAAA;AACf,MAAA,OAAO,WAAA;AAAA,EACX;AAEA,EAAA,OAAO,YAAA;AACT","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminUpdateUserInput,\n IamCurrentUser,\n} from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { IamFunctions, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { implement } from '../serv-router.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/** IAM 默认 procedures 依赖。 */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/** 创建 IAM 默认 procedures。 */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const { iam } = deps\n\n return implement(iamContract)\n .context<ServContext>()\n\n .route('auth.login', ({ input }) => iam.auth.login(input))\n .route('auth.loginWithOtp', ({ input }) => iam.auth.loginWithOtp(input))\n .route('auth.loginWithLdap', ({ input }) => iam.auth.loginWithLdap(input))\n .route('auth.publicConfig', () => ok(iam.publicConfig))\n .route('auth.requestPasswordReset', ({ input }) => iam.user.requestPasswordReset(input.identifier))\n .route('auth.confirmPasswordReset', ({ input }) => iam.user.confirmPasswordReset(input.token, input.newPassword))\n\n .route('auth.logout')\n .auth()\n .handle(({ input, context }) => {\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.auth.logout(token)\n })\n\n .route('auth.currentUser')\n .auth()\n .handle(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n\n return mapHaiResult<User, IamCurrentUser>(\n await iam.user.getCurrentUser(token),\n user => ({\n ...user,\n roles: context.session.roles,\n permissions: context.session.permissions,\n }),\n )\n })\n\n .route('auth.refresh', async ({ input }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n })\n .route('auth.sendOtp', ({ input }) => iam.auth.sendOtp(input.identifier))\n .route('auth.register', ({ input }) => iam.auth.registerAndLogin(input))\n\n .route('auth.changePassword')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })\n\n .route('auth.updateCurrentUser')\n .auth()\n .handle(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale }))\n return iam.user.updateCurrentUser(token, input)\n })\n\n .route('users.list')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.listUsers(input))\n\n .route('users.get')\n .permission('iam.users.read')\n .handle(({ input }) => iam.user.getUser(input.id, { include: ['roles'] }))\n\n .route('users.create')\n .permission('iam.users.write')\n .handle(({ input }) => createUser(iam, input))\n\n .route('users.update')\n .permission('iam.users.write')\n .handle(({ input }) => updateUser(iam, input))\n\n .route('users.delete')\n .permission('iam.users.write')\n .handle(({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session.userId === input.id)\n return err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale }))\n return iam.user.deleteUser(input.id)\n })\n\n .route('users.resetPassword')\n .permission('iam.users.write')\n .handle(({ input }) => iam.user.adminResetPassword(input.id, input.newPassword))\n\n .route('roles.list')\n .permission('iam.roles.read')\n .handle(() => iam.authz.getAllRoles())\n\n .route('roles.get')\n .permission('iam.roles.read')\n .handle(({ input }) => iam.authz.getRole(input.id))\n\n .route('roles.create')\n .permission('iam.roles.write')\n .handle(({ input }) => iam.authz.createRole(input))\n\n .route('roles.update')\n .permission('iam.roles.write')\n .handle(({ input }) => {\n const { id, ...data } = input\n return iam.authz.updateRole(id, data)\n })\n\n .route('roles.delete')\n .permission('iam.roles.write')\n .handle(({ input }) => iam.authz.deleteRole(input.id))\n\n .route('permissions.list')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getAllPermissions(input))\n\n .route('permissions.get')\n .permission('iam.permissions.read')\n .handle(({ input }) => iam.authz.getPermission(input.id))\n\n .route('permissions.create')\n .permission('iam.permissions.write')\n .handle(({ input }) => iam.authz.createPermission(input))\n\n .route('permissions.delete')\n .permission('iam.permissions.write')\n .handle(({ input }) => iam.authz.deletePermission(input.id))\n\n .build()\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput): Promise<HaiResult<User>> {\n const registerResult = await iam.user.createUser(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds !== undefined) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n if (input.enabled === false) {\n const updateResult = await iam.user.updateUser(userId, { enabled: false })\n if (!updateResult.success) {\n await rollback('initial disable failed')\n return updateResult\n }\n return updateResult\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
|
package/dist/index.js
CHANGED
|
@@ -766,6 +766,7 @@ function buildAuthContextFactory(verifyToken) {
|
|
|
766
766
|
var IAM_AUTH_PATHS = {
|
|
767
767
|
login: apiContract.pathOf(apiContract.iam.auth.login),
|
|
768
768
|
loginWithOtp: apiContract.pathOf(apiContract.iam.auth.loginWithOtp),
|
|
769
|
+
loginWithLdap: apiContract.pathOf(apiContract.iam.auth.loginWithLdap),
|
|
769
770
|
register: apiContract.pathOf(apiContract.iam.auth.register),
|
|
770
771
|
logout: apiContract.pathOf(apiContract.iam.auth.logout),
|
|
771
772
|
refresh: apiContract.pathOf(apiContract.iam.auth.refresh)
|
|
@@ -845,6 +846,7 @@ function mountRefreshCookieRoutes(app, apiPrefix, config, iam) {
|
|
|
845
846
|
const loginPaths = [
|
|
846
847
|
`${apiPrefix}${IAM_AUTH_PATHS.login}`,
|
|
847
848
|
`${apiPrefix}${IAM_AUTH_PATHS.loginWithOtp}`,
|
|
849
|
+
`${apiPrefix}${IAM_AUTH_PATHS.loginWithLdap}`,
|
|
848
850
|
`${apiPrefix}${IAM_AUTH_PATHS.register}`
|
|
849
851
|
];
|
|
850
852
|
for (const path of loginPaths) {
|