@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +86 -241
- package/README.ja.md +86 -241
- package/README.md +86 -241
- package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
- package/dist/chunk-FI56GONY.js.map +1 -0
- package/dist/chunk-MBPB37FR.js +1348 -0
- package/dist/chunk-MBPB37FR.js.map +1 -0
- package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +1304 -532
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/src-DSJJXM2F.js +3849 -0
- package/dist/src-DSJJXM2F.js.map +1 -0
- package/package.json +9 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/messages/ja-JP.json +38 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
- package/templates/apps/api/.dockerignore.hbs +14 -7
- package/templates/apps/api/.gitignore.hbs +7 -0
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
- package/templates/apps/api/hai.build.json.hbs +1 -1
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +6 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/.dockerignore.hbs +14 -9
- package/templates/apps/fullstack/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
- package/templates/apps/fullstack/hai.build.json.hbs +2 -2
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
- package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
- package/templates/apps/h5/messages/ja-JP.json +40 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +14 -8
- package/templates/base/.gitignore.hbs +7 -0
- package/templates/base/e2e/app.spec.ts.hbs +23 -8
- package/templates/base/hai.build.json.hbs +1 -1
- package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
- package/templates/base/package.json.hbs +11 -3
- package/templates/base/playwright.config.ts +15 -4
- package/templates/base/src/hooks.server.ts.hbs +15 -6
- package/templates/base/src/lib/server/init.ts.hbs +139 -30
- package/templates/base/vite.config.ts.hbs +5 -3
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
- package/templates/i18n/project.inlang/settings.json +1 -1
- package/templates/shared/.gitattributes +6 -0
- package/templates/shared/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +7 -3
- package/templates/skills/bridges/admin/AGENTS.md +6 -2
- package/templates/skills/bridges/api/AGENTS.md +3 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
- package/templates/skills/bridges/h5/AGENTS.md +6 -2
- package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
- package/templates/skills/bridges/website/AGENTS.md +6 -2
- package/templates/skills/hai-ai/SKILL.md +8 -2
- package/templates/skills/hai-ai/guide.md +134 -60
- package/templates/skills/hai-ai/reference.md +51 -11
- package/templates/skills/hai-api-client/SKILL.md +15 -160
- package/templates/skills/hai-api-client/reference.md +178 -0
- package/templates/skills/hai-api-contract/SKILL.md +16 -273
- package/templates/skills/hai-api-contract/reference.md +280 -0
- package/templates/skills/hai-app-create/SKILL.md +6 -2
- package/templates/skills/hai-app-create/development.md +24 -8
- package/templates/skills/hai-app-create/reference.md +7 -5
- package/templates/skills/hai-app-review/SKILL.md +7 -3
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +6 -3
- package/templates/skills/hai-app-tests/reference.md +5 -5
- package/templates/skills/hai-app-tests/validation.md +38 -0
- package/templates/skills/hai-audit/SKILL.md +24 -147
- package/templates/skills/hai-audit/reference.md +148 -0
- package/templates/skills/hai-build/SKILL.md +14 -8
- package/templates/skills/hai-build/reference.md +16 -13
- package/templates/skills/hai-cache/SKILL.md +20 -181
- package/templates/skills/hai-cache/reference.md +229 -0
- package/templates/skills/hai-capacitor/SKILL.md +7 -4
- package/templates/skills/hai-capacitor/reference.md +15 -9
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +7 -3
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +22 -250
- package/templates/skills/hai-crypto/reference.md +310 -0
- package/templates/skills/hai-datapipe/SKILL.md +16 -195
- package/templates/skills/hai-datapipe/reference.md +204 -0
- package/templates/skills/hai-deploy/SKILL.md +24 -54
- package/templates/skills/hai-deploy/middleware.md +51 -43
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +25 -11
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +35 -174
- package/templates/skills/hai-kit/reference.md +190 -0
- package/templates/skills/hai-payment/SKILL.md +33 -263
- package/templates/skills/hai-payment/reference.md +295 -0
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +6 -3
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +6 -3
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +18 -195
- package/templates/skills/hai-scheduler/reference.md +216 -0
- package/templates/skills/hai-serv/SKILL.md +25 -10
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +5 -1
- package/templates/skills/hai-spec-implement/evaluation.md +18 -0
- package/templates/skills/hai-spec-implement/handoff.md +47 -0
- package/templates/skills/hai-spec-implement/templates.md +5 -0
- package/templates/skills/hai-spec-implement/workflow.md +5 -3
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +23 -238
- package/templates/skills/hai-storage/reference.md +282 -0
- package/templates/skills/hai-ui/SKILL.md +17 -3
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +26 -11
- package/templates/skills/hai-ui-design/SKILL.md +31 -0
- package/templates/skills/hai-ui-design/patterns.md +26 -0
- package/templates/skills/hai-ui-review/SKILL.md +30 -0
- package/templates/skills/hai-ui-review/checklist.md +22 -0
- package/templates/skills/hai-vecdb/SKILL.md +14 -10
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-FWN5B54L.js +0 -468
- package/dist/chunk-FWN5B54L.js.map +0 -1
- package/dist/chunk-QYX6V7H2.js.map +0 -1
- package/dist/cli-config-templates-3XSFIXBP.js +0 -5
- package/dist/src-RDQ2EIBT.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -35
- package/templates/apps/fullstack/Dockerfile.hbs +0 -57
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
- /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
管理界面自动创建角色时使用 `role_${core.id.generate()}` 生成 code,显示名称独立保存;不要通过移除中文、表情或折叠大小写生成标识。重命名不重建 code。
|
|
12
12
|
|
|
13
|
-
> `@h-ai/iam` 是统一的身份与访问管理模块,支持多种认证策略(密码/OTP/LDAP/API Key)、Bearer Token 管理和 RBAC 授权。依赖 `@h-ai/reldb`(持久化)、`@h-ai/cache`(会话 / OTP /
|
|
13
|
+
> `@h-ai/iam` 是统一的身份与访问管理模块,支持多种认证策略(密码/OTP/LDAP/API Key)、Bearer Token 管理和 RBAC 授权。依赖 `@h-ai/reldb`(持久化)、`@h-ai/cache`(会话 / OTP / 一次性票据)、`@h-ai/crypto`(密码哈希)、`@h-ai/audit`(审计日志)。
|
|
14
14
|
|
|
15
15
|
## 运行环境
|
|
16
16
|
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
## 依赖
|
|
20
20
|
|
|
21
|
-
| 模块
|
|
22
|
-
|
|
|
23
|
-
| `@h-ai/reldb`
|
|
24
|
-
| `@h-ai/cache`
|
|
25
|
-
| `@h-ai/crypto` | 密码哈希
|
|
26
|
-
| `@h-ai/audit`
|
|
21
|
+
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
22
|
+
| -------------- | ------------------------------ | -------- | -------------------------- |
|
|
23
|
+
| `@h-ai/reldb` | 数据库(用户/角色/权限持久化) | **必需** | 需在 `iam.init()` 前初始化 |
|
|
24
|
+
| `@h-ai/cache` | 缓存(会话/OTP/一次性票据) | **必需** | 需在 `iam.init()` 前初始化 |
|
|
25
|
+
| `@h-ai/crypto` | 密码哈希 | 内部使用 | 自动初始化 |
|
|
26
|
+
| `@h-ai/audit` | 审计日志(RBAC 关键操作记录) | 内部使用 | 已初始化时自动写入 |
|
|
27
27
|
|
|
28
28
|
## 适用场景
|
|
29
29
|
|
|
@@ -79,11 +79,14 @@ import { reldb } from '@h-ai/reldb'
|
|
|
79
79
|
// 位于应用启动函数内;依赖初始化失败时立即返回,不能继续启动 IAM。
|
|
80
80
|
core.init({ configDir: './config' })
|
|
81
81
|
const db = await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
82
|
-
if (!db.success)
|
|
82
|
+
if (!db.success)
|
|
83
|
+
return db
|
|
83
84
|
const cached = await cache.init({ type: 'memory' })
|
|
84
|
-
if (!cached.success)
|
|
85
|
+
if (!cached.success)
|
|
86
|
+
return cached
|
|
85
87
|
const initialized = await iam.init(core.config.getOrThrow<IamConfigSettingsInput>('iam'))
|
|
86
|
-
if (!initialized.success)
|
|
88
|
+
if (!initialized.success)
|
|
89
|
+
return initialized
|
|
87
90
|
// 关闭时按 iam → cache → reldb 的反向依赖顺序执行。
|
|
88
91
|
```
|
|
89
92
|
|
|
@@ -91,7 +94,7 @@ if (!initialized.success) return initialized
|
|
|
91
94
|
|
|
92
95
|
基础初始化创建用户/RBAC 五张表;启用 API Key 登录时另创建 `hai_iam_api_keys`。
|
|
93
96
|
|
|
94
|
-
Token 和 OTP 存储在 cache 中(不落库)。
|
|
97
|
+
Token 和 OTP 存储在 cache 中(不落库)。session/refresh/index 位于同一用户分区版本文档,通过单键 CAS 提交。
|
|
95
98
|
|
|
96
99
|
### 可选 LDAP 与通知
|
|
97
100
|
|
|
@@ -99,25 +102,27 @@ LDAP 默认关闭,Reach 不属于 IAM 的启动前置依赖。`ldapts` 和 `@h
|
|
|
99
102
|
|
|
100
103
|
- 默认 LDAP 客户端:在服务端 workspace 执行 `pnpm add ldapts`,配置 `login.ldap: true` 与 `ldap.url/bindDn/bindPassword/searchBase`。`searchFilter` 默认 `(uid={{username}})`;AD 可使用 `(sAMAccountName={{username}})` 与对应 `usernameAttribute`。`ldaps://` 使用 TLS,`ldap://` + `useTls: true` 使用 StartTLS。`ldapClientFactory` 可注入返回 `bind/search/unbind` 和 HaiResult 的客户端,此时无需安装 ldapts。
|
|
101
104
|
- Reach 自动发送:应用安装、初始化 `@h-ai/reach`,在 `iam.init({ ...settings, reach })` 中注入一次。`otp_email/otp_sms` 模板使用 `{code}`;`password_reset/password_reset_sms` 使用 `{token}/{expiresAt}/{resetUrl}`。模板需在 IAM 初始化前配置,Provider 绑定实际邮件/短信通道。`passwordReset.mode` 选择 `email/phone`。
|
|
102
|
-
- 自定义发送:直接传 `onOtpSendEmail(email, code)`、`onOtpSendSms(phone, code)`、`onPasswordResetRequest(user, token,
|
|
105
|
+
- 自定义发送:直接传 `onOtpSendEmail(email, code, context)`、`onOtpSendSms(phone, code, context)`、`onPasswordResetRequest(user, token, context)`,均返回 `Promise<void>`;context 包含 expiresAt/idempotencyKey,发送服务按该稳定键持久化去重,异常表示结果未知,不自动重发。可以只实现选用通道或与 Reach 混用,显式回调优先。检查发送服务结果,投递失败在回调内抛错;IAM 捕获并返回业务错误,不要吞掉发送失败。
|
|
103
106
|
- `otp` 必须有配置并开启所需通道;缺少回调或标准模板时 `iam.publicConfig` 不公布该入口。Reach 由应用管理生命周期,IAM 不负责初始化或关闭。Console 仅记录元数据。
|
|
104
107
|
|
|
105
108
|
协议以 Modal 显示,关闭后保留输入。URL 方式必须为 HTTP(S) 且页面允许 iframe 嵌入;脚本被隔离禁用。`LoginForm/RegisterForm` 提供 `header/extraFields/footer`;`AuthPanel` 提供 `loginHeader/loginFields/loginFooter`、`registerHeader/registerFields/registerFooter`。额外字段由应用绑定状态,在自己的动作中处理,IAM 不接收未经定义的字段。
|
|
106
109
|
|
|
110
|
+
密码登录只接收 identifier/password;AuthPanel 将 LoginForm 的 username 映射为 identifier。会话期限来自服务端 session.maxAge/refreshTokenMaxAge,不增加未实现的登录偏好字段或持久会话选择框。
|
|
111
|
+
|
|
107
112
|
## 核心 API
|
|
108
113
|
|
|
109
114
|
### 认证 — `iam.auth`
|
|
110
115
|
|
|
111
|
-
| 方法 | 签名
|
|
112
|
-
| ------------------ |
|
|
113
|
-
| `login` | `({ identifier, password }) => Promise<HaiResult<AuthResult>>` | 密码登录(用户名/邮箱/手机号)
|
|
114
|
-
| `loginWithOtp` | `({ identifier, code }) => Promise<HaiResult<AuthResult>>` | OTP 验证码登录
|
|
115
|
-
| `loginWithLdap` | `({ username, password }) => Promise<HaiResult<AuthResult>>` | LDAP 登录
|
|
116
|
-
| `loginWithApiKey` | `({ key }) => Promise<HaiResult<AuthResult>>` | API Key 登录
|
|
117
|
-
| `sendOtp` | `(identifier) => Promise<HaiResult<{ expiresAt }>>` | 发送 OTP 验证码
|
|
116
|
+
| 方法 | 签名 | 说明 |
|
|
117
|
+
| ------------------ | -------------------------------------------------------------- | ------------------------------------------------ |
|
|
118
|
+
| `login` | `({ identifier, password }) => Promise<HaiResult<AuthResult>>` | 密码登录(用户名/邮箱/手机号) |
|
|
119
|
+
| `loginWithOtp` | `({ identifier, code }) => Promise<HaiResult<AuthResult>>` | OTP 验证码登录 |
|
|
120
|
+
| `loginWithLdap` | `({ username, password }) => Promise<HaiResult<AuthResult>>` | LDAP 登录 |
|
|
121
|
+
| `loginWithApiKey` | `({ key }) => Promise<HaiResult<AuthResult>>` | API Key 登录 |
|
|
122
|
+
| `sendOtp` | `(identifier) => Promise<HaiResult<{ expiresAt }>>` | 发送 OTP 验证码 |
|
|
118
123
|
| `verifyToken` | `(token) => Promise<HaiResult<Session>>` | 验证会话并读取当前用户状态及授权;数据库失败拒绝 |
|
|
119
|
-
| `logout` | `(token) => Promise<HaiResult<void>>` | 登出
|
|
120
|
-
| `registerAndLogin` | `(options: RegisterOptions) => Promise<HaiResult<AuthResult>>`
|
|
124
|
+
| `logout` | `(token) => Promise<HaiResult<void>>` | 登出 |
|
|
125
|
+
| `registerAndLogin` | `(options: RegisterOptions) => Promise<HaiResult<AuthResult>>` | 注册并登录(一站式) |
|
|
121
126
|
|
|
122
127
|
**AuthResult**(已改造为 TokenPair):
|
|
123
128
|
|
|
@@ -140,16 +145,16 @@ interface TokenPair {
|
|
|
140
145
|
|
|
141
146
|
### 会话管理 — `iam.session`
|
|
142
147
|
|
|
143
|
-
| 方法 | 签名
|
|
144
|
-
| ------------------- |
|
|
145
|
-
| `create` | `({ userId, username, roles, source? }) => Promise<HaiResult<Session>>` | 创建会话(返回含 TokenPair 的 session)
|
|
146
|
-
| `get` | `(token) => Promise<HaiResult<Session \| null>>` | 获取会话(滑动续期自动延长)
|
|
147
|
-
| `verifyToken` | `(token) => Promise<HaiResult<Session>>` | 验证令牌
|
|
148
|
-
| `update` | `(token, data) => Promise<HaiResult<void>>`
|
|
149
|
-
| `delete` | `(token) => Promise<HaiResult<void>>` | 删除会话
|
|
150
|
-
| `deleteByUserId` | `(userId) => Promise<HaiResult<number>>` | 删除用户所有会话(返回删除数量)
|
|
151
|
-
| `refresh` | `(refreshToken) => Promise<HaiResult<TokenPair>>` | 刷新 Token(rotation:旧 Token 失效)
|
|
152
|
-
| `revokeRefresh` | `(refreshToken) => Promise<HaiResult<void>>` | 吊销 refreshToken
|
|
148
|
+
| 方法 | 签名 | 说明 |
|
|
149
|
+
| ------------------- | ----------------------------------------------------------------------- | ---------------------------------------- |
|
|
150
|
+
| `create` | `({ userId, username, roles, source? }) => Promise<HaiResult<Session>>` | 创建会话(返回含 TokenPair 的 session) |
|
|
151
|
+
| `get` | `(token) => Promise<HaiResult<Session \| null>>` | 获取会话(滑动续期自动延长) |
|
|
152
|
+
| `verifyToken` | `(token) => Promise<HaiResult<Session>>` | 验证令牌 |
|
|
153
|
+
| `update` | `(token, data, expectedVersion) => Promise<HaiResult<void>>` | 更新会话字段(浅合并 data) |
|
|
154
|
+
| `delete` | `(token) => Promise<HaiResult<void>>` | 删除会话 |
|
|
155
|
+
| `deleteByUserId` | `(userId) => Promise<HaiResult<number>>` | 删除用户所有会话(返回删除数量) |
|
|
156
|
+
| `refresh` | `(refreshToken) => Promise<HaiResult<TokenPair>>` | 刷新 Token(rotation:旧 Token 失效) |
|
|
157
|
+
| `revokeRefresh` | `(refreshToken) => Promise<HaiResult<void>>` | 吊销 refreshToken |
|
|
153
158
|
| `patchUserSessions` | `(userId, updates: SessionFieldUpdates) => Promise<HaiResult<void>>` | 批量更新用户所有会话的 roles/permissions |
|
|
154
159
|
|
|
155
160
|
**Token 刷新(rotation 策略)**:
|
|
@@ -162,10 +167,10 @@ interface TokenPair {
|
|
|
162
167
|
|
|
163
168
|
短期、一次性能力票据,用于无法携带 Bearer Token 的通道(如 WebSocket URL)。密码学安全随机、TTL、原子单次消费、用途绑定。
|
|
164
169
|
|
|
165
|
-
| 方法
|
|
166
|
-
|
|
|
167
|
-
| `issue`
|
|
168
|
-
| `consume`
|
|
170
|
+
| 方法 | 签名 | 说明 |
|
|
171
|
+
| --------- | --------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
172
|
+
| `issue` | `({ subjectId, purpose, grant?, ttlMs? }) => Promise<HaiResult<{ ticket, expiresAt }>>` | 签发(默认 30s) |
|
|
173
|
+
| `consume` | `(ticket, { purpose? }?) => Promise<HaiResult<{ subjectId, purpose, grant }>>` | 原子消费(单次有效,用途不符拒绝) |
|
|
169
174
|
|
|
170
175
|
```ts
|
|
171
176
|
const issued = await iam.ticket.issue({ subjectId, purpose: 'ai-audio', grant: { operation: 'transcribe' } })
|
|
@@ -176,50 +181,50 @@ const consumed = await iam.ticket.consume(ticket, { purpose: 'ai-audio' }) //
|
|
|
176
181
|
|
|
177
182
|
### 用户管理 — `iam.user`
|
|
178
183
|
|
|
179
|
-
| 方法
|
|
180
|
-
|
|
|
181
|
-
| `register`
|
|
182
|
-
| `getUser`
|
|
183
|
-
| `getCurrentUser`
|
|
184
|
-
| `updateCurrentUser`
|
|
185
|
-
| `getUsers`
|
|
186
|
-
| `listUsers`
|
|
187
|
-
| `updateUser`
|
|
188
|
-
| `deleteUser`
|
|
189
|
-
| `changePassword`
|
|
190
|
-
| `changeCurrentUserPassword
|
|
191
|
-
| `requestPasswordReset`
|
|
192
|
-
| `confirmPasswordReset`
|
|
193
|
-
| `adminResetPassword`
|
|
194
|
-
| `validatePassword`
|
|
184
|
+
| 方法 | 签名 | 说明 |
|
|
185
|
+
| --------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
186
|
+
| `register` | `(input) => Promise<HaiResult<RegisterResult>>` | 用户注册 |
|
|
187
|
+
| `getUser` | `(id, options?) => Promise<HaiResult<User \| null>>` | 获取用户(可选 include: ['roles']) |
|
|
188
|
+
| `getCurrentUser` | `(token) => Promise<HaiResult<User>>` | 通过令牌获取当前用户 |
|
|
189
|
+
| `updateCurrentUser` | `(token, data: UpdateCurrentUserInput) => Promise<HaiResult<User>>` | 当前用户修改个人资料(白名单字段) |
|
|
190
|
+
| `getUsers` | `(ids, options?) => Promise<HaiResult<User[]>>` | 批量公开用户,上限 1000;去重输入顺序、缺失省略、可 include roles |
|
|
191
|
+
| `listUsers` | `(options?: ListUsersOptions) => Promise<HaiResult<PaginatedResult<User>>>` | 用户列表(分页 + 搜索 + roleCode 分页前筛选 + 白名单排序) |
|
|
192
|
+
| `updateUser` | `(id, data) => Promise<HaiResult<User>>` | 更新用户信息 |
|
|
193
|
+
| `deleteUser` | `(id) => Promise<HaiResult<void>>` | 删除用户(自动清除会话) |
|
|
194
|
+
| `changePassword` | `(id, old, new) => Promise<HaiResult<void>>` | 修改密码(自动清除会话) |
|
|
195
|
+
| `changeCurrentUserPassword` | `(token, old, new) => Promise<HaiResult<void>>` | 当前用户修改密码(通过令牌识别) |
|
|
196
|
+
| `requestPasswordReset` | `(email) => Promise<HaiResult<void>>` | 请求密码重置(防枚举) |
|
|
197
|
+
| `confirmPasswordReset` | `(token, newPassword) => Promise<HaiResult<void>>` | 确认密码重置(自动清除会话) |
|
|
198
|
+
| `adminResetPassword` | `(userId, newPassword) => Promise<HaiResult<void>>` | 管理员直接重置密码 |
|
|
199
|
+
| `validatePassword` | `(password) => HaiResult<void>` | 验证密码强度(**同步方法**) |
|
|
195
200
|
|
|
196
201
|
### RBAC 授权 — `iam.authz`
|
|
197
202
|
|
|
198
|
-
| 方法
|
|
199
|
-
|
|
|
200
|
-
| `checkPermission`
|
|
201
|
-
| `getUserAuthorization`
|
|
202
|
-
| `getUserPermissions`
|
|
203
|
-
| `getUserRoles`
|
|
204
|
-
| `getUserRolesForMany`
|
|
205
|
-
| `assignRole`
|
|
206
|
-
| `removeRole`
|
|
207
|
-
| `syncRoles`
|
|
208
|
-
| `createRole`
|
|
209
|
-
| `getRole`
|
|
210
|
-
| `getRoleByCode`
|
|
211
|
-
| `getAllRoles`
|
|
212
|
-
| `updateRole`
|
|
213
|
-
| `deleteRole`
|
|
214
|
-
| `createPermission`
|
|
215
|
-
| `getPermission`
|
|
216
|
-
| `getPermissionByCode`
|
|
217
|
-
| `getAllPermissions` | `(options?: PermissionQueryOptions) => Promise<HaiResult<PaginatedResult<Permission>>>` | 获取所有权限(分页 + 类型/关键字筛选)
|
|
218
|
-
| `deletePermission`
|
|
219
|
-
| `assignPermissionToRole`
|
|
220
|
-
| `removePermissionFromRole`
|
|
221
|
-
| `getRolePermissions`
|
|
222
|
-
| `getRolePermissionsForMany
|
|
203
|
+
| 方法 | 签名 | 说明 |
|
|
204
|
+
| --------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
205
|
+
| `checkPermission` | `(userId, permission) => Promise<HaiResult<boolean>>` | 检查权限(超管自动通过,支持通配符) |
|
|
206
|
+
| `getUserAuthorization` | `(userId) => Promise<HaiResult<{roles: string[], permissions: string[]}>>` | 当前角色及权限代码,角色 JOIN + 批量权限查询;失败不回退缓存 |
|
|
207
|
+
| `getUserPermissions` | `(userId) => Promise<HaiResult<Permission[]>>` | 获取用户权限列表(通过角色聚合去重) |
|
|
208
|
+
| `getUserRoles` | `(userId) => Promise<HaiResult<Role[]>>` | 获取用户角色列表 |
|
|
209
|
+
| `getUserRolesForMany` | `(userIds) => Promise<HaiResult<Map<string, Role[]>>>` | 批量获取多用户角色(避免 N+1) |
|
|
210
|
+
| `assignRole` | `(userId, roleId, tx?) => Promise<HaiResult<void>>` | 分配角色给用户 |
|
|
211
|
+
| `removeRole` | `(userId, roleId, tx?) => Promise<HaiResult<void>>` | 移除用户角色 |
|
|
212
|
+
| `syncRoles` | `(userId, roleIds, tx?) => Promise<HaiResult<void>>` | 同步用户角色(替换为目标列表) |
|
|
213
|
+
| `createRole` | `({ code, name, description? }, tx?) => Promise<HaiResult<Role>>` | 创建角色 |
|
|
214
|
+
| `getRole` | `(roleId) => Promise<HaiResult<Role \| null>>` | 获取角色 |
|
|
215
|
+
| `getRoleByCode` | `(code) => Promise<HaiResult<Role \| null>>` | 根据代码获取角色 |
|
|
216
|
+
| `getAllRoles` | `(options?) => Promise<HaiResult<PaginatedResult<Role>>>` | 获取所有角色(分页) |
|
|
217
|
+
| `updateRole` | `(roleId, data, tx?) => Promise<HaiResult<Role>>` | 更新角色 |
|
|
218
|
+
| `deleteRole` | `(roleId, tx?) => Promise<HaiResult<void>>` | 删除角色(级联清理关联 + 会话同步) |
|
|
219
|
+
| `createPermission` | `({ code, name, type?, resource?, action? }, tx?) => Promise<HaiResult<Permission>>` | 创建权限 |
|
|
220
|
+
| `getPermission` | `(permissionId) => Promise<HaiResult<Permission \| null>>` | 获取权限 |
|
|
221
|
+
| `getPermissionByCode` | `(code) => Promise<HaiResult<Permission \| null>>` | 根据代码获取权限 |
|
|
222
|
+
| `getAllPermissions` | `(options?: PermissionQueryOptions) => Promise<HaiResult<PaginatedResult<Permission>>>` | 获取所有权限(分页 + 类型/关键字筛选) |
|
|
223
|
+
| `deletePermission` | `(permissionId, tx?) => Promise<HaiResult<void>>` | 删除权限(级联清理关联 + 会话同步) |
|
|
224
|
+
| `assignPermissionToRole` | `(roleId, permId, tx?) => Promise<HaiResult<void>>` | 分配权限给角色 |
|
|
225
|
+
| `removePermissionFromRole` | `(roleId, permId, tx?) => Promise<HaiResult<void>>` | 移除角色权限 |
|
|
226
|
+
| `getRolePermissions` | `(roleId) => Promise<HaiResult<Permission[]>>` | 获取角色的权限列表 |
|
|
227
|
+
| `getRolePermissionsForMany` | `(roleIds) => Promise<HaiResult<Map<string, Permission[]>>>` | 批量获取多角色权限(避免 N+1) |
|
|
223
228
|
|
|
224
229
|
**通配符规则**:`admin:*` 匹配 `admin:read`、`admin:write` 等。超管角色自动拥有所有权限。
|
|
225
230
|
|
|
@@ -238,14 +243,14 @@ apikey:
|
|
|
238
243
|
|
|
239
244
|
假设主体拥有 `record:read`、`record:write`、`user:read`,不同 scopes 的结果如下(主体授权改变后重新计算):
|
|
240
245
|
|
|
241
|
-
| scopes
|
|
242
|
-
|
|
|
243
|
-
| 未传或 `[]`
|
|
244
|
-
| `['record:read']`
|
|
245
|
-
| `['record:*']`
|
|
246
|
-
| `['record:read', 'user:read']` | 两个指定权限
|
|
247
|
-
| `['missing:read']`
|
|
248
|
-
| `['*']`
|
|
246
|
+
| scopes | 有效权限 | roles |
|
|
247
|
+
| ------------------------------ | ----------------------------- | ------------ |
|
|
248
|
+
| 未传或 `[]` | 无操作权限,仅可识别身份 | `[]` |
|
|
249
|
+
| `['record:read']` | `record:read` | `[]` |
|
|
250
|
+
| `['record:*']` | `record:read`、`record:write` | `[]` |
|
|
251
|
+
| `['record:read', 'user:read']` | 两个指定权限 | `[]` |
|
|
252
|
+
| `['missing:read']` | 主体未拥有,因此无权限 | `[]` |
|
|
253
|
+
| `['*']` | 主体当前拥有的全部权限 | 保留主体角色 |
|
|
249
254
|
|
|
250
255
|
`record:*` 匹配冒号后的全部层级,不支持任意正则或 `record:r*` 等中间通配符。主体仅拥有 `record:read` 时,`['record:*']` 仍只得到读取权限;主体持有通配符时,交集保留较窄范围。受限 Key 没有角色入口,使用 permission 守卫;显式 `*` 是宽授权,按实际需要选择。
|
|
251
256
|
|
|
@@ -258,7 +263,9 @@ import { iam } from '@h-ai/iam'
|
|
|
258
263
|
async function createReadKey(userId: string) {
|
|
259
264
|
// 调用前校验管理权限或本人归属;scopes 使用权限 code。
|
|
260
265
|
const created = await iam.apiKey.createApiKey(userId, {
|
|
261
|
-
name: 'Read client',
|
|
266
|
+
name: 'Read client',
|
|
267
|
+
expirationDays: 30,
|
|
268
|
+
scopes: ['record:read'],
|
|
262
269
|
})
|
|
263
270
|
if (!created.success)
|
|
264
271
|
return created
|
|
@@ -288,7 +295,10 @@ async function requireRecordRead(accessToken: string, deniedMessage: string) {
|
|
|
288
295
|
|
|
289
296
|
async function listMyKeys(userId: string) {
|
|
290
297
|
return iam.apiKey.listApiKeys(userId, {
|
|
291
|
-
page: 1,
|
|
298
|
+
page: 1,
|
|
299
|
+
pageSize: 20,
|
|
300
|
+
search: 'Read',
|
|
301
|
+
enabled: true,
|
|
292
302
|
})
|
|
293
303
|
}
|
|
294
304
|
|
|
@@ -304,13 +314,13 @@ Key 登录会话绑定 Key ID。刷新使用普通 `iam.session.refresh`,不
|
|
|
304
314
|
|
|
305
315
|
`expirationDays: 0` 表示不设过期时间,省略时使用 `apikey.defaultExpirationDays`;需要限时访问时显式设置。明文 Key 和 Token 不进入 URL、日志、公开配置或普通 localStorage。
|
|
306
316
|
|
|
307
|
-
| 方法 | 签名
|
|
308
|
-
| -------------- |
|
|
309
|
-
| `createApiKey` | `(userId, options: CreateApiKeyOptions) => Promise<HaiResult<CreateApiKeyResult>>` | 创建 API Key(明文密钥仅返回一次)
|
|
310
|
-
| `listApiKeys` | `(userId, options?) => Promise<HaiResult<PaginatedResult<ApiKey>>>`
|
|
311
|
-
| `getApiKey` | `(keyId) => Promise<HaiResult<ApiKey \| null>>`
|
|
312
|
-
| `revokeApiKey` | `(keyId) => Promise<HaiResult<void>>`
|
|
313
|
-
| `verifyApiKey` | `(rawKey) => Promise<HaiResult<ApiKey>>`
|
|
317
|
+
| 方法 | 签名 | 说明 |
|
|
318
|
+
| -------------- | ---------------------------------------------------------------------------------- | ------------------------------------ |
|
|
319
|
+
| `createApiKey` | `(userId, options: CreateApiKeyOptions) => Promise<HaiResult<CreateApiKeyResult>>` | 创建 API Key(明文密钥仅返回一次) |
|
|
320
|
+
| `listApiKeys` | `(userId, options?) => Promise<HaiResult<PaginatedResult<ApiKey>>>` | 分页列出用户的 API Key |
|
|
321
|
+
| `getApiKey` | `(keyId) => Promise<HaiResult<ApiKey \| null>>` | 获取 API Key 详情 |
|
|
322
|
+
| `revokeApiKey` | `(keyId) => Promise<HaiResult<void>>` | 吊销/删除 API Key |
|
|
323
|
+
| `verifyApiKey` | `(rawKey) => Promise<HaiResult<ApiKey>>` | 验证 API Key 并返回实体(含用户 ID) |
|
|
314
324
|
|
|
315
325
|
## HTTP API 契约
|
|
316
326
|
|
|
@@ -324,52 +334,52 @@ const result = await apiClient.iam.auth.login({ identifier: username, password }
|
|
|
324
334
|
|
|
325
335
|
## 错误码 — `HaiIamError`
|
|
326
336
|
|
|
327
|
-
| 错误码
|
|
328
|
-
|
|
329
|
-
| `HaiIamError.AUTH_FAILED`
|
|
330
|
-
| `HaiIamError.INVALID_CREDENTIALS`
|
|
331
|
-
| `HaiIamError.USER_NOT_FOUND`
|
|
332
|
-
| `HaiIamError.USER_DISABLED`
|
|
333
|
-
| `HaiIamError.USER_LOCKED`
|
|
334
|
-
| `HaiIamError.USER_ALREADY_EXISTS`
|
|
335
|
-
| `HaiIamError.PASSWORD_EXPIRED`
|
|
336
|
-
| `HaiIamError.PASSWORD_POLICY_VIOLATION` | `hai:iam:008` | 密码不符合策略
|
|
337
|
-
| `HaiIamError.OTP_INVALID`
|
|
338
|
-
| `HaiIamError.NOT_INITIALIZED`
|
|
339
|
-
| `HaiIamError.OTP_RESEND_TOO_FAST`
|
|
340
|
-
| `HaiIamError.LOGIN_DISABLED`
|
|
341
|
-
| `HaiIamError.REGISTER_DISABLED`
|
|
342
|
-
| `HaiIamError.STRATEGY_NOT_SUPPORTED`
|
|
343
|
-
| `HaiIamError.APIKEY_INVALID`
|
|
344
|
-
| `HaiIamError.APIKEY_EXPIRED`
|
|
345
|
-
| `HaiIamError.APIKEY_DISABLED`
|
|
346
|
-
| `HaiIamError.APIKEY_NOT_FOUND`
|
|
347
|
-
| `HaiIamError.RESET_TOKEN_INVALID`
|
|
348
|
-
| `HaiIamError.RESET_TOKEN_EXPIRED`
|
|
349
|
-
| `HaiIamError.RESET_TOKEN_MAX_ATTEMPTS`
|
|
350
|
-
| `HaiIamError.OTP_EXPIRED`
|
|
351
|
-
| `HaiIamError.SESSION_NOT_FOUND`
|
|
352
|
-
| `HaiIamError.SESSION_EXPIRED`
|
|
353
|
-
| `HaiIamError.SESSION_INVALID`
|
|
354
|
-
| `HaiIamError.SESSION_CREATE_FAILED`
|
|
355
|
-
| `HaiIamError.TOKEN_EXPIRED`
|
|
356
|
-
| `HaiIamError.TOKEN_INVALID`
|
|
357
|
-
| `HaiIamError.TOKEN_REFRESH_FAILED`
|
|
358
|
-
| `HaiIamError.PERMISSION_DENIED`
|
|
359
|
-
| `HaiIamError.ROLE_NOT_FOUND`
|
|
360
|
-
| `HaiIamError.PERMISSION_NOT_FOUND`
|
|
361
|
-
| `HaiIamError.ROLE_ALREADY_EXISTS`
|
|
362
|
-
| `HaiIamError.PERMISSION_ALREADY_EXISTS` | `hai:iam:205` | 权限已存在
|
|
363
|
-
| `HaiIamError.LDAP_CONNECTION_FAILED`
|
|
364
|
-
| `HaiIamError.LDAP_BIND_FAILED`
|
|
365
|
-
| `HaiIamError.LDAP_SEARCH_FAILED`
|
|
366
|
-
| `HaiIamError.REPOSITORY_ERROR`
|
|
367
|
-
| `HaiIamError.NOT_FOUND`
|
|
368
|
-
| `HaiIamError.CONFLICT`
|
|
369
|
-
| `HaiIamError.FORBIDDEN`
|
|
370
|
-
| `HaiIamError.INVALID_ARGUMENT`
|
|
371
|
-
| `HaiIamError.CONFIG_ERROR`
|
|
372
|
-
| `HaiIamError.INTERNAL_ERROR`
|
|
337
|
+
| 错误码 | code | 说明 |
|
|
338
|
+
| --------------------------------------- | ------------- | ---------------- |
|
|
339
|
+
| `HaiIamError.AUTH_FAILED` | `hai:iam:001` | 认证失败 |
|
|
340
|
+
| `HaiIamError.INVALID_CREDENTIALS` | `hai:iam:002` | 凭证无效 |
|
|
341
|
+
| `HaiIamError.USER_NOT_FOUND` | `hai:iam:003` | 用户不存在 |
|
|
342
|
+
| `HaiIamError.USER_DISABLED` | `hai:iam:004` | 用户已禁用 |
|
|
343
|
+
| `HaiIamError.USER_LOCKED` | `hai:iam:005` | 用户已锁定 |
|
|
344
|
+
| `HaiIamError.USER_ALREADY_EXISTS` | `hai:iam:006` | 用户已存在 |
|
|
345
|
+
| `HaiIamError.PASSWORD_EXPIRED` | `hai:iam:007` | 密码已过期 |
|
|
346
|
+
| `HaiIamError.PASSWORD_POLICY_VIOLATION` | `hai:iam:008` | 密码不符合策略 |
|
|
347
|
+
| `HaiIamError.OTP_INVALID` | `hai:iam:009` | 验证码无效 |
|
|
348
|
+
| `HaiIamError.NOT_INITIALIZED` | `hai:iam:010` | 未初始化 |
|
|
349
|
+
| `HaiIamError.OTP_RESEND_TOO_FAST` | `hai:iam:011` | 发送过于频繁 |
|
|
350
|
+
| `HaiIamError.LOGIN_DISABLED` | `hai:iam:012` | 登录方式已禁用 |
|
|
351
|
+
| `HaiIamError.REGISTER_DISABLED` | `hai:iam:013` | 注册已禁用 |
|
|
352
|
+
| `HaiIamError.STRATEGY_NOT_SUPPORTED` | `hai:iam:014` | 认证策略不支持 |
|
|
353
|
+
| `HaiIamError.APIKEY_INVALID` | `hai:iam:015` | API Key 无效 |
|
|
354
|
+
| `HaiIamError.APIKEY_EXPIRED` | `hai:iam:016` | API Key 已过期 |
|
|
355
|
+
| `HaiIamError.APIKEY_DISABLED` | `hai:iam:017` | API Key 已禁用 |
|
|
356
|
+
| `HaiIamError.APIKEY_NOT_FOUND` | `hai:iam:018` | API Key 不存在 |
|
|
357
|
+
| `HaiIamError.RESET_TOKEN_INVALID` | `hai:iam:019` | 重置令牌无效 |
|
|
358
|
+
| `HaiIamError.RESET_TOKEN_EXPIRED` | `hai:iam:020` | 重置令牌已过期 |
|
|
359
|
+
| `HaiIamError.RESET_TOKEN_MAX_ATTEMPTS` | `hai:iam:021` | 重置验证次数超限 |
|
|
360
|
+
| `HaiIamError.OTP_EXPIRED` | `hai:iam:022` | 验证码已过期 |
|
|
361
|
+
| `HaiIamError.SESSION_NOT_FOUND` | `hai:iam:101` | 会话不存在 |
|
|
362
|
+
| `HaiIamError.SESSION_EXPIRED` | `hai:iam:102` | 会话已过期 |
|
|
363
|
+
| `HaiIamError.SESSION_INVALID` | `hai:iam:103` | 会话无效 |
|
|
364
|
+
| `HaiIamError.SESSION_CREATE_FAILED` | `hai:iam:104` | 会话创建失败 |
|
|
365
|
+
| `HaiIamError.TOKEN_EXPIRED` | `hai:iam:105` | 令牌已过期 |
|
|
366
|
+
| `HaiIamError.TOKEN_INVALID` | `hai:iam:106` | 令牌无效 |
|
|
367
|
+
| `HaiIamError.TOKEN_REFRESH_FAILED` | `hai:iam:107` | 令牌刷新失败 |
|
|
368
|
+
| `HaiIamError.PERMISSION_DENIED` | `hai:iam:201` | 权限不足 |
|
|
369
|
+
| `HaiIamError.ROLE_NOT_FOUND` | `hai:iam:202` | 角色不存在 |
|
|
370
|
+
| `HaiIamError.PERMISSION_NOT_FOUND` | `hai:iam:203` | 权限不存在 |
|
|
371
|
+
| `HaiIamError.ROLE_ALREADY_EXISTS` | `hai:iam:204` | 角色已存在 |
|
|
372
|
+
| `HaiIamError.PERMISSION_ALREADY_EXISTS` | `hai:iam:205` | 权限已存在 |
|
|
373
|
+
| `HaiIamError.LDAP_CONNECTION_FAILED` | `hai:iam:301` | LDAP 连接失败 |
|
|
374
|
+
| `HaiIamError.LDAP_BIND_FAILED` | `hai:iam:302` | LDAP 绑定失败 |
|
|
375
|
+
| `HaiIamError.LDAP_SEARCH_FAILED` | `hai:iam:303` | LDAP 搜索失败 |
|
|
376
|
+
| `HaiIamError.REPOSITORY_ERROR` | `hai:iam:401` | 存储层操作错误 |
|
|
377
|
+
| `HaiIamError.NOT_FOUND` | `hai:iam:402` | 资源不存在 |
|
|
378
|
+
| `HaiIamError.CONFLICT` | `hai:iam:403` | 资源冲突 |
|
|
379
|
+
| `HaiIamError.FORBIDDEN` | `hai:iam:501` | 禁止访问 |
|
|
380
|
+
| `HaiIamError.INVALID_ARGUMENT` | `hai:iam:502` | 参数无效 |
|
|
381
|
+
| `HaiIamError.CONFIG_ERROR` | `hai:iam:901` | 配置错误 |
|
|
382
|
+
| `HaiIamError.INTERNAL_ERROR` | `hai:iam:999` | 内部错误 |
|
|
373
383
|
|
|
374
384
|
## 常见模式
|
|
375
385
|
|
|
@@ -414,9 +424,12 @@ if (me.success) {
|
|
|
414
424
|
// Token 过期时,api-client 自动调用 refreshPath 刷新
|
|
415
425
|
// 刷新失败时,清除 Token 并跳转登录页
|
|
416
426
|
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
await apiClient.auth.
|
|
427
|
+
// 登出:本地清理与服务端撤销确认分别处理,调用方继续检查返回结果。
|
|
428
|
+
async function signOut() {
|
|
429
|
+
const result = await apiClient.iam.auth.logout({})
|
|
430
|
+
await apiClient.auth.clear()
|
|
431
|
+
return result
|
|
432
|
+
}
|
|
420
433
|
```
|
|
421
434
|
|
|
422
435
|
### 自动会话失效
|
|
@@ -451,18 +464,16 @@ agreements:
|
|
|
451
464
|
# privacyPolicyUrl: https://example.com/privacy
|
|
452
465
|
```
|
|
453
466
|
|
|
454
|
-
|
|
455
|
-
|
|
456
467
|
服务端通过 `iam.publicConfig` 提供白名单配置,或通过客户端的 `client.iam.auth.publicConfig()` 标准 HTTP contract 获取。只展示已开启且具备依赖的登录方式;LDAP 为可选集成,见上方配置与安装说明。
|
|
457
468
|
|
|
458
|
-
| 配置
|
|
459
|
-
|
|
|
460
|
-
| `login.password / otp / ldap`
|
|
461
|
-
| `otp.email / sms`
|
|
462
|
-
| `register.enabled / defaultEnabled`
|
|
463
|
-
| `passwordReset.enabled / mode / resetUrl`
|
|
464
|
-
| `agreements.showOnRegister / showOnLogin`
|
|
465
|
-
| `ui.variant / title / subtitle / brandTitle / brandText / logoUrl / illustrationUrl` | 卡片/分栏布局、标题、品牌、Logo 和配图
|
|
469
|
+
| 配置 | 用途 |
|
|
470
|
+
| ------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
|
471
|
+
| `login.password / otp / ldap` | 独立启用密码、验证码、企业账号 |
|
|
472
|
+
| `otp.email / sms` | 独立启用邮件、短信 |
|
|
473
|
+
| `register.enabled / defaultEnabled` | 自助注册及新账户状态 |
|
|
474
|
+
| `passwordReset.enabled / mode / resetUrl` | 启用重置、选择 email/phone 渠道、配置重置页面绝对地址 |
|
|
475
|
+
| `agreements.showOnRegister / showOnLogin` | 注册/登录协议弹窗显示;链接只接受 HTTP(S) |
|
|
476
|
+
| `ui.variant / title / subtitle / brandTitle / brandText / logoUrl / illustrationUrl` | 卡片/分栏布局、标题、品牌、Logo 和配图 |
|
|
466
477
|
|
|
467
478
|
选择 Reach 时,在 reldb/cache/Reach 初始化成功后使用 `iam.init({ ...settings, reach })` 自动接入 Reach 的 `otp_email`、`otp_sms`、`password_reset`、`password_reset_sms` 模板。模板绑定对应邮件/短信 Provider;显式发送回调可覆盖默认行为。Console Provider 只记录元数据,实际收信需配置 SMTP、短信或 HTTP 通知服务。发送失败返回错误,不会伪报已发送。
|
|
468
479
|
|
|
@@ -472,7 +483,7 @@ SvelteKit 使用 `kit.auth.createApi(iam)` 导出标准 handler,匿名端点
|
|
|
472
483
|
|
|
473
484
|
### 分布式安全与边界
|
|
474
485
|
|
|
475
|
-
|
|
486
|
+
所有节点共享同一数据库和 Redis;Memory 仅用于单进程测试。OTP 的验证码、尝试次数和冷却期保存在同一身份 key,通过 CAS 发布和消费;冲突拒绝旧代,消费/耗尽保留冷却期。密码重置的摘要、有效期、尝试次数和 resetVersion 保存在用户数据库行,策略失败计次,最终一条条件 SQL 同时改密、消费令牌并推进 credentialVersion;新令牌、后续尝试、停用或到期都会阻止迟到提交。发送重置请求只推进 resetVersion,不撤销现有会话;成功改密才推进 credentialVersion。认证策略保存已验证的凭据代际,建立会话/认证/刷新均校验数据库当前代际;缓存批量清理失败也不能使旧会话有效。通知只在成功发布后调用,第三参数为 { expiresAt, idempotencyKey },应用及 Reach Provider 必须用该稳定键持久化去重。异常/超时可能已送达,返回结果未知,不自动重发或回滚已发布凭据;没有接收端幂等保障时不能承诺外部副作用恰好一次。
|
|
476
487
|
|
|
477
488
|
可选字段(如 `ldap`)若尚未写入 YAML,请先声明该字段,或通过 `HAI_IAM` 的 JSON 值整体覆盖模块配置;已有字段支持 `HAI_IAM_LOGIN_OTP` 等环境变量覆盖。
|
|
478
489
|
|
|
@@ -484,6 +495,22 @@ SvelteKit 使用 `kit.auth.createApi(iam)` 导出标准 handler,匿名端点
|
|
|
484
495
|
- `hai-kit`:SvelteKit 集成(Bearer Token + 契约处理)
|
|
485
496
|
- `hai-api-client`:客户端契约调用
|
|
486
497
|
- `hai-reldb`:底层数据存储
|
|
487
|
-
- `hai-cache`:会话、OTP
|
|
498
|
+
- `hai-cache`:会话、OTP 状态和一次性票据
|
|
488
499
|
- `hai-crypto`:密码哈希(内部依赖)
|
|
489
500
|
- `hai-ui`:IAM 场景组件(LoginForm/RegisterForm 等)
|
|
501
|
+
|
|
502
|
+
会话与 refresh 消费记录统一保存在 `hai:iam:sessions:{SHA256(userId)}` 版本化文档,不再使用独立 token 映射/索引。Bearer token 携带不可作为凭据的分区提示和 256 位随机值;客户端必须视为不透明字符串。`Session.version` 用于轮换提交校验。Memory 仅用于单进程测试,生产所有节点共享 Redis(含 Cluster)。并发冲突从最新文档重试,耗尽返回 CONFLICT;旧 refresh 不会产生第二个后继。若响应丢失后结果不明,调用方按失败关闭并重新登录,不假定旧凭据仍有效;全部会话仍受同一用户状态索引管理。
|
|
503
|
+
|
|
504
|
+
`iam.session.update(token, patch, expectedVersion)` 必须传入读取到的 Session.version。旧版本返回 CONFLICT,已撤销 token 返回 SESSION_NOT_FOUND;不会将旧内容重新写回或覆盖新会话。仅滑动时间戳更新不改变内容版本,允许正常并行请求。批量权限更新绑定首次读取的 token/版本集合,不将迟到数据应用到后续登录;遇到较新内容版本失败,调用方重新查询授权后再提交。
|
|
505
|
+
|
|
506
|
+
`create({ maxAge })` 可覆盖配置默认时长(正安全整数秒),该值持久化为 `Session.maxAge`;签发和刷新返回的 `expiresIn` 均使用它。滑动续期按该时长延长 access,返回已成功提交的 `lastActiveAt/expiresAt`;迟到请求只延长时间,不缩短较新续期。显式 `update` 的有效日期补丁完整生效并产生新内容版本。客户端根据本次返回的 `expiresIn` 安排刷新,不能硬编码配置默认值。
|
|
507
|
+
|
|
508
|
+
access 与 refresh 独立到期:用户分区保留至所有 access/refresh 的最晚有效期,但 access 认证只检查自己的 `expiresAt`。access 过期仍可凭未过期、未撤销的 refresh 轮换;每次轮换从新签发时刻重新计算 `refreshTokenMaxAge`,旧 access/refresh 同时失效。删除 access、logoutAll、单设备替换均撤销待刷新记录;仅 revokeRefresh 保留仍有效的 access。刷新路由只校验 refresh,不能先要求旧 access 认证成功。
|
|
509
|
+
|
|
510
|
+
会话仓库将缓存读取/提交失败返回为失败 HaiResult,不解释为会话不存在或过期,也不执行清理补偿。用户会话及索引共用一个文档,批量授权更新不再逐 token 调用 get/ttl 或维护第二份索引;只按已确认的绝对期限清理。故障恢复后可重新读取并重试更新/退出,logoutAll 仍覆盖原有全部记录。结构损坏同样失败关闭并保留原值,交由运维处理,禁止用空文档覆盖未知授权状态。
|
|
511
|
+
|
|
512
|
+
LDAP 的 ldapSyncUser: false 要求已有启用的本地用户以绑定数据库 credentialVersion;没有本地记录返回 USER_NOT_FOUND,不再创建虚拟 DN 身份。
|
|
513
|
+
|
|
514
|
+
Session.credentialVersion 绑定创建时的数据库凭据代际;CreateSessionOptions.expectedCredentialVersion 仅供已验证策略传入,旧代拒绝。省略它的受信低层 create 仍读取真实、启用的本地用户并绑定当前代,不能伪造缺少数据库记录的身份。
|
|
515
|
+
|
|
516
|
+
`iam.auth.logout(accessToken)` 直接按完整不透明令牌在用户分区执行一次 CAS,原子撤销 access 与关联 refresh;access 已到期也不能跳过撤销。非法/不存在的令牌幂等成功,缓存读取或提交故障返回失败,恢复后可用同一令牌重试。HTTP 登出只允许撤销所持令牌,不以 userId 定位他人会话,也不要求该 access 仍通过认证。调用方必须检查 `success`;本地 Cookie/安全存储清理不代表服务端撤销已经确认,失败应显示提示。
|