@h-ai/iam 1.0.0-beta.19 → 1.0.0-beta.20
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 +253 -85
- package/README.ja.md +256 -88
- package/README.md +259 -91
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -4,23 +4,9 @@
|
|
|
4
4
|
|
|
5
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
|
-
## 简介
|
|
8
|
-
|
|
9
7
|
身份与访问管理模块,提供统一的 `iam` 对象实现认证、会话、授权与用户管理。
|
|
10
8
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
| 功能 | 说明 |
|
|
14
|
-
| -------------- | -------------------------------------------------------------------------------------- |
|
|
15
|
-
| **认证** | 密码、OTP(邮箱/短信验证码)、LDAP、API Key 多策略认证 |
|
|
16
|
-
| **会话** | 有状态会话(随机访问令牌 + 缓存,滑动续期可选) |
|
|
17
|
-
| **授权** | RBAC 角色与权限管理(DB 批量读取,通配符权限匹配) |
|
|
18
|
-
| **用户管理** | 注册、查询、更新、密码重置、管理员重置密码 |
|
|
19
|
-
| **API Key** | API Key 创建、吊销、验证,支持 scope 与过期时间 |
|
|
20
|
-
| **一次性票据** | 短期、一次性能力票据(签发 / 原子消费),用于 WebSocket 等无法携带 Bearer Token 的通道 |
|
|
21
|
-
| **前端客户端** | HTTP API 契约定义,支持独立前端使用 |
|
|
22
|
-
|
|
23
|
-
## 使用入门
|
|
9
|
+
## 快速开始
|
|
24
10
|
|
|
25
11
|
### 安装
|
|
26
12
|
|
|
@@ -38,6 +24,223 @@ pnpm add @h-ai/iam
|
|
|
38
24
|
- `@h-ai/reach` — **可选**,应用安装、初始化并注入后可使用标准邮件/短信模板;自定义发送回调无需 Reach。
|
|
39
25
|
- `ldapts` — **可选**,仅在显式启用 LDAP 且没有注入 `ldapClientFactory` 时加载。
|
|
40
26
|
|
|
27
|
+
```ts
|
|
28
|
+
import { cache } from '@h-ai/cache'
|
|
29
|
+
import { core } from '@h-ai/core'
|
|
30
|
+
import { iam } from '@h-ai/iam'
|
|
31
|
+
import { reldb } from '@h-ai/reldb'
|
|
32
|
+
|
|
33
|
+
// 基础密码认证不需要安装 Reach 或 ldapts。
|
|
34
|
+
async function example() {
|
|
35
|
+
try {
|
|
36
|
+
const db = await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
37
|
+
if (!db.success)
|
|
38
|
+
return db
|
|
39
|
+
const cached = await cache.init({ type: 'memory' })
|
|
40
|
+
if (!cached.success)
|
|
41
|
+
return cached
|
|
42
|
+
const initialized = await iam.init({})
|
|
43
|
+
if (!initialized.success)
|
|
44
|
+
return initialized
|
|
45
|
+
const registered = await iam.user.register({ username: 'demo', password: 'Password123' })
|
|
46
|
+
if (!registered.success)
|
|
47
|
+
return registered
|
|
48
|
+
const login = await iam.auth.login({ identifier: 'demo', password: 'Password123' })
|
|
49
|
+
if (!login.success)
|
|
50
|
+
return login
|
|
51
|
+
return await iam.auth.verifyToken(login.data.tokens.accessToken)
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
// 应用关闭时按依赖的反向顺序释放。
|
|
55
|
+
await iam.close()
|
|
56
|
+
await cache.close()
|
|
57
|
+
await reldb.close()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = await example()
|
|
62
|
+
if (!result.success)
|
|
63
|
+
core.logger.error('Authentication example failed', { code: result.error.code })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
默认 LDAP 关闭;OTP 需要 `otp` 配置和可用发送回调,重置需要可用通知回调,才能在 `iam.publicConfig` 中启用。
|
|
67
|
+
|
|
68
|
+
`iam.config` 返回的是**脱敏后的配置快照**:LDAP `url` / `bindPassword` 等敏感配置会自动隐藏,便于状态检查但不能代替原始配置源。
|
|
69
|
+
|
|
70
|
+
## 功能
|
|
71
|
+
|
|
72
|
+
| 功能 | 说明 |
|
|
73
|
+
| -------------- | -------------------------------------------------------------------------------------- |
|
|
74
|
+
| **认证** | 密码、OTP(邮箱/短信验证码)、LDAP、API Key 多策略认证 |
|
|
75
|
+
| **会话** | 有状态会话(随机访问令牌 + 缓存,滑动续期可选) |
|
|
76
|
+
| **授权** | RBAC 角色与权限管理(DB 批量读取,通配符权限匹配) |
|
|
77
|
+
| **用户管理** | 注册、查询、更新、密码重置、管理员重置密码 |
|
|
78
|
+
| **API Key** | API Key 创建、吊销、验证,支持 scope 与过期时间 |
|
|
79
|
+
| **一次性票据** | 短期、一次性能力票据(签发 / 原子消费),用于 WebSocket 等无法携带 Bearer Token 的通道 |
|
|
80
|
+
| **前端客户端** | HTTP API 契约定义,支持独立前端使用 |
|
|
81
|
+
|
|
82
|
+
### 功能输入输出
|
|
83
|
+
|
|
84
|
+
以下片段依赖本节完整初始化流程。示例中的业务值由宿主校验和授权后传入;异步领域结果先判断 success,再读取 data。表中 void 表示操作已完成且没有返回数据,null 表示不存在;同步工厂/工具直接返回表中类型。
|
|
85
|
+
|
|
86
|
+
ID/code 必须来自已授权业务;accessToken/refreshToken/用途票据不是用户 ID。options/data 是本功能的公开输入类型,expectedVersion 为读取所得版本,tx 为同一已开启事务。低层写入口不替宿主检查管理员权限。
|
|
87
|
+
|
|
88
|
+
#### iam.auth
|
|
89
|
+
|
|
90
|
+
登录输入对应策略凭据及可信请求 context,注册登录输入注册资料;成功返回用户、令牌和角色/权限,失败不建立会话。
|
|
91
|
+
|
|
92
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
93
|
+
| --------------------------- | -------------------- | ------------ | --------------------------------------------------------- |
|
|
94
|
+
| `iam.auth.login` | credentials, context | `AuthResult` | `await iam.auth.login({ identifier, password })` |
|
|
95
|
+
| `iam.auth.loginWithOtp` | credentials, context | `AuthResult` | `await iam.auth.loginWithOtp({ identifier, code })` |
|
|
96
|
+
| `iam.auth.loginWithLdap` | credentials, context | `AuthResult` | `await iam.auth.loginWithLdap({ username, password })` |
|
|
97
|
+
| `iam.auth.loginWithApiKey` | credentials, context | `AuthResult` | `await iam.auth.loginWithApiKey({ key: rawKey })` |
|
|
98
|
+
| `iam.auth.registerAndLogin` | options, context | `AuthResult` | `await iam.auth.registerAndLogin({ username, password })` |
|
|
99
|
+
|
|
100
|
+
#### iam.auth
|
|
101
|
+
|
|
102
|
+
挑战输入 operation;OTP 输入标识符/挑战;令牌验证返回当前 Session,登出返回 void;再次验证输入 token/密码/操作返回用途票据,敏感检查输入 token/操作/票据返回 void。
|
|
103
|
+
|
|
104
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
105
|
+
| ---------------------------------- | -------------------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
|
106
|
+
| `iam.auth.createCaptcha` | input | `IamCaptchaChallenge` | `await iam.auth.createCaptcha({ operation: 'login' })` |
|
|
107
|
+
| `iam.auth.sendOtp` | identifier, options | `{ expiresAt: Date; }` | `await iam.auth.sendOtp(identifier)` |
|
|
108
|
+
| `iam.auth.verifyToken` | accessToken | `Session` | `await iam.auth.verifyToken(accessToken)` |
|
|
109
|
+
| `iam.auth.logout` | accessToken | `void` | `await iam.auth.logout(accessToken)` |
|
|
110
|
+
| `iam.auth.reauthenticate` | accessToken, password, operation | `IssuedTicket` | `await iam.auth.reauthenticate(accessToken, password, 'users:delete')` |
|
|
111
|
+
| `iam.auth.checkSensitiveOperation` | accessToken, operation, ticket | `void` | `await iam.auth.checkSensitiveOperation(accessToken, 'users:delete', operationTicket)` |
|
|
112
|
+
|
|
113
|
+
#### iam.user
|
|
114
|
+
|
|
115
|
+
用户管理输入注册资料、可信用户 ID/数组、筛选分页或 token/补丁;分别返回注册结果、用户/null、用户数组、分页或更新用户,删除返回 void。管理员授权由入口执行。
|
|
116
|
+
|
|
117
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
118
|
+
| ---------------------------- | ----------------- | ----------------------- | ------------------------------------------------------------------------- |
|
|
119
|
+
| `iam.user.createUser` | options | `RegisterResult` | `await iam.user.createUser({ username, password })` |
|
|
120
|
+
| `iam.user.register` | options | `RegisterResult` | `await iam.user.register({ username, password })` |
|
|
121
|
+
| `iam.user.inviteUser` | options | `RegisterResult` | `await iam.user.inviteUser({ username, email })` |
|
|
122
|
+
| `iam.user.getUser` | userId, options | `User \| null` | `await iam.user.getUser(userId)` |
|
|
123
|
+
| `iam.user.getUsers` | userIds, options | `User[]` | `await iam.user.getUsers([userId], { include: ['roles'] })` |
|
|
124
|
+
| `iam.user.listUsers` | options | `PaginatedResult<User>` | `await iam.user.listUsers({ page: 1, pageSize: 20 })` |
|
|
125
|
+
| `iam.user.getCurrentUser` | accessToken | `User` | `await iam.user.getCurrentUser(accessToken)` |
|
|
126
|
+
| `iam.user.updateCurrentUser` | accessToken, data | `User` | `await iam.user.updateCurrentUser(accessToken, { displayName: 'Alice' })` |
|
|
127
|
+
| `iam.user.updateUser` | userId, data | `User` | `await iam.user.updateUser(userId, { displayName: 'Alice' })` |
|
|
128
|
+
| `iam.user.deleteUser` | userId | `void` | `await iam.user.deleteUser(userId)` |
|
|
129
|
+
|
|
130
|
+
#### iam.user
|
|
131
|
+
|
|
132
|
+
密码流程输入用户 ID/旧新密码、到期续密资料、找回标识符或用途 token;领域结果为 void。validatePassword 同步检查策略;scanInactiveAccounts 无参数返回停用数。
|
|
133
|
+
|
|
134
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
135
|
+
| ------------------------------------ | ------------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
|
136
|
+
| `iam.user.adminResetPassword` | userId, newPassword | `void` | `await iam.user.adminResetPassword(userId, newPassword)` |
|
|
137
|
+
| `iam.user.renewPassword` | input | `void` | `await iam.user.renewPassword({ identifier, password, newPassword })` |
|
|
138
|
+
| `iam.user.changePassword` | userId, oldPassword, newPassword | `void` | `await iam.user.changePassword(userId, oldPassword, newPassword)` |
|
|
139
|
+
| `iam.user.changeCurrentUserPassword` | accessToken, oldPassword, newPassword | `void` | `await iam.user.changeCurrentUserPassword(accessToken, oldPassword, newPassword)` |
|
|
140
|
+
| `iam.user.requestPasswordReset` | identifier, options | `void` | `await iam.user.requestPasswordReset(identifier)` |
|
|
141
|
+
| `iam.user.confirmPasswordReset` | token, newPassword | `void` | `await iam.user.confirmPasswordReset(resetToken, newPassword)` |
|
|
142
|
+
| `iam.user.validatePassword` | password | `void` | `iam.user.validatePassword(newPassword)` |
|
|
143
|
+
| `iam.user.scanInactiveAccounts` | 无参数 | `number` | `await iam.user.scanInactiveAccounts()` |
|
|
144
|
+
|
|
145
|
+
#### iam.authz
|
|
146
|
+
|
|
147
|
+
授权查询输入可信用户 ID/ID数组及权限 code,返回 roles/permissions、boolean、权限/角色数组或按 ID 索引的 Map。不能把前端自报角色当授权依据。
|
|
148
|
+
|
|
149
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
150
|
+
| ------------------------------------- | ------------------ | ----------------------------------------- | -------------------------------------------------------- |
|
|
151
|
+
| `iam.authz.getUserAuthorization` | userId | `Pick<Session, "roles" \| "permissions">` | `await iam.authz.getUserAuthorization(userId)` |
|
|
152
|
+
| `iam.authz.checkPermission` | userId, permission | `boolean` | `await iam.authz.checkPermission(userId, 'orders:read')` |
|
|
153
|
+
| `iam.authz.getUserPermissions` | userId | `Permission[]` | `await iam.authz.getUserPermissions(userId)` |
|
|
154
|
+
| `iam.authz.getUserRoles` | userId | `Role[]` | `await iam.authz.getUserRoles(userId)` |
|
|
155
|
+
| `iam.authz.getUserRolesForMany` | userIds | `Map<string, Role[]>` | `await iam.authz.getUserRolesForMany([userId])` |
|
|
156
|
+
| `iam.authz.getRolePermissionsForMany` | roleIds | `Map<string, Permission[]>` | `await iam.authz.getRolePermissionsForMany([roleId])` |
|
|
157
|
+
|
|
158
|
+
#### iam.authz
|
|
159
|
+
|
|
160
|
+
授权关系写入输入用户/角色/权限 ID,支持可选 tx;返回 void 并使共享授权缓存失效。syncRoles 的数组为期望完整集合。
|
|
161
|
+
|
|
162
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
163
|
+
| ------------------------------------ | ------------------------ | -------- | ---------------------------------------------------------------- |
|
|
164
|
+
| `iam.authz.assignRole` | userId, roleId, tx | `void` | `await iam.authz.assignRole(userId, roleId)` |
|
|
165
|
+
| `iam.authz.removeRole` | userId, roleId, tx | `void` | `await iam.authz.removeRole(userId, roleId)` |
|
|
166
|
+
| `iam.authz.syncRoles` | userId, roleIds, tx | `void` | `await iam.authz.syncRoles(userId, [roleId])` |
|
|
167
|
+
| `iam.authz.assignPermissionToRole` | roleId, permissionId, tx | `void` | `await iam.authz.assignPermissionToRole(roleId, permissionId)` |
|
|
168
|
+
| `iam.authz.removePermissionFromRole` | roleId, permissionId, tx | `void` | `await iam.authz.removePermissionFromRole(roleId, permissionId)` |
|
|
169
|
+
|
|
170
|
+
#### iam.authz
|
|
171
|
+
|
|
172
|
+
RBAC 管理输入实体资料、ID/code、分页或补丁;创建/更新返回实体,单查返回实体/null,全查返回分页,角色权限为数组、用户计数为 Map,删除为 void。
|
|
173
|
+
|
|
174
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
175
|
+
| ------------------------------- | ---------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| `iam.authz.createRole` | role, tx | `Role` | `await iam.authz.createRole({ code: 'editor', name: 'Editor', description: null, isSystem: false })` |
|
|
177
|
+
| `iam.authz.getRole` | roleId | `Role \| null` | `await iam.authz.getRole(roleId)` |
|
|
178
|
+
| `iam.authz.getRoleByCode` | code | `Role \| null` | `await iam.authz.getRoleByCode('editor')` |
|
|
179
|
+
| `iam.authz.getAllRoles` | options | `PaginatedResult<Role>` | `await iam.authz.getAllRoles({ page: 1, pageSize: 20 })` |
|
|
180
|
+
| `iam.authz.getRoleUserCounts` | roleIds | `Map<string, number>` | `await iam.authz.getRoleUserCounts([roleId])` |
|
|
181
|
+
| `iam.authz.updateRole` | roleId, data, tx | `Role` | `await iam.authz.updateRole(roleId, { name: 'Editor' })` |
|
|
182
|
+
| `iam.authz.deleteRole` | roleId, tx | `void` | `await iam.authz.deleteRole(roleId)` |
|
|
183
|
+
| `iam.authz.createPermission` | permission, tx | `Permission` | `await iam.authz.createPermission({ code: 'orders:read', name: 'Read orders', type: 'api' })` |
|
|
184
|
+
| `iam.authz.getPermission` | permissionId | `Permission \| null` | `await iam.authz.getPermission(permissionId)` |
|
|
185
|
+
| `iam.authz.getPermissionByCode` | code | `Permission \| null` | `await iam.authz.getPermissionByCode('orders:read')` |
|
|
186
|
+
| `iam.authz.getAllPermissions` | options | `PaginatedResult<Permission>` | `await iam.authz.getAllPermissions({ page: 1, pageSize: 20 })` |
|
|
187
|
+
| `iam.authz.deletePermission` | permissionId, tx | `void` | `await iam.authz.deletePermission(permissionId)` |
|
|
188
|
+
| `iam.authz.getRolePermissions` | roleId | `Permission[]` | `await iam.authz.getRolePermissions(roleId)` |
|
|
189
|
+
|
|
190
|
+
#### iam.authz
|
|
191
|
+
|
|
192
|
+
为权限绑定再次验证用途。输入可选 operation 返回敏感操作数组;输入 permissionId 和 operations 保存映射并返回 void。
|
|
193
|
+
|
|
194
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
195
|
+
| -------------------------------------------- | ------------------------ | ---------- | ---------------------------------------------------------------------------------- |
|
|
196
|
+
| `iam.authz.getSensitiveOperations` | operation | `string[]` | `await iam.authz.getSensitiveOperations('users:delete')` |
|
|
197
|
+
| `iam.authz.setPermissionSensitiveOperations` | permissionId, operations | `void` | `await iam.authz.setPermissionSensitiveOperations(permissionId, ['users:delete'])` |
|
|
198
|
+
|
|
199
|
+
#### iam.session
|
|
200
|
+
|
|
201
|
+
低层会话管理:可信创建资料返回 Session;token 查询为 Session/null,验证为 Session;更新须 expectedVersion;用户批量删除返回数,刷新返回 TokenPair,其余修改为 void。受保护请求应走 auth.verifyToken。
|
|
202
|
+
|
|
203
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
204
|
+
| ------------------------------- | ---------------------------------- | ----------------- | ---------------------------------------------------------------------- |
|
|
205
|
+
| `iam.session.create` | options | `Session` | `await iam.session.create(trustedSessionInput)` |
|
|
206
|
+
| `iam.session.get` | accessToken | `Session \| null` | `await iam.session.get(accessToken)` |
|
|
207
|
+
| `iam.session.verifyToken` | accessToken | `Session` | `await iam.session.verifyToken(accessToken)` |
|
|
208
|
+
| `iam.session.update` | accessToken, data, expectedVersion | `void` | `await iam.session.update(accessToken, sessionPatch, expectedVersion)` |
|
|
209
|
+
| `iam.session.delete` | accessToken | `void` | `await iam.session.delete(accessToken)` |
|
|
210
|
+
| `iam.session.deleteByUserId` | userId | `number` | `await iam.session.deleteByUserId(userId)` |
|
|
211
|
+
| `iam.session.refresh` | refreshToken | `TokenPair` | `await iam.session.refresh(refreshToken)` |
|
|
212
|
+
| `iam.session.revokeRefresh` | refreshToken | `void` | `await iam.session.revokeRefresh(refreshToken)` |
|
|
213
|
+
| `iam.session.patchUserSessions` | userId, updates | `void` | `await iam.session.patchUserSessions(userId, sessionFieldUpdates)` |
|
|
214
|
+
|
|
215
|
+
#### iam.apiKey
|
|
216
|
+
|
|
217
|
+
API 凭据:用户 ID/选项创建时只返回一次 rawKey;列表输入用户/分页返回页,ID 查询返回项/null,撤销返回 void,验证明文返回安全 ApiKey DTO。
|
|
218
|
+
|
|
219
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
220
|
+
| ------------------------- | --------------- | ------------------------- | --------------------------------------------------------------------------------------- |
|
|
221
|
+
| `iam.apiKey.createApiKey` | userId, options | `CreateApiKeyResult` | `await iam.apiKey.createApiKey(userId, { name: 'reporting', scopes: ['orders:read'] })` |
|
|
222
|
+
| `iam.apiKey.listApiKeys` | userId, options | `PaginatedResult<ApiKey>` | `await iam.apiKey.listApiKeys(userId, { page: 1, pageSize: 20 })` |
|
|
223
|
+
| `iam.apiKey.getApiKey` | keyId | `ApiKey \| null` | `await iam.apiKey.getApiKey(keyId)` |
|
|
224
|
+
| `iam.apiKey.revokeApiKey` | keyId | `void` | `await iam.apiKey.revokeApiKey(keyId)` |
|
|
225
|
+
| `iam.apiKey.verifyApiKey` | rawKey | `ApiKey` | `await iam.apiKey.verifyApiKey(rawKey)` |
|
|
226
|
+
|
|
227
|
+
#### iam.ticket
|
|
228
|
+
|
|
229
|
+
一次性用途票据输入主体、用途和有效期签发,返回票据;消费输入票据及用途绑定,返回已消费凭据,不用普通 token 替代操作凭据。
|
|
230
|
+
|
|
231
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
232
|
+
| -------------------- | --------------- | ---------------- | ---------------------------------------------------------- |
|
|
233
|
+
| `iam.ticket.issue` | options | `IssuedTicket` | `await iam.ticket.issue(ticketInput)` |
|
|
234
|
+
| `iam.ticket.consume` | ticket, options | `ConsumedTicket` | `await iam.ticket.consume(operationTicket, ticketBinding)` |
|
|
235
|
+
|
|
236
|
+
#### iam
|
|
237
|
+
|
|
238
|
+
无参数取得前端允许显示的策略;返回 IamPublicConfig,不暴露目录、通知或数据库凭据。
|
|
239
|
+
|
|
240
|
+
| 入口 | 输入参数 | 成功结果 | 调用示例 |
|
|
241
|
+
| --------------------- | -------- | ----------------- | ----------------------------- |
|
|
242
|
+
| `iam.getPublicConfig` | 无参数 | `IamPublicConfig` | `await iam.getPublicConfig()` |
|
|
243
|
+
|
|
41
244
|
### 可选 LDAP 与通知接入
|
|
42
245
|
|
|
43
246
|
只使用密码、用户管理和 RBAC 时,无需安装或初始化 LDAP/Reach。两者均为可选 peer dependency,应用只安装选用的集成。下列发送、LDAP 片段位于应用启动/认证函数中,reldb/cache 必须先成功初始化;`username/password` 来自已校验输入。
|
|
@@ -179,51 +382,6 @@ if (!initialized.success)
|
|
|
179
382
|
return initialized
|
|
180
383
|
```
|
|
181
384
|
|
|
182
|
-
### 快速开始
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
import { cache } from '@h-ai/cache'
|
|
186
|
-
import { core } from '@h-ai/core'
|
|
187
|
-
import { iam } from '@h-ai/iam'
|
|
188
|
-
import { reldb } from '@h-ai/reldb'
|
|
189
|
-
|
|
190
|
-
// 基础密码认证不需要安装 Reach 或 ldapts。
|
|
191
|
-
async function example() {
|
|
192
|
-
try {
|
|
193
|
-
const db = await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
194
|
-
if (!db.success)
|
|
195
|
-
return db
|
|
196
|
-
const cached = await cache.init({ type: 'memory' })
|
|
197
|
-
if (!cached.success)
|
|
198
|
-
return cached
|
|
199
|
-
const initialized = await iam.init({})
|
|
200
|
-
if (!initialized.success)
|
|
201
|
-
return initialized
|
|
202
|
-
const registered = await iam.user.register({ username: 'demo', password: 'Password123' })
|
|
203
|
-
if (!registered.success)
|
|
204
|
-
return registered
|
|
205
|
-
const login = await iam.auth.login({ identifier: 'demo', password: 'Password123' })
|
|
206
|
-
if (!login.success)
|
|
207
|
-
return login
|
|
208
|
-
return await iam.auth.verifyToken(login.data.tokens.accessToken)
|
|
209
|
-
}
|
|
210
|
-
finally {
|
|
211
|
-
// 应用关闭时按依赖的反向顺序释放。
|
|
212
|
-
await iam.close()
|
|
213
|
-
await cache.close()
|
|
214
|
-
await reldb.close()
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const result = await example()
|
|
219
|
-
if (!result.success)
|
|
220
|
-
core.logger.error('Authentication example failed', { code: result.error.code })
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
默认 LDAP 关闭;OTP 需要 `otp` 配置和可用发送回调,重置需要可用通知回调,才能在 `iam.publicConfig` 中启用。
|
|
224
|
-
|
|
225
|
-
`iam.config` 返回的是**脱敏后的配置快照**:LDAP `url` / `bindPassword` 等敏感配置会自动隐藏,便于状态检查但不能代替原始配置源。
|
|
226
|
-
|
|
227
385
|
### 设计说明
|
|
228
386
|
|
|
229
387
|
#### 架构概览
|
|
@@ -852,7 +1010,7 @@ SvelteKit 使用 `kit.auth.createApi(iam)` 导出标准 handler,匿名端点
|
|
|
852
1010
|
|
|
853
1011
|
管理员使用 `iam.user.createUser()` 建用户,调用端必须校验管理员权限;关闭自助注册不影响管理端。注册与密码登录开关独立,`registerAndLogin` 在注册完成后创建会话;默认休眠账号不会创建会话;`defaultAccountStatus` 默认 active,可设为 dormant 等待管理员激活。
|
|
854
1012
|
|
|
855
|
-
协议在 Modal 中打开,关闭后保留表单输入。使用 URL 时,协议页面须允许 iframe 嵌入;禁止脚本执行,并提供新窗口查看入口。`LoginForm/RegisterForm` 提供 `header/extraFields/footer` snippet,`AuthPanel` 透传 `loginHeader/loginFields/loginFooter` 和 `registerHeader/registerFields/registerFooter`;自定义字段由应用绑定状态并在动作中处理。示例见 [UI 用法](../ui/README.md#配置驱动认证页面)
|
|
1013
|
+
协议在 Modal 中打开,关闭后保留表单输入。使用 URL 时,协议页面须允许 iframe 嵌入;禁止脚本执行,并提供新窗口查看入口。`LoginForm/RegisterForm` 提供 `header/extraFields/footer` snippet,`AuthPanel` 透传 `loginHeader/loginFields/loginFooter` 和 `registerHeader/registerFields/registerFooter`;自定义字段由应用绑定状态并在动作中处理。示例见 [UI 用法](../ui/README.md#配置驱动认证页面).
|
|
856
1014
|
|
|
857
1015
|
#### Markdown 协议内容
|
|
858
1016
|
|
|
@@ -885,32 +1043,13 @@ agreements:
|
|
|
885
1043
|
|
|
886
1044
|
标准 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)`。
|
|
887
1045
|
|
|
888
|
-
### 错误处理
|
|
889
|
-
|
|
890
|
-
所有操作返回 `HaiResult<T>`,通过 `HaiIamError` 做分支判断:
|
|
891
|
-
|
|
892
|
-
```ts
|
|
893
|
-
import { HaiIamError, iam } from '@h-ai/iam'
|
|
894
|
-
|
|
895
|
-
const result = await iam.auth.login({ identifier: 'admin', password: 'wrong' })
|
|
896
|
-
if (!result.success) {
|
|
897
|
-
if (result.error.code === HaiIamError.INVALID_CREDENTIALS.code) {
|
|
898
|
-
// 用户名或密码错误
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
会话与 refresh 消费记录统一保存在 `hai:iam:sessions:{SHA256(userId)}` 版本化文档,不再使用独立 token 映射/索引。Bearer token 携带不可作为凭据的分区提示和 256 位随机值;客户端必须视为不透明字符串。`Session.version` 用于轮换提交校验。Memory 仅用于单进程测试,生产所有节点共享 Redis(含 Cluster)。并发冲突从最新文档重试,耗尽返回 CONFLICT;旧 refresh 不会产生第二个后继。若响应丢失后结果不明,调用方按失败关闭并重新登录,不假定旧凭据仍有效;全部会话仍受同一用户状态索引管理。
|
|
904
|
-
|
|
905
|
-
`iam.session.update(token, patch, expectedVersion)` 必须传入读取到的 Session.version。旧版本返回 CONFLICT,已撤销 token 返回 SESSION_NOT_FOUND;不会将旧内容重新写回或覆盖新会话。仅滑动时间戳更新不改变内容版本,允许正常并行请求。批量权限更新绑定首次读取的 token/版本集合,不将迟到数据应用到后续登录;遇到较新内容版本失败,调用方重新查询授权后再提交。
|
|
906
|
-
|
|
907
1046
|
### 共享鉴权缓存
|
|
908
1047
|
|
|
909
1048
|
验签所需账号/凭据状态、角色权限、敏感操作和已验证 API Key 使用 Redis 共享缓存(60 秒 DTO TTL),缓存值不保存数据库中的密码/API Key 验证哈希、明文密钥或重置令牌;API Key 缓存地址使用明文 Key 的单向 SHA-256 指纹;账号/密码/Key 到期时间每次按当前时间核验。权限冷读使用单次 JOIN。所有 IAM 写入口在数据库变更前建立共享脏标记,提交/回滚后推进代际,外部事务同样等到完成才允许回填;冷读与代际变化并发时不发布旧缓存。正常热路径不查 PG,写入中、失效未确认或缓存读失败时回源;会话 Redis 本身不可用仍拒绝验签。
|
|
910
1049
|
|
|
911
1050
|
写前无法登记失效时拒绝变更;已提交后失效失败不会伪装回滚,持久脏标记使后续授权继续读数据库。进程崩溃或提交结果未知时不通过 TTL 擅自清除此标记;应告警并在确认没有 IAM 写事务的维护窗口修复缓存状态。多节点必须同时升级并通过 IAM API 写权限/账号;直接 SQL、旧版本写节点或独立系统改表不触发缓存失效,需停写并使 IAM 缓存整体失效后再恢复,不能与在线缓存写入混用。API Key 明文验证命中缓存后避免重复昂贵哈希,lastUsedAt 在冷验证时采样更新(最多约一分钟延迟)。
|
|
912
1051
|
|
|
913
|
-
|
|
1052
|
+
### 自托管交互挑战
|
|
914
1053
|
|
|
915
1054
|
`captcha.enabled` 默认关闭。开启后使用 ALTCHA 自托管计算挑战,配置 `ttlSeconds`(默认120,1–600秒)、`cost`(默认5000,100–50000)和 `maxCounter`(默认100,1–2000)控制期限与工作量。不需要外部验证码账号,HMAC 秘密与单次消费状态只保存在既有共享 Cache;生产仍须使用共享后端。计算挑战增加自动化尝试成本,不保证请求来自真人。
|
|
916
1055
|
|
|
@@ -918,7 +1057,7 @@ if (!result.success) {
|
|
|
918
1057
|
|
|
919
1058
|
标准 Kit/Serv 暴露 POST /auth/captcha(Kit 同源前缀为 /api),并保留 captcha 字段。`iam.publicConfig.captcha` 只公开 enabled。框架 AuthPanel 与 createAuthActions 在启用后提交时自动生成、异步求解并提交新证明,关闭时不加载求解器;自定义表单可复用同一协议。UI 配置启用但缺少 createCaptcha 动作时拒绝提交,取消/卸载会停止尚未提交的鉴别请求。captcha 证明按凭据规则始终脱敏,不写浏览器持久存储。030/031/032/033 分别表示需要验证、证明无效/已用/过期、未启用、服务不可用。
|
|
920
1059
|
|
|
921
|
-
|
|
1060
|
+
### 可选密码与登录保护
|
|
922
1061
|
|
|
923
1062
|
公开登录与 `renewPassword` 对不存在、无本地口令、休眠、注销、到期及锁定账号统一返回 `INVALID_CREDENTIALS`(“用户名或密码不正确”)。受限账号不继续校验猜测的密码,正确与错误密码得到相同反馈且不改变原有锁定计数;已认证管理入口仍可查看具体状态。密码计算或存储故障保留为基础设施错误,不能计为用户输错密码。
|
|
924
1063
|
|
|
@@ -936,27 +1075,27 @@ if (!result.success) {
|
|
|
936
1075
|
|
|
937
1076
|
`reauthentication.enabled/operations` 选择敏感操作,`ttlSeconds` 为证明期限。`iam.auth.reauthenticate(token, password, operation)` 验证原密码并签发单次票据;操作入口完成权限/资源检查后用 `checkSensitiveOperation(token, operation, ticket)` 消费。证明绑定主体、当前会话和操作,不提升权限。Serv 标准 IAM 写操作与 admin 守卫读取 `x-hai-reauthentication`;Kit 业务入口用 `kit.guard.requireSensitive(iam, event, operation)`,业务操作 ID 由项目定义。票据只短暂保存在内存,公开配置仅暴露开关/操作清单。admin/fullstack 标准界面在已登录用户点击敏感动作时弹出当前账号密码验证窗口;取消不提交、密码错误不继续,正确后才执行原操作,不创建另一个登录会话。
|
|
938
1077
|
|
|
939
|
-
RBAC 权限支持 `sensitiveOperations: string[]`(持久字段 `sensitive_operations`)。可信管理端可调用 `iam.authz.setPermissionSensitiveOperations(permissionId, operations)`;检查结果的 `success`,空列表仅移除该权限的附加规则。开启 `reauthentication.enabled` 后,所有权限记录的规则与文件 `operations` 取并集,服务端实时执行。规则 ID 必须对应实际业务守卫,不能替代原权限检查。异步 `iam.getPublicConfig()` 返回合并后的公开策略,DB 读取失败返回失败;同步 `iam.publicConfig` 只保留文件快照。RBAC
|
|
1078
|
+
RBAC 权限支持 `sensitiveOperations: string[]`(持久字段 `sensitive_operations`)。可信管理端可调用 `iam.authz.setPermissionSensitiveOperations(permissionId, operations)`;检查结果的 `success`,空列表仅移除该权限的附加规则。开启 `reauthentication.enabled` 后,所有权限记录的规则与文件 `operations` 取并集,服务端实时执行。规则 ID 必须对应实际业务守卫,不能替代原权限检查。异步 `iam.getPublicConfig()` 返回合并后的公开策略,DB 读取失败返回失败;同步 `iam.publicConfig` 只保留文件快照。RBAC 关闭时忽略数据库规则。
|
|
940
1079
|
|
|
941
|
-
|
|
1080
|
+
### 账号类型和生命周期
|
|
942
1081
|
|
|
943
1082
|
用户默认是 `accountType: 'permanent'`、`accountStatus: 'active'`。管理入口可创建 `temporary` 账号并提供 `accountExpiresAt`,或通过 `updateUser` 将账号转为 `dormant`、`closed`。状态与凭据代际在同一事务更新;账号到期或非激活时,所有登录方式、旧 access 和 refresh 均被拒绝。管理员明确恢复为 `active` 时仍需重新登录,原 token 不会复活;账号仅使用 `accountStatus` 表达生命周期,不再提供独立的启用/停用字段。`closed` 保留账号记录,物理删除仍由 `deleteUser` 完成,项目决定个人数据的保留与注销流程。
|
|
944
1083
|
|
|
945
|
-
|
|
1084
|
+
管理员可重置任何 accountStatus 的账号密码,但重置不会把 dormant/closed 改成 active,也不会恢复此前失效的凭据;是否要求后续首次改密由 `password.requireInitialChange` 决定。账号列表的筛选与排序使用同一 `accountStatus` 字段。
|
|
946
1085
|
|
|
947
1086
|
`accounts.dormantAfterDays` 默认 `0`,关闭自动休眠。设为 `90` 时,IAM 每隔 `accounts.scanIntervalSeconds`(默认3600秒)扫描最近90天没有成功登录的激活账号,未登录账号从创建时间起算。`lastLoginAt` 只在成功鉴别时更新。扫描是数据库条件更新,多节点重复执行不会重复变更;`iam.user.scanInactiveAccounts()` 也可由可信后台或既有调度器调用。`close()` 停止定时器并等待本轮扫描完成。公开注册接口不应允许用户自行设置这些管理字段。
|
|
948
1087
|
|
|
949
|
-
|
|
1088
|
+
### 可选会话安全策略
|
|
950
1089
|
|
|
951
1090
|
在 `session` 中设置 `idleTimeout: 1800` 可要求30分钟没有成功认证请求后重新登录;`0` 关闭。该策略在 access 读取、活动提交和 refresh 轮换时检查,不能通过长期 refresh 恢复已闲置的会话。自动 refresh 只换票,不延长闲置期限;后台业务轮询仍会产生认证活动,应用应按实际用户操作设计轮询行为。
|
|
952
1091
|
|
|
953
1092
|
`absoluteTimeout` 是从首次登录起的秒数上限,刷新不重置起点,`0` 关闭。`maxSessions` 限制每个用户的有效会话数,包含 access 已过期但仍可刷新的记录,`0` 不限制;并发登录在同一共享状态 CAS 中检查配额。`singleDevice: true` 默认顶旧保新;配合 `singleDeviceMode: 'reject'` 可保留旧会话并拒绝重复登录。显式退出撤销 access 和 refresh,关闭浏览器不能替代服务端期限。
|
|
954
1093
|
|
|
955
|
-
未启用上述期限时,保留 access 与 refresh 独立到期的行为。每次成功登录始终生成新随机凭据,不复用客户端会话ID
|
|
1094
|
+
未启用上述期限时,保留 access 与 refresh 独立到期的行为。每次成功登录始终生成新随机凭据,不复用客户端会话ID。`SESSION_LIMIT_EXCEEDED` 表示登录达到已配置的会话上限。
|
|
956
1095
|
|
|
957
|
-
|
|
1096
|
+
### 持久结构与凭据通知
|
|
958
1097
|
|
|
959
|
-
`migrateIam({ dryRun: true })` 在 RelDB 初始化后只读预检;iam.init
|
|
1098
|
+
`migrateIam({ dryRun: true })` 在 RelDB 初始化后只读预检;iam.init 准备模块持久结构和凭据/重置字段,保留用户、密码与业务权限。结构变更前备份并停止冲突写入;会话必须匹配当前安全代际,不匹配时重新登录,不能降低数据库安全代际来恢复授权。默认 Reach 通知使用 sensitive/expiresAt/idempotencyKey,不落明文、不延迟重投;自定义回调同样须遵守有效期与凭据保护。
|
|
960
1099
|
|
|
961
1100
|
审计模块已初始化且 capture.enabled 开启时,IAM 自动记录关键账号、鉴别、密码、授权、API Key 和会话事件,包含失败/拒绝与安全原因码。先初始化 audit 再初始化 IAM,关闭时反序。不会记录密码、一次性票据或令牌;成功登录才确认主体,可信服务/CLI 调用应以 audit.withContext 提供操作者和关联信息。外部授权事务的事件随该事务提交或回滚,回滚失败尝试由调用入口在事务结束后记录。审计写失败输出诊断,不把已生效业务改成可盲目重试的失败。
|
|
962
1101
|
|
|
@@ -964,15 +1103,44 @@ RBAC 权限支持 `sensitiveOperations: string[]`(持久字段 `sensitive_oper
|
|
|
964
1103
|
|
|
965
1104
|
Web 原地恢复使用 UI 的 `createSessionRecovery` / `SessionRecoveryDialog` 与 SDK 的 `recoverSession`,复用正常登录策略。`expectedUserId` 在密码、OTP、LDAP 登录签发会话前限制原主体;验证失败不签发恢复会话。仅重发明确未认证拒绝的一笔请求,网络结果不明时不重发。页面刷新/关闭后的内存草稿不在恢复范围内。
|
|
966
1105
|
|
|
967
|
-
|
|
1106
|
+
### 页面离开会话策略
|
|
968
1107
|
|
|
969
1108
|
`session.endOnPageHide` 默认 `false`。启用后宿主调用 `actions.bindPageExit({ enabled: config.session.endOnPageHide })` 并在卸载时调用返回的清理函数;标准 admin/H5/fullstack 模板已经接入。非 BFCache 的 `pagehide`(也包括刷新、整页导航)通过 POST `/auth/session/close` 尽力撤销当前 access 及关联 refresh,沿用 CSRF/传输加密,使用 `keepalive` 且不自动刷新会话。该响应不清 Cookie/本地凭据,避免旧响应覆盖下一次登录。显式退出仍使用正常 logout,可靠撤销并清理当前凭据。浏览器可能不发送页面离开请求,因此必须按业务配合服务端 `idleTimeout`/`absoluteTimeout`;不能据此保证关闭即撤销。
|
|
970
1109
|
|
|
971
1110
|
标准 `/auth/me` 也检查 `iam.profile.update` / `iam.profile.avatar`;混合修改优先选取已启用的资料用途,否则使用头像用途。`authz.getSensitiveOperations(operation?)` 可只查询一个操作;未传则列出全部有效数据库规则。超管身份每次从共享角色读取,默认种子允许并发创建冲突后重读,并传播权限分配失败。
|
|
972
1111
|
|
|
1112
|
+
## 配置
|
|
1113
|
+
|
|
1114
|
+
生命周期:init 输入后端/业务配置,成功后才能使用;close 在释放借用依赖前等待完成。返回值以快速开始示例和公开签名为准。
|
|
1115
|
+
|
|
1116
|
+
配置由宿主加载和校验,再传入本模块;私密值放服务端私有环境,不传到浏览器。按实际使用场景逐项选择后端、容量、期限和日志策略,修改后重新初始化并核验成功结果。
|
|
1117
|
+
|
|
1118
|
+
[逐项配置说明、适用场景和示例](../../docs/usage/configuration/identity.md).
|
|
1119
|
+
|
|
1120
|
+
## 错误处理
|
|
1121
|
+
|
|
1122
|
+
领域操作先判断 `result.success`,失败按本模块错误定义与安全提示处理,服务端保留错误码、关联编号和脱敏原因。同步工具、工厂、流迭代及生命周期按各自签名处理,不把 Promise 完成视为业务成功。具体失败与恢复边界见对应功能。
|
|
1123
|
+
|
|
1124
|
+
所有操作返回 `HaiResult<T>`,通过 `HaiIamError` 做分支判断:
|
|
1125
|
+
|
|
1126
|
+
```ts
|
|
1127
|
+
import { HaiIamError, iam } from '@h-ai/iam'
|
|
1128
|
+
|
|
1129
|
+
const result = await iam.auth.login({ identifier: 'admin', password: 'wrong' })
|
|
1130
|
+
if (!result.success) {
|
|
1131
|
+
if (result.error.code === HaiIamError.INVALID_CREDENTIALS.code) {
|
|
1132
|
+
// 用户名或密码错误
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
会话与 refresh 消费记录统一保存在 `hai:iam:sessions:{SHA256(userId)}` 版本化文档,不再使用独立 token 映射/索引。Bearer token 携带不可作为凭据的分区提示和 256 位随机值;客户端必须视为不透明字符串。`Session.version` 用于轮换提交校验。Memory 仅用于单进程测试,生产所有节点共享 Redis(含 Cluster)。并发冲突从最新文档重试,耗尽返回 CONFLICT;旧 refresh 不会产生第二个后继。若响应丢失后结果不明,调用方按失败关闭并重新登录,不假定旧凭据仍有效;全部会话仍受同一用户状态索引管理。
|
|
1138
|
+
|
|
1139
|
+
`iam.session.update(token, patch, expectedVersion)` 必须传入读取到的 Session.version。旧版本返回 CONFLICT,已撤销 token 返回 SESSION_NOT_FOUND;不会将旧内容重新写回或覆盖新会话。仅滑动时间戳更新不改变内容版本,允许正常并行请求。批量权限更新绑定首次读取的 token/版本集合,不将迟到数据应用到后续登录;遇到较新内容版本失败,调用方重新查询授权后再提交。
|
|
1140
|
+
|
|
973
1141
|
## 开发测试
|
|
974
1142
|
|
|
975
|
-
公开示例从 `@h-ai/iam` 导入,区分可省略默认值的 `IamConfigInput` 与 Schema 解析后的 `IamConfig`。服务端依赖先初始化;`HaiResult` 先检查 `success`,会话修改使用读取到的 `version`,通知复用持久幂等键。内部策略/仓储示例标记为“模块内部上下文”,不作为应用的深层导入入口。检查器只验证示例类型;真实 Redis/数据库、LDAP 和通知投递仍需对应集成测试。见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)
|
|
1143
|
+
公开示例从 `@h-ai/iam` 导入,区分可省略默认值的 `IamConfigInput` 与 Schema 解析后的 `IamConfig`。服务端依赖先初始化;`HaiResult` 先检查 `success`,会话修改使用读取到的 `version`,通知复用持久幂等键。内部策略/仓储示例标记为“模块内部上下文”,不作为应用的深层导入入口。检查器只验证示例类型;真实 Redis/数据库、LDAP 和通知投递仍需对应集成测试。见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证).
|
|
976
1144
|
|
|
977
1145
|
```bash
|
|
978
1146
|
pnpm --filter @h-ai/iam typecheck
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/iam",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.20",
|
|
5
5
|
"description": "Hai Framework identity and access management module.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": "^22.23.4 || ^24.22.1 || ^26.12.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@h-ai/reach": "1.0.0-beta.
|
|
31
|
+
"@h-ai/reach": "1.0.0-beta.20",
|
|
32
32
|
"ldapts": "^9.2.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependenciesMeta": {
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@h-ai/audit": "1.0.0-beta.
|
|
44
|
-
"@h-ai/cache": "1.0.0-beta.
|
|
45
|
-
"@h-ai/core": "1.0.0-beta.
|
|
46
|
-
"@h-ai/crypto": "1.0.0-beta.
|
|
47
|
-
"@h-ai/reldb": "1.0.0-beta.
|
|
43
|
+
"@h-ai/audit": "1.0.0-beta.20",
|
|
44
|
+
"@h-ai/cache": "1.0.0-beta.20",
|
|
45
|
+
"@h-ai/core": "1.0.0-beta.20",
|
|
46
|
+
"@h-ai/crypto": "1.0.0-beta.20",
|
|
47
|
+
"@h-ai/reldb": "1.0.0-beta.20",
|
|
48
48
|
"altcha": "^3.5.0",
|
|
49
49
|
"zod": "^4.6.7"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@h-ai/reach": "1.0.0-beta.
|
|
52
|
+
"@h-ai/reach": "1.0.0-beta.20",
|
|
53
53
|
"@types/node": "^26.6.6",
|
|
54
54
|
"ldapts": "^9.2.0",
|
|
55
55
|
"rimraf": "^6.1.4",
|