@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 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。
@@ -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;
@@ -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, ok } from '@h-ai/core';
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.register(input);
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?.length) {
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");
@@ -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":"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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 .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) {