@h-ai/iam 0.1.0-alpha.59 → 0.1.0-alpha.61
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 +212 -55
- package/README.ja.md +212 -57
- package/README.md +225 -57
- package/dist/index.d.ts +104 -9
- package/dist/index.js +665 -339
- package/dist/index.js.map +1 -1
- package/package.json +20 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
4
|
|
|
5
|
-
[框架首页](../../README.md) · 相关模块:[@h-ai/audit](../audit/README.md) · [@h-ai/cache](../cache/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/crypto](../crypto/README.md) · [@h-ai/reldb](../reldb/README.md)
|
|
5
|
+
[框架首页](../../README.md) · 相关模块:[@h-ai/audit](../audit/README.md) · [@h-ai/cache](../cache/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/crypto](../crypto/README.md) · [@h-ai/reldb](../reldb/README.md) · [@h-ai/reach](../reach/README.md)
|
|
6
6
|
|
|
7
7
|
身份与访问管理模块,提供统一的 `iam` 对象实现认证、会话、授权与用户管理。
|
|
8
8
|
|
|
@@ -31,66 +31,182 @@ pnpm add @h-ai/iam
|
|
|
31
31
|
- `@h-ai/crypto` — 密码哈希(内部使用,自动初始化)
|
|
32
32
|
- `@h-ai/audit` — 审计日志(RBAC 关键操作记录)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
- `@h-ai/reach` — **可选**,应用安装、初始化并注入后可使用标准邮件/短信模板;自定义发送回调无需 Reach。
|
|
35
|
+
- `ldapts` — **可选**,仅在显式启用 LDAP 且没有注入 `ldapClientFactory` 时加载。
|
|
36
|
+
|
|
37
|
+
## 可选 LDAP 与通知接入
|
|
38
|
+
|
|
39
|
+
只使用密码、用户管理和 RBAC 时,无需安装或初始化 LDAP/Reach。两者均为可选 peer dependency,应用只安装选用的集成。下列发送、LDAP 片段位于应用启动/认证函数中,reldb/cache 必须先成功初始化;`username/password` 来自已校验输入。
|
|
40
|
+
|
|
41
|
+
### LDAP / Active Directory
|
|
42
|
+
|
|
43
|
+
使用默认客户端时执行 `pnpm add ldapts`,再显式设置 `login.ldap: true` 和 `ldap` 连接配置。只提供连接配置不会启用 LDAP。启用默认客户端却缺少 SDK 时,`iam.init()` 返回配置错误。
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
# config/_iam.yml(启动前设置 LDAP_BIND_PASSWORD)
|
|
47
|
+
login:
|
|
48
|
+
ldap: true
|
|
49
|
+
ldap:
|
|
50
|
+
url: ldaps://ldap.example.com:636
|
|
51
|
+
bindDn: cn=service,dc=example,dc=com
|
|
52
|
+
bindPassword: '${LDAP_BIND_PASSWORD}'
|
|
53
|
+
searchBase: ou=users,dc=example,dc=com
|
|
54
|
+
searchFilter: '(uid={{username}})'
|
|
55
|
+
usernameAttribute: uid
|
|
56
|
+
emailAttribute: mail
|
|
57
|
+
displayNameAttribute: cn
|
|
58
|
+
connectTimeout: 5000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`ldaps://` 使用 TLS;`ldap://` 加 `useTls: true` 使用 StartTLS。Active Directory 可用 `searchFilter: "(sAMAccountName={{username}})"` 和 `usernameAttribute: sAMAccountName`;占位符会转义后搜索。首次登录默认同步本地用户并分配默认角色,账户禁用与锁定仍有效。可传 `ldapSyncUser: false` 或自定义 `ldapClientFactory`;工厂返回具备 `bind/search/unbind` 的客户端,结果均为 HaiResult。注入工厂时无需安装 ldapts。
|
|
35
62
|
|
|
36
63
|
```ts
|
|
37
|
-
import {
|
|
64
|
+
import type { IamConfigSettingsInput } from '@h-ai/iam'
|
|
65
|
+
import { core } from '@h-ai/core'
|
|
38
66
|
import { iam } from '@h-ai/iam'
|
|
39
|
-
import { reldb } from '@h-ai/reldb'
|
|
40
67
|
|
|
41
|
-
//
|
|
42
|
-
await
|
|
43
|
-
|
|
68
|
+
// core 已加载 config/,reldb 与 cache 已成功初始化。
|
|
69
|
+
const initialized = await iam.init(core.config.getOrThrow<IamConfigSettingsInput>('iam'))
|
|
70
|
+
if (!initialized.success)
|
|
71
|
+
return initialized
|
|
72
|
+
const result = await iam.auth.loginWithLdap({ username, password })
|
|
73
|
+
if (!result.success)
|
|
74
|
+
return result
|
|
75
|
+
return result
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 使用可选 Reach 自动发送
|
|
79
|
+
|
|
80
|
+
执行 `pnpm add @h-ai/reach`,应用负责 Reach 的配置、初始化和关闭,IAM 只使用传入的实例。以下 SMTP/HTTP Provider 为示例,SMS 网关按 [Reach 接口](../reach/README.md) 接收通知。Console 只记录元数据,不能用于实际收信。
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
# config/_reach.yml;环境变量仅在服务端设置。
|
|
84
|
+
providers:
|
|
85
|
+
- name: email
|
|
86
|
+
type: smtp
|
|
87
|
+
host: '${SMTP_HOST}'
|
|
88
|
+
port: 465
|
|
89
|
+
secure: true
|
|
90
|
+
user: '${SMTP_USER}'
|
|
91
|
+
pass: '${SMTP_PASSWORD}'
|
|
92
|
+
from: '${MAIL_FROM}'
|
|
93
|
+
- name: sms
|
|
94
|
+
type: api
|
|
95
|
+
url: '${SMS_GATEWAY_URL}'
|
|
96
|
+
templates:
|
|
97
|
+
- name: otp_email
|
|
98
|
+
provider: email
|
|
99
|
+
subject: Verification code
|
|
100
|
+
body: 'Your code is {code}.'
|
|
101
|
+
- name: otp_sms
|
|
102
|
+
provider: sms
|
|
103
|
+
body: 'Your code is {code}.'
|
|
104
|
+
- name: password_reset
|
|
105
|
+
provider: email
|
|
106
|
+
subject: Password reset
|
|
107
|
+
body: 'Reset: {resetUrl}. Expires: {expiresAt}.'
|
|
108
|
+
- name: password_reset_sms
|
|
109
|
+
provider: sms
|
|
110
|
+
body: 'Reset: {resetUrl}. Expires: {expiresAt}.'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
标准模板及变量:`otp_email / otp_sms` 使用 `{code}`;`password_reset / password_reset_sms` 使用 `{token}`、`{expiresAt}`、`{resetUrl}`。`passwordReset.mode: phone` 选择短信重置。必须在 `iam.init()` 前配置模板;缺少通道时公开配置隐藏对应入口。
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import type { ReachConfigInput } from '@h-ai/reach'
|
|
117
|
+
import { core } from '@h-ai/core'
|
|
118
|
+
import { iam } from '@h-ai/iam'
|
|
119
|
+
import { reach } from '@h-ai/reach'
|
|
120
|
+
|
|
121
|
+
// core、reldb、cache 已初始化;按应用生命周期反序关闭 IAM 与 Reach。
|
|
122
|
+
const notification = await reach.init(core.config.getOrThrow<ReachConfigInput>('reach'))
|
|
123
|
+
if (!notification.success)
|
|
124
|
+
return notification
|
|
125
|
+
const initialized = await iam.init({
|
|
126
|
+
reach,
|
|
127
|
+
login: { otp: true },
|
|
128
|
+
otp: { email: true, sms: true },
|
|
129
|
+
passwordReset: { mode: 'email', resetUrl: 'https://app.example.com/auth/reset-password' },
|
|
130
|
+
})
|
|
131
|
+
if (!initialized.success)
|
|
132
|
+
return initialized
|
|
133
|
+
return iam.auth.sendOtp('user@example.com')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 应用自定义短信 / 邮件发送
|
|
44
137
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
138
|
+
注入 `onOtpSendEmail`、`onOtpSendSms` 和 `onPasswordResetRequest` 即可接入现有发送服务;可只实现需要的回调,也可与 Reach 混用,显式回调优先。以下服务为应用实现,示例假定其 Promise 仅在投递成功时完成;若服务返回 HaiResult,须检查 `success`,失败时在回调内抛错。IAM 捕获回调异常并返回业务错误,不将验证码、令牌或内部投递详情写入用户提示。
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { iam } from '@h-ai/iam'
|
|
142
|
+
import { mailService, smsService } from './notifications.js'
|
|
143
|
+
|
|
144
|
+
// mailService/smsService 为应用已有的发送服务;不需要安装或初始化 Reach。
|
|
145
|
+
// 调用方须检查服务投递结果,失败时拒绝 Promise,不能吞掉错误。
|
|
146
|
+
const initialized = await iam.init({
|
|
147
|
+
otp: { email: true, sms: true },
|
|
49
148
|
onOtpSendEmail: async (email, code) => {
|
|
50
|
-
await
|
|
149
|
+
await mailService.send({ to: email, subject: 'Verification code', text: code })
|
|
51
150
|
},
|
|
52
|
-
|
|
151
|
+
onOtpSendSms: async (phone, code) => {
|
|
152
|
+
await smsService.send({ to: phone, code })
|
|
153
|
+
},
|
|
154
|
+
passwordReset: { mode: 'email' },
|
|
53
155
|
onPasswordResetRequest: async (user, token, expiresAt) => {
|
|
54
|
-
|
|
156
|
+
if (!user.email)
|
|
157
|
+
throw new Error('Reset recipient unavailable')
|
|
158
|
+
const url = new URL('https://app.example.com/auth/reset-password')
|
|
159
|
+
url.searchParams.set('token', token)
|
|
160
|
+
await mailService.send({ to: user.email, subject: 'Password reset', text: url.toString(), expiresAt })
|
|
55
161
|
},
|
|
56
162
|
})
|
|
163
|
+
if (!initialized.success)
|
|
164
|
+
return initialized
|
|
165
|
+
```
|
|
57
166
|
|
|
58
|
-
|
|
59
|
-
const userResult = await iam.user.register({
|
|
60
|
-
username: 'admin',
|
|
61
|
-
email: 'admin@example.com',
|
|
62
|
-
password: 'Password123',
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// 3. 登录
|
|
66
|
-
const loginResult = await iam.auth.login({
|
|
67
|
-
identifier: 'admin',
|
|
68
|
-
password: 'Password123',
|
|
69
|
-
})
|
|
70
|
-
if (loginResult.success) {
|
|
71
|
-
const { user, tokens } = loginResult.data
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 4. 验证令牌
|
|
75
|
-
const session = await iam.auth.verifyToken(loginResult.data.tokens.accessToken)
|
|
167
|
+
## 快速开始
|
|
76
168
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
169
|
+
```ts
|
|
170
|
+
import { cache } from '@h-ai/cache'
|
|
171
|
+
import { core } from '@h-ai/core'
|
|
172
|
+
import { iam } from '@h-ai/iam'
|
|
173
|
+
import { reldb } from '@h-ai/reldb'
|
|
83
174
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
175
|
+
// 基础密码认证不需要安装 Reach 或 ldapts。
|
|
176
|
+
async function example() {
|
|
177
|
+
try {
|
|
178
|
+
const db = await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
179
|
+
if (!db.success)
|
|
180
|
+
return db
|
|
181
|
+
const cached = await cache.init({ type: 'memory' })
|
|
182
|
+
if (!cached.success)
|
|
183
|
+
return cached
|
|
184
|
+
const initialized = await iam.init({})
|
|
185
|
+
if (!initialized.success)
|
|
186
|
+
return initialized
|
|
187
|
+
const registered = await iam.user.register({ username: 'demo', password: 'Password123' })
|
|
188
|
+
if (!registered.success)
|
|
189
|
+
return registered
|
|
190
|
+
const login = await iam.auth.login({ identifier: 'demo', password: 'Password123' })
|
|
191
|
+
if (!login.success)
|
|
192
|
+
return login
|
|
193
|
+
return await iam.auth.verifyToken(login.data.tokens.accessToken)
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
// 应用关闭时按依赖的反向顺序释放。
|
|
197
|
+
await iam.close()
|
|
198
|
+
await cache.close()
|
|
199
|
+
await reldb.close()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
89
202
|
|
|
90
|
-
|
|
91
|
-
|
|
203
|
+
const result = await example()
|
|
204
|
+
if (!result.success)
|
|
205
|
+
core.logger.error('Authentication example failed', { code: result.error.code })
|
|
92
206
|
```
|
|
93
207
|
|
|
208
|
+
默认 LDAP 关闭;OTP 需要 `otp` 配置和可用发送回调,重置需要可用通知回调,才能在 `iam.publicConfig` 中启用。
|
|
209
|
+
|
|
94
210
|
`iam.config` 返回的是**脱敏后的配置快照**:LDAP `url` / `bindPassword` 等敏感配置会自动隐藏,便于状态检查但不能代替原始配置源。
|
|
95
211
|
|
|
96
212
|
## 设计说明
|
|
@@ -114,14 +230,14 @@ iam.init()
|
|
|
114
230
|
|
|
115
231
|
### 数据存储分布
|
|
116
232
|
|
|
117
|
-
| 数据类型 | 存储位置
|
|
118
|
-
| ------------------------------------------------- |
|
|
119
|
-
| 用户、角色、权限、关联关系、API Key | DB
|
|
120
|
-
| 会话(accessToken → Session) | 缓存
|
|
121
|
-
| Token 映射(refreshToken → userId + accessToken) | 缓存
|
|
122
|
-
| OTP 验证码 | 缓存
|
|
123
|
-
| 密码重置令牌 | 缓存
|
|
124
|
-
| API Key | DB
|
|
233
|
+
| 数据类型 | 存储位置 | 说明 |
|
|
234
|
+
| ------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
235
|
+
| 用户、角色、权限、关联关系、API Key | DB(基础 5 张,可选 API Key 1 张) | `hai_iam_users`、`hai_iam_roles`、`hai_iam_permissions`、`hai_iam_role_permissions`、`hai_iam_user_roles`、`hai_iam_api_keys` |
|
|
236
|
+
| 会话(accessToken → Session) | 缓存 | 有 TTL,滑动续期可选 |
|
|
237
|
+
| Token 映射(refreshToken → userId + accessToken) | 缓存 | 独立 key,refresh 后旧 token 立即失效 |
|
|
238
|
+
| OTP 验证码 | 缓存 | 有 TTL + 尝试次数限制 |
|
|
239
|
+
| 密码重置令牌 | 缓存 | 有 TTL + 最大验证次数限制 |
|
|
240
|
+
| API Key | DB | `hai_iam_api_keys`,明文不落库,仅存 hash;前缀用于候选检索 |
|
|
125
241
|
|
|
126
242
|
---
|
|
127
243
|
|
|
@@ -174,7 +290,7 @@ const result = await iam.auth.login({ identifier: 'admin', password: 'Password12
|
|
|
174
290
|
await iam.auth.sendOtp('user@example.com')
|
|
175
291
|
const result = await iam.auth.loginWithOtp({ identifier: 'user@example.com', code: '123456' })
|
|
176
292
|
|
|
177
|
-
// LDAP
|
|
293
|
+
// LDAP 登录(内置客户端,可选注入 ldapClientFactory)
|
|
178
294
|
const result = await iam.auth.loginWithLdap({ username: 'jdoe', password: 'pass' })
|
|
179
295
|
|
|
180
296
|
// API Key 登录(需启用 login.apikey: true)
|
|
@@ -250,7 +366,7 @@ await iam.session.deleteByUserId(userId)
|
|
|
250
366
|
|
|
251
367
|
#### 特别关注
|
|
252
368
|
|
|
253
|
-
-
|
|
369
|
+
- **纯缓存存储**:不落库;Memory 重启后丢失,共享 Redis 可跨 IAM 服务重启保留会话直至 TTL 或撤销
|
|
254
370
|
- **滑动续期**:`sliding: true` 时每次 `get()` 自动延长 TTL
|
|
255
371
|
- **Refresh Rotation**:旧 refreshToken 单次使用后立即失效,检测到重放即说明 token 泄漏
|
|
256
372
|
- **Token 强度**:256-bit crypto random(`crypto.getRandomValues(new Uint8Array(32))`),base64url 编码
|
|
@@ -259,6 +375,8 @@ await iam.session.deleteByUserId(userId)
|
|
|
259
375
|
|
|
260
376
|
### 子模块:授权(authz)— `iam.authz`
|
|
261
377
|
|
|
378
|
+
角色列表通过 `iam.authz.getRoleUserCounts(roleIds)` 一次聚合当前页角色的真实成员数,返回 `HaiResult<Map<string, number>>`。无关联为 0;查询失败显示加载错误,不生成零统计。
|
|
379
|
+
|
|
262
380
|
#### 关键对象
|
|
263
381
|
|
|
264
382
|
- **`AuthzOperations`** — 授权操作接口,覆盖角色 CRUD、权限 CRUD、角色-权限/用户-角色分配、权限检查
|
|
@@ -528,6 +646,58 @@ if (consumed.success) {
|
|
|
528
646
|
|
|
529
647
|
特性:密码学安全随机值、TTL、原子单次消费(并发消费仅一个成功)、用途绑定(防跨用途重放)。
|
|
530
648
|
|
|
649
|
+
## 配置驱动认证页面
|
|
650
|
+
|
|
651
|
+
服务端通过 `iam.publicConfig` 提供白名单配置,或通过客户端的 `client.iam.auth.publicConfig()` 标准 HTTP contract 获取。只展示已开启且具备依赖的登录方式;LDAP 接入见上方可选配置,默认关闭;只有选用默认客户端时才安装 ldapts。
|
|
652
|
+
|
|
653
|
+
| 配置 | 用途 |
|
|
654
|
+
| ------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
|
655
|
+
| `login.password / otp / ldap` | 独立启用密码、验证码、企业账号 |
|
|
656
|
+
| `otp.email / sms` | 独立启用邮件、短信 |
|
|
657
|
+
| `register.enabled / defaultEnabled` | 自助注册及新账户状态 |
|
|
658
|
+
| `passwordReset.enabled / mode / resetUrl` | 启用重置、选择 email/phone 渠道、配置重置页面绝对地址 |
|
|
659
|
+
| `agreements.showOnRegister / showOnLogin` | 注册/登录协议弹窗显示;链接只接受 HTTP(S) |
|
|
660
|
+
| `ui.variant / title / subtitle / brandTitle / brandText / logoUrl / illustrationUrl` | 卡片/分栏布局、标题、品牌、Logo 和配图 |
|
|
661
|
+
|
|
662
|
+
选用 Reach 时,在 reldb/cache/Reach 初始化成功后使用 `iam.init({ ...settings, reach })` 自动接入 Reach 的 `otp_email`、`otp_sms`、`password_reset`、`password_reset_sms` 模板。模板绑定对应邮件/短信 Provider;显式发送回调可覆盖默认行为。Console Provider 只记录元数据,实际收信需配置 SMTP、短信或 HTTP 通知服务。发送失败返回错误,不会伪报已发送。
|
|
663
|
+
|
|
664
|
+
SvelteKit 使用 `kit.auth.createApi(iam)` 导出标准 handler,匿名端点白名单使用 `kit.auth.publicPaths`。客户端通过 `kit.client.createAuthActions()` 连接 Cookie 端点;独立 HTTP 客户端通过 `apiClient.createAuthActions(client)` 连接 IAM contract 并自动保存 Token 到已配置安全存储。将配置及动作直接传入 `AuthPanel`,完成登录、注册、找回及重置;单独使用 `LoginForm` 可传 `methods`、`onsendotp`、`resendInterval`,密码表单可传完整 `passwordPolicy`。
|
|
665
|
+
|
|
666
|
+
管理员使用 `iam.user.createUser()` 建用户,调用端必须校验管理员权限;关闭自助注册不影响管理端。注册与密码登录开关独立,`registerAndLogin` 在注册完成后创建会话;默认禁用账户不会创建会话。
|
|
667
|
+
|
|
668
|
+
协议在 Modal 中打开,关闭后保留表单输入。使用 URL 时,协议页面须允许 iframe 嵌入;禁止脚本执行,并提供新窗口查看入口。`LoginForm/RegisterForm` 提供 `header/extraFields/footer` snippet,`AuthPanel` 透传 `loginHeader/loginFields/loginFooter` 和 `registerHeader/registerFields/registerFooter`;自定义字段由应用绑定状态并在动作中处理。示例见 [UI 用法](../ui/README.md#配置驱动认证页面)。
|
|
669
|
+
|
|
670
|
+
### Markdown 协议内容
|
|
671
|
+
|
|
672
|
+
协议支持 `agreements.userAgreementMarkdown` 与 `privacyPolicyMarkdown` 直接从配置加载;YAML 的 `|` 保留 Markdown 段落和换行。弹窗复用安全 MarkdownRenderer,关闭原始 HTML、脚本和代码复制工具。同主题同时提供正文与 URL 时优先正文,URL 可作为额外查看入口;只有 URL 时使用隔离 iframe。
|
|
673
|
+
|
|
674
|
+
```yaml
|
|
675
|
+
agreements:
|
|
676
|
+
showOnRegister: true
|
|
677
|
+
showOnLogin: false
|
|
678
|
+
userAgreementMarkdown: |
|
|
679
|
+
# 用户协议
|
|
680
|
+
|
|
681
|
+
**账户使用条款**
|
|
682
|
+
|
|
683
|
+
- 请妥善保管账户凭据。
|
|
684
|
+
privacyPolicyMarkdown: |
|
|
685
|
+
# 隐私政策
|
|
686
|
+
|
|
687
|
+
注册资料用于账户管理,会话信息用于身份认证。
|
|
688
|
+
# 可选 URL;同主题同时配置 Markdown 和 URL 时,弹窗优先显示 Markdown。
|
|
689
|
+
# userAgreementUrl: https://example.com/terms
|
|
690
|
+
# privacyPolicyUrl: https://example.com/privacy
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### 分布式安全与边界
|
|
694
|
+
|
|
695
|
+
所有节点必须共享同一数据库和 Redis;Memory 仅用于单进程。登录 `maxLoginAttempts / lockoutDuration` 使用数据库原子更新,到期后的失败重新计数。OTP `maxAttempts / resendInterval` 使用共享锁和独立发送间隔状态,耗尽或消费验证码不能绕过重发限制,并发只消费一次。重置 `tokenExpiresIn / maxAttempts` 使用共享缓存 TTL 和每用户共享锁;每次有效令牌请求(包括密码策略失败)计数,最新令牌有效且只成功消费一次。重置后撤销旧会话、解除登录锁定;禁用用户不能通过密码、LDAP、OTP 登录。密码策略以服务端为准。
|
|
696
|
+
|
|
697
|
+
可选字段(如 `ldap`)若尚未写入 YAML,请先声明该字段,或通过 `HAI_IAM` 的 JSON 值整体覆盖模块配置;已有字段支持 `HAI_IAM_LOGIN_OTP` 等环境变量覆盖。
|
|
698
|
+
|
|
699
|
+
标准 HTTP 入口:`GET /auth/config`、`POST /auth/login/ldap`、`POST /auth/password-reset/request`、`POST /auth/password-reset/confirm`;验证码继续使用 `/auth/otp/send` 与 `/auth/login/otp`。安全错误:`SECURITY_BUSY (hai:iam:023)`、`PASSWORD_RESET_DISABLED (hai:iam:024)`。
|
|
700
|
+
|
|
531
701
|
## 错误处理
|
|
532
702
|
|
|
533
703
|
所有操作返回 `HaiResult<T>`,通过 `HaiIamError` 做分支判断:
|
|
@@ -552,5 +722,3 @@ pnpm test
|
|
|
552
722
|
## 许可证
|
|
553
723
|
|
|
554
724
|
Apache-2.0
|
|
555
|
-
|
|
556
|
-
角色列表通过 `iam.authz.getRoleUserCounts(roleIds)` 一次聚合当前页角色的真实成员数,返回 `HaiResult<Map<string, number>>`。无关联为 0;查询失败显示加载错误,不生成零统计。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as _h_ai_core from '@h-ai/core';
|
|
3
3
|
import { HaiResult, PaginationOptionsInput, PaginatedResult } from '@h-ai/core';
|
|
4
|
+
import { ReachFunctions } from '@h-ai/reach';
|
|
4
5
|
import { DmlWithTxOperations } from '@h-ai/reldb';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -44,6 +45,8 @@ type PasswordConfig = z.infer<typeof PasswordConfigSchema>;
|
|
|
44
45
|
* OTP 配置 Schema
|
|
45
46
|
*/
|
|
46
47
|
declare const OtpConfigSchema: z.ZodObject<{
|
|
48
|
+
email: z.ZodDefault<z.ZodBoolean>;
|
|
49
|
+
sms: z.ZodDefault<z.ZodBoolean>;
|
|
47
50
|
length: z.ZodDefault<z.ZodNumber>;
|
|
48
51
|
expiresIn: z.ZodDefault<z.ZodNumber>;
|
|
49
52
|
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
@@ -55,6 +58,12 @@ type OtpConfig = z.infer<typeof OtpConfigSchema>;
|
|
|
55
58
|
* 密码重置配置 Schema
|
|
56
59
|
*/
|
|
57
60
|
declare const PasswordResetConfigSchema: z.ZodObject<{
|
|
61
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
62
|
+
email: "email";
|
|
63
|
+
phone: "phone";
|
|
64
|
+
}>>;
|
|
65
|
+
resetUrl: z.ZodOptional<z.ZodURL>;
|
|
66
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
58
67
|
tokenExpiresIn: z.ZodDefault<z.ZodNumber>;
|
|
59
68
|
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
60
69
|
}, z.core.$strip>;
|
|
@@ -64,7 +73,7 @@ type PasswordResetConfig = z.infer<typeof PasswordResetConfigSchema>;
|
|
|
64
73
|
* LDAP 配置 Schema
|
|
65
74
|
*/
|
|
66
75
|
declare const LdapConfigSchema: z.ZodObject<{
|
|
67
|
-
url: z.
|
|
76
|
+
url: z.ZodURL;
|
|
68
77
|
bindDn: z.ZodString;
|
|
69
78
|
bindPassword: z.ZodString;
|
|
70
79
|
searchBase: z.ZodString;
|
|
@@ -109,12 +118,27 @@ declare const SecurityConfigSchema: z.ZodObject<{
|
|
|
109
118
|
type SecurityConfig = z.infer<typeof SecurityConfigSchema>;
|
|
110
119
|
/** 协议展示配置 */
|
|
111
120
|
declare const AgreementConfigSchema: z.ZodObject<{
|
|
121
|
+
userAgreementMarkdown: z.ZodOptional<z.ZodString>;
|
|
122
|
+
privacyPolicyMarkdown: z.ZodOptional<z.ZodString>;
|
|
112
123
|
userAgreementUrl: z.ZodOptional<z.ZodURL>;
|
|
113
124
|
privacyPolicyUrl: z.ZodOptional<z.ZodURL>;
|
|
114
125
|
showOnRegister: z.ZodDefault<z.ZodBoolean>;
|
|
115
126
|
showOnLogin: z.ZodDefault<z.ZodBoolean>;
|
|
116
127
|
}, z.core.$strip>;
|
|
117
128
|
type AgreementConfig = z.infer<typeof AgreementConfigSchema>;
|
|
129
|
+
declare const AuthUiConfigSchema: z.ZodObject<{
|
|
130
|
+
variant: z.ZodDefault<z.ZodEnum<{
|
|
131
|
+
card: "card";
|
|
132
|
+
split: "split";
|
|
133
|
+
}>>;
|
|
134
|
+
title: z.ZodOptional<z.ZodString>;
|
|
135
|
+
subtitle: z.ZodOptional<z.ZodString>;
|
|
136
|
+
brandTitle: z.ZodOptional<z.ZodString>;
|
|
137
|
+
brandText: z.ZodOptional<z.ZodString>;
|
|
138
|
+
logoUrl: z.ZodOptional<z.ZodString>;
|
|
139
|
+
illustrationUrl: z.ZodOptional<z.ZodString>;
|
|
140
|
+
}, z.core.$strip>;
|
|
141
|
+
type AuthUiConfig = z.infer<typeof AuthUiConfigSchema>;
|
|
118
142
|
/**
|
|
119
143
|
* 会话配置 Schema
|
|
120
144
|
*/
|
|
@@ -154,6 +178,18 @@ type RbacConfig = z.infer<typeof RbacConfigSchema>;
|
|
|
154
178
|
* ```
|
|
155
179
|
*/
|
|
156
180
|
declare const IamConfigSchema: z.ZodObject<{
|
|
181
|
+
ui: z.ZodOptional<z.ZodObject<{
|
|
182
|
+
variant: z.ZodDefault<z.ZodEnum<{
|
|
183
|
+
card: "card";
|
|
184
|
+
split: "split";
|
|
185
|
+
}>>;
|
|
186
|
+
title: z.ZodOptional<z.ZodString>;
|
|
187
|
+
subtitle: z.ZodOptional<z.ZodString>;
|
|
188
|
+
brandTitle: z.ZodOptional<z.ZodString>;
|
|
189
|
+
brandText: z.ZodOptional<z.ZodString>;
|
|
190
|
+
logoUrl: z.ZodOptional<z.ZodString>;
|
|
191
|
+
illustrationUrl: z.ZodOptional<z.ZodString>;
|
|
192
|
+
}, z.core.$strip>>;
|
|
157
193
|
password: z.ZodOptional<z.ZodObject<{
|
|
158
194
|
minLength: z.ZodDefault<z.ZodNumber>;
|
|
159
195
|
maxLength: z.ZodDefault<z.ZodNumber>;
|
|
@@ -164,13 +200,15 @@ declare const IamConfigSchema: z.ZodObject<{
|
|
|
164
200
|
expirationDays: z.ZodDefault<z.ZodNumber>;
|
|
165
201
|
}, z.core.$strip>>;
|
|
166
202
|
otp: z.ZodOptional<z.ZodObject<{
|
|
203
|
+
email: z.ZodDefault<z.ZodBoolean>;
|
|
204
|
+
sms: z.ZodDefault<z.ZodBoolean>;
|
|
167
205
|
length: z.ZodDefault<z.ZodNumber>;
|
|
168
206
|
expiresIn: z.ZodDefault<z.ZodNumber>;
|
|
169
207
|
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
170
208
|
resendInterval: z.ZodDefault<z.ZodNumber>;
|
|
171
209
|
}, z.core.$strip>>;
|
|
172
210
|
ldap: z.ZodOptional<z.ZodObject<{
|
|
173
|
-
url: z.
|
|
211
|
+
url: z.ZodURL;
|
|
174
212
|
bindDn: z.ZodString;
|
|
175
213
|
bindPassword: z.ZodString;
|
|
176
214
|
searchBase: z.ZodString;
|
|
@@ -187,6 +225,12 @@ declare const IamConfigSchema: z.ZodObject<{
|
|
|
187
225
|
prefix: z.ZodDefault<z.ZodString>;
|
|
188
226
|
}, z.core.$strip>>;
|
|
189
227
|
passwordReset: z.ZodOptional<z.ZodObject<{
|
|
228
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
229
|
+
email: "email";
|
|
230
|
+
phone: "phone";
|
|
231
|
+
}>>;
|
|
232
|
+
resetUrl: z.ZodOptional<z.ZodURL>;
|
|
233
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
190
234
|
tokenExpiresIn: z.ZodDefault<z.ZodNumber>;
|
|
191
235
|
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
192
236
|
}, z.core.$strip>>;
|
|
@@ -201,6 +245,8 @@ declare const IamConfigSchema: z.ZodObject<{
|
|
|
201
245
|
defaultEnabled: z.ZodDefault<z.ZodBoolean>;
|
|
202
246
|
}, z.core.$strip>>;
|
|
203
247
|
agreements: z.ZodDefault<z.ZodObject<{
|
|
248
|
+
userAgreementMarkdown: z.ZodOptional<z.ZodString>;
|
|
249
|
+
privacyPolicyMarkdown: z.ZodOptional<z.ZodString>;
|
|
204
250
|
userAgreementUrl: z.ZodOptional<z.ZodURL>;
|
|
205
251
|
privacyPolicyUrl: z.ZodOptional<z.ZodURL>;
|
|
206
252
|
showOnRegister: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -216,7 +262,7 @@ declare const IamConfigSchema: z.ZodObject<{
|
|
|
216
262
|
singleDevice: z.ZodDefault<z.ZodBoolean>;
|
|
217
263
|
refreshTokenMaxAge: z.ZodDefault<z.ZodNumber>;
|
|
218
264
|
}, z.core.$strip>>;
|
|
219
|
-
rbac: z.
|
|
265
|
+
rbac: z.ZodDefault<z.ZodObject<{
|
|
220
266
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
221
267
|
superAdminRole: z.ZodDefault<z.ZodString>;
|
|
222
268
|
defaultRole: z.ZodDefault<z.ZodString>;
|
|
@@ -679,6 +725,10 @@ interface User {
|
|
|
679
725
|
* 用户协议/隐私协议展示配置
|
|
680
726
|
*/
|
|
681
727
|
interface AgreementDisplay {
|
|
728
|
+
/** 用户协议 Markdown 正文,显示优先于 URL。 */
|
|
729
|
+
userAgreementMarkdown?: string;
|
|
730
|
+
/** 隐私协议 Markdown 正文,显示优先于 URL。 */
|
|
731
|
+
privacyPolicyMarkdown?: string;
|
|
682
732
|
/** 用户协议 URL */
|
|
683
733
|
userAgreementUrl?: string;
|
|
684
734
|
/** 隐私协议 URL */
|
|
@@ -752,6 +802,8 @@ interface UpdateCurrentUserInput {
|
|
|
752
802
|
* 用户子功能接口
|
|
753
803
|
*/
|
|
754
804
|
interface UserOperations {
|
|
805
|
+
/** 管理端创建账户,不受自助注册开关限制;调用端必须执行管理员授权。 */
|
|
806
|
+
createUser: (options: RegisterOptions) => Promise<HaiResult<RegisterResult>>;
|
|
755
807
|
/**
|
|
756
808
|
* 注册用户
|
|
757
809
|
*
|
|
@@ -1272,6 +1324,38 @@ interface LdapSearchEntry {
|
|
|
1272
1324
|
*/
|
|
1273
1325
|
type LdapClientFactory = (config: LdapConfig) => Promise<HaiResult<LdapClient>>;
|
|
1274
1326
|
|
|
1327
|
+
/** 提供给认证页面的白名单配置;不含连接信息、密钥及安全状态。 */
|
|
1328
|
+
|
|
1329
|
+
interface IamPublicConfig {
|
|
1330
|
+
/** 页面显示密码策略,不包含密码哈希或账户信息。 */
|
|
1331
|
+
password: ReturnType<typeof PasswordConfigSchema.parse>;
|
|
1332
|
+
register: {
|
|
1333
|
+
enabled: boolean;
|
|
1334
|
+
};
|
|
1335
|
+
/** 方式开关同时考虑配置和发送回调就绪状态,避免展示无法使用的入口。 */
|
|
1336
|
+
login: {
|
|
1337
|
+
password: boolean;
|
|
1338
|
+
otp: boolean;
|
|
1339
|
+
ldap: boolean;
|
|
1340
|
+
email: boolean;
|
|
1341
|
+
sms: boolean;
|
|
1342
|
+
};
|
|
1343
|
+
passwordReset: {
|
|
1344
|
+
enabled: boolean;
|
|
1345
|
+
mode: 'email' | 'phone';
|
|
1346
|
+
};
|
|
1347
|
+
/** 单位:验证码位数、重发间隔秒;安全计数与期限只由服务端执行。 */
|
|
1348
|
+
otp: {
|
|
1349
|
+
length: number;
|
|
1350
|
+
resendInterval: number;
|
|
1351
|
+
};
|
|
1352
|
+
agreements: IamConfig['agreements'];
|
|
1353
|
+
ui: ReturnType<typeof AuthUiConfigSchema.parse>;
|
|
1354
|
+
session: {
|
|
1355
|
+
maxAge: number;
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1275
1359
|
/**
|
|
1276
1360
|
* @h-ai/iam — 一次性票据类型
|
|
1277
1361
|
*
|
|
@@ -1388,6 +1472,8 @@ declare const HaiIamError: {
|
|
|
1388
1472
|
readonly RESET_TOKEN_EXPIRED: _h_ai_core.HaiErrorDef;
|
|
1389
1473
|
readonly RESET_TOKEN_MAX_ATTEMPTS: _h_ai_core.HaiErrorDef;
|
|
1390
1474
|
readonly OTP_EXPIRED: _h_ai_core.HaiErrorDef;
|
|
1475
|
+
readonly SECURITY_BUSY: _h_ai_core.HaiErrorDef;
|
|
1476
|
+
readonly PASSWORD_RESET_DISABLED: _h_ai_core.HaiErrorDef;
|
|
1391
1477
|
readonly SESSION_NOT_FOUND: _h_ai_core.HaiErrorDef;
|
|
1392
1478
|
readonly SESSION_EXPIRED: _h_ai_core.HaiErrorDef;
|
|
1393
1479
|
readonly SESSION_INVALID: _h_ai_core.HaiErrorDef;
|
|
@@ -1421,7 +1507,12 @@ declare const HaiIamError: {
|
|
|
1421
1507
|
* 传入 `iam.init()` 的唯一参数。
|
|
1422
1508
|
*/
|
|
1423
1509
|
interface IamConfigInput extends IamConfigSettingsInput {
|
|
1424
|
-
/**
|
|
1510
|
+
/**
|
|
1511
|
+
* 可选、已初始化的触达模块;由应用安装并注入,IAM 不导入或关闭该模块。
|
|
1512
|
+
* 自动使用 otp_email、otp_sms、password_reset、password_reset_sms 模板;显式发送回调优先。
|
|
1513
|
+
*/
|
|
1514
|
+
reach?: ReachFunctions;
|
|
1515
|
+
/** LDAP 客户端工厂(可选);启用 LDAP 且未注入时才加载应用安装的 ldapts。 */
|
|
1425
1516
|
ldapClientFactory?: LdapClientFactory;
|
|
1426
1517
|
/** LDAP 用户同步开关(默认 true) */
|
|
1427
1518
|
ldapSyncUser?: boolean;
|
|
@@ -1430,7 +1521,7 @@ interface IamConfigInput extends IamConfigSettingsInput {
|
|
|
1430
1521
|
*
|
|
1431
1522
|
* 当用户请求密码重置时,框架生成令牌后通过此回调通知业务层,
|
|
1432
1523
|
* 业务层负责将令牌通过邮件/短信等渠道发送给用户。
|
|
1433
|
-
*
|
|
1524
|
+
* 也可传入已初始化的 reach 自动发送;两者均未配置时重置功能不可用。
|
|
1434
1525
|
*
|
|
1435
1526
|
* @param user - 请求重置的用户信息
|
|
1436
1527
|
* @param token - 重置令牌(明文,需发送给用户)
|
|
@@ -1442,7 +1533,8 @@ interface IamConfigInput extends IamConfigSettingsInput {
|
|
|
1442
1533
|
*
|
|
1443
1534
|
* 当用户请求 OTP 验证码时,框架生成验证码后通过此回调通知业务层,
|
|
1444
1535
|
* 业务层负责将验证码通过邮件发送给用户。
|
|
1445
|
-
*
|
|
1536
|
+
* 显式回调覆盖 Reach 的 otp_email 模板;两者均不可用时该登录入口关闭。
|
|
1537
|
+
* 投递失败须拒绝 Promise,异常由 IAM 转为业务结果,不要在回调中吞掉失败。
|
|
1446
1538
|
*
|
|
1447
1539
|
* @param email - 目标邮箱
|
|
1448
1540
|
* @param code - 验证码
|
|
@@ -1453,7 +1545,8 @@ interface IamConfigInput extends IamConfigSettingsInput {
|
|
|
1453
1545
|
*
|
|
1454
1546
|
* 当用户请求 OTP 验证码时,框架生成验证码后通过此回调通知业务层,
|
|
1455
1547
|
* 业务层负责将验证码通过短信发送给用户。
|
|
1456
|
-
*
|
|
1548
|
+
* 显式回调覆盖 Reach 的 otp_sms 模板;两者均不可用时该登录入口关闭。
|
|
1549
|
+
* 投递失败须拒绝 Promise,异常由 IAM 转为业务结果,不要在回调中吞掉失败。
|
|
1457
1550
|
*
|
|
1458
1551
|
* @param phone - 目标手机号
|
|
1459
1552
|
* @param code - 验证码
|
|
@@ -1469,7 +1562,7 @@ interface IamFunctions {
|
|
|
1469
1562
|
/**
|
|
1470
1563
|
* 初始化 IAM 服务
|
|
1471
1564
|
*
|
|
1472
|
-
*
|
|
1565
|
+
* 已初始化时先关闭旧内部状态再重新初始化;并发初始化返回配置错误。
|
|
1473
1566
|
* 内部按依赖顺序创建 session → authz → authn → user 子功能,
|
|
1474
1567
|
* 并可选执行种子数据初始化。
|
|
1475
1568
|
*
|
|
@@ -1494,6 +1587,8 @@ interface IamFunctions {
|
|
|
1494
1587
|
close: () => Promise<void>;
|
|
1495
1588
|
/** 当前脱敏后的配置快照(未初始化时为 null) */
|
|
1496
1589
|
readonly config: IamConfig | null;
|
|
1590
|
+
/** 安全且反映实际可用能力的认证页面配置。 */
|
|
1591
|
+
readonly publicConfig: IamPublicConfig | null;
|
|
1497
1592
|
/** 是否已初始化 */
|
|
1498
1593
|
readonly isInitialized: boolean;
|
|
1499
1594
|
/** 注册功能是否启用(未初始化或未配置时默认启用) */
|
|
@@ -1521,4 +1616,4 @@ interface IamFunctions {
|
|
|
1521
1616
|
|
|
1522
1617
|
declare const iam: IamFunctions;
|
|
1523
1618
|
|
|
1524
|
-
export { type AgreementConfig, AgreementConfigSchema, type AgreementDisplay, type ApiKey, type ApiKeyConfig, ApiKeyConfigSchema, type ApiKeyCredentials, type ApiKeyOperations, type ApiKeySortDirection, type ApiKeySortField, type AuthResult, type AuthStrategy, type AuthStrategyType, AuthStrategyTypeSchema, type AuthnOperations, type AuthzOperations, type ConsumeTicketOptions, type ConsumedTicket, type CreateApiKeyOptions, type CreateApiKeyResult, type CreateSessionOptions, type Credentials, HaiIamError, type IamConfig, type IamConfigInput, IamConfigSchema, type IamConfigSettingsInput, type IamFunctions, type IssueTicketOptions, type IssuedTicket, type LdapClientFactory, type LdapConfig, LdapConfigSchema, type LdapCredentials, type ListApiKeysOptions, type ListUsersOptions, type LoginConfig, LoginConfigSchema, type OtpConfig, OtpConfigSchema, type OtpCredentials, type PasswordConfig, PasswordConfigSchema, type PasswordCredentials, type PasswordResetConfig, PasswordResetConfigSchema, type Permission, type PermissionQueryOptions, type PermissionType, type RbacConfig, RbacConfigSchema, type RegisterConfig, RegisterConfigSchema, type RegisterOptions, type RegisterResult, type Role, type SecurityConfig, SecurityConfigSchema, type Session, type SessionConfig, SessionConfigSchema, type SessionData, type SessionFieldUpdates, type SessionOperations, type StoredUser, type TicketGrant, type TicketOperations, type TokenPair, type UpdateCurrentUserInput, type User, type UserOperations, type UserSortDirection, type UserSortField, iam };
|
|
1619
|
+
export { type AgreementConfig, AgreementConfigSchema, type AgreementDisplay, type ApiKey, type ApiKeyConfig, ApiKeyConfigSchema, type ApiKeyCredentials, type ApiKeyOperations, type ApiKeySortDirection, type ApiKeySortField, type AuthResult, type AuthStrategy, type AuthStrategyType, AuthStrategyTypeSchema, type AuthUiConfig, AuthUiConfigSchema, type AuthnOperations, type AuthzOperations, type ConsumeTicketOptions, type ConsumedTicket, type CreateApiKeyOptions, type CreateApiKeyResult, type CreateSessionOptions, type Credentials, HaiIamError, type IamConfig, type IamConfigInput, IamConfigSchema, type IamConfigSettingsInput, type IamFunctions, type IamPublicConfig, type IssueTicketOptions, type IssuedTicket, type LdapClientFactory, type LdapConfig, LdapConfigSchema, type LdapCredentials, type ListApiKeysOptions, type ListUsersOptions, type LoginConfig, LoginConfigSchema, type OtpConfig, OtpConfigSchema, type OtpCredentials, type PasswordConfig, PasswordConfigSchema, type PasswordCredentials, type PasswordResetConfig, PasswordResetConfigSchema, type Permission, type PermissionQueryOptions, type PermissionType, type RbacConfig, RbacConfigSchema, type RegisterConfig, RegisterConfigSchema, type RegisterOptions, type RegisterResult, type Role, type SecurityConfig, SecurityConfigSchema, type Session, type SessionConfig, SessionConfigSchema, type SessionData, type SessionFieldUpdates, type SessionOperations, type StoredUser, type TicketGrant, type TicketOperations, type TokenPair, type UpdateCurrentUserInput, type User, type UserOperations, type UserSortDirection, type UserSortField, iam };
|