@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.
Files changed (4) hide show
  1. package/README.en.md +253 -85
  2. package/README.ja.md +256 -88
  3. package/README.md +259 -91
  4. package/package.json +8 -8
package/README.ja.md CHANGED
@@ -4,23 +4,9 @@
4
4
 
5
5
  [フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/audit](../audit/README.ja.md) · [@h-ai/cache](../cache/README.ja.md) · [@h-ai/core](../core/README.ja.md) · [@h-ai/crypto](../crypto/README.ja.md) · [@h-ai/reldb](../reldb/README.ja.md) · [@h-ai/reach](../reach/README.ja.md)
6
6
 
7
- ## 概要
8
-
9
7
  共通の `iam` オブジェクトで認証、セッション、認可、ユーザー管理を提供する Identity and Access Management モジュールです。
10
8
 
11
- ## 主な機能
12
-
13
- | 機能 | 説明 |
14
- | ------------------------------ | ----------------------------------------------------------------------------------------- |
15
- | **認証** | パスワード、OTP(メール・SMS コード)、LDAP、API Key の認証方式 |
16
- | **セッション** | 状態を持つセッション(ランダム access token + キャッシュ、任意の自動延長) |
17
- | **認可** | RBAC のロール・権限管理(DB 一括検索、ワイルドカード照合) |
18
- | **ユーザー管理** | 登録、検索、更新、パスワードリセット、管理者によるリセット |
19
- | **API Key** | scope・期限付き API Key の作成、失効、検証 |
20
- | **一度限りのチケット** | Bearer Token を付けられない WebSocket などのための短期チケット(発行 / アトミックな消費) |
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`:**任意**。アプリがインストール、初期化、注入して通知テンプレートを使用します。独自送信コールバックでは不要です。
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` 設定と送信コールバック、パスワードリセットは通知コールバックが必要です。
67
+
68
+ `iam.config` は**機密情報をマスクした設定スナップショット**を返します。LDAP の `url` / `bindPassword` などは自動的に非表示になります。状態確認用で、元の設定の代わりにはなりません。
69
+
70
+ ## 機能
71
+
72
+ | 機能 | 説明 |
73
+ | ------------------------------ | ----------------------------------------------------------------------------------------- |
74
+ | **認証** | パスワード、OTP(メール・SMS コード)、LDAP、API Key の認証方式 |
75
+ | **セッション** | 状態を持つセッション(ランダム access token + キャッシュ、任意の自動延長) |
76
+ | **認可** | RBAC のロール・権限管理(DB 一括検索、ワイルドカード照合) |
77
+ | **ユーザー管理** | 登録、検索、更新、パスワードリセット、管理者によるリセット |
78
+ | **API Key** | scope・期限付き API Key の作成、失効、検証 |
79
+ | **一度限りのチケット** | Bearer Token を付けられない WebSocket などのための短期チケット(発行 / アトミックな消費) |
80
+ | **フロントエンドクライアント** | 独立フロントエンド向けの HTTP API 契約 |
81
+
82
+ ### 操作と入出力
83
+
84
+ 本節の完全な初期化手順を先に実行します。例の業務値はホストが検証・認可し、非同期領域結果は success を確認してから data を読みます。void はデータなし完了、null は不存在、同期ファクトリー/ツールは表の型を直接返します。
85
+
86
+ ID/code は認可済み業務から取得。トークン/票は利用者 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/配列・権限 code から役割/権限・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 get は Session/null・verify は Session。更新は expectedVersion 必須、利用者削除は件数、refresh は 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 資格情報。ユーザー/指定で作成時のみ 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 を取得。ディレクトリー・通知・DB 資格情報は公開しない。
239
+
240
+ | 入口 | 入力引数 | 成功結果 | 呼出し例 |
241
+ | --------------------- | -------- | ----------------- | ----------------------------- |
242
+ | `iam.getPublicConfig` | 引数なし | `IamPublicConfig` | `await iam.getPublicConfig()` |
243
+
41
244
  ### 任意の LDAP と通知連携
42
245
 
43
246
  パスワード認証、ユーザー管理、RBAC は LDAP/Reach なしで動作します。両方とも任意の peer dependency で、必要な連携だけをインストールします。以下は 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` 設定と送信コールバック、パスワードリセットは通知コールバックが必要です。
224
-
225
- `iam.config` は**機密情報をマスクした設定スナップショット**を返します。LDAP の `url` / `bindPassword` などは自動的に非表示になります。状態確認用で、元の設定の代わりにはなりません。
226
-
227
385
  ### 設計
228
386
 
229
387
  #### アーキテクチャ
@@ -872,32 +1030,13 @@ agreements:
872
1030
  # privacyPolicyUrl: https://example.com/privacy
873
1031
  ```
874
1032
 
875
- ### エラー処理
876
-
877
- すべての操作は `HaiResult<T>` を返し、`HaiIamError` で分岐します:
878
-
879
- ```ts
880
- import { HaiIamError, iam } from '@h-ai/iam'
881
-
882
- const result = await iam.auth.login({ identifier: 'admin', password: 'wrong' })
883
- if (!result.success) {
884
- if (result.error.code === HaiIamError.INVALID_CREDENTIALS.code) {
885
- // 用户名或密码错误
886
- }
887
- }
888
- ```
889
-
890
- 会話と refresh 消費状態を `hai:iam:sessions:{SHA256(userId)}` のバージョン付き文書にまとめ、独立した対応キーと索引を廃止します。Bearer Token は認証には使えない分区ヒントと256ビットの乱数を含み、クライアントは不透明な文字列として扱います。更新時は Session.version を再検証します。Memory は単一プロセスのテスト用で、本番は全ノードが Redis(Cluster 対応)を共有します。競合は最新状態から再試行し、上限で CONFLICT を返します。応答喪失で結果が不明なら認証を拒否し再ログインします。すべての会話は同じユーザー索引で管理されます。
891
-
892
- `iam.session.update(token, patch, expectedVersion)` は読み取った Session.version が必須です。旧版は CONFLICT、失効 Token は SESSION_NOT_FOUND を返し、新しい状態を上書きしません。自動延長の時刻だけの更新は内容版を変更せず、通常の並行リクエストを許容します。権限一括更新は最初の Token/版集合に限定し、後続ログインに古い値を適用しません。
893
-
894
1033
  ### 共有認可キャッシュ
895
1034
 
896
1035
  Redisは秘密を含まないアカウント/資格情報の状態、ロール・権限、機密操作、検証済みAPI Key DTOを60秒保存します。キャッシュ値にはDBのパスワード/Key検証ハッシュ、平文Key、再設定トークンを保存しません。API Keyのキャッシュアドレスには平文Keyの一方向SHA-256指紋を使います。期限は毎回現在時刻で検証し、認可のコールド読込は単一JOINです。IAM書込はDB更新前に共有dirtyマーカーを登録し、外部トランザクションもcommit/rollback完了後に世代を更新します。競合したコールド読込は古い値を公開しません。通常の高頻度チェックはPGを照会せず、更新中・無効化未確認・キャッシュ読込障害時はDBを参照します。セッションRedis自体の障害は認証を拒否します。
897
1036
 
898
1037
  無効化登録に失敗した書込はDB更新前に拒否します。commit後の無効化失敗は成功済み更新を取り消した扱いにせず、永続dirtyマーカーでDB参照を継続します。プロセスクラッシュやcommit結果不明をTTLで解除せず、IAM書込がない保守時間帯に確認して修復してください。全ノード同時更新とIAM API経由の書込が必要です。直接SQL、旧版ノード、外部テーブル更新は書込停止とIAMキャッシュ全体の無効化が必要です。平文Key検証のヒットは高負荷ハッシュを省略し、lastUsedAtはコールド検証時に記録するため最大約1分遅れます。
899
1038
 
900
- ## 自己ホスト型の対話チャレンジ
1039
+ ### 自己ホスト型の対話チャレンジ
901
1040
 
902
1041
  captcha.enabled は既定で false。ALTCHA の計算チャレンジを自己ホストし、ttlSeconds(既定120、1–600秒)、cost(既定5000、100–50000)、maxCounter(既定100、1–2000)で期限と計算量を指定します。外部 CAPTCHA アカウントは不要で、HMAC 秘密と一度限りの状態は既存の共有 Cache にのみ保存します。本番では共有バックエンドが必要です。計算量は自動化のコストを増やしますが、利用者が人間であることは保証しません。
903
1042
 
@@ -905,7 +1044,7 @@ iam.auth.createCaptcha({operation}) は {id,challenge} を返します。operati
905
1044
 
906
1045
  Kit/Serv の標準入口は POST /auth/captcha(Kit は同一生成元の /api 接頭辞)。captcha を保持し、iam.publicConfig.captcha は enabled のみ公開します。AuthPanel/createAuthActions は有効時に送信操作で自動生成・非同期求解し、無効時には求解器を読み込みません。独自フォームも同じ手順を使えます。有効設定で createCaptcha がなければ送信を拒否し、取消/破棄後にログインしません。証明は資格情報として常に秘匿し、ブラウザーに永続保存しません。030/031/032/033 は確認必須・無効/使用済/期限切れ・無効設定・一時利用不可です。
907
1046
 
908
- ## 任意のパスワード・ログイン保護
1047
+ ### 任意のパスワード・ログイン保護
909
1048
 
910
1049
  公開ログインと `renewPassword` は、存在しない・パスワード未設定・休止・終了・期限切れ・ロック中のアカウントに `INVALID_CREDENTIALS`(「ユーザー名またはパスワードが正しくありません。」)を返します。制限中はパスワードの推測を照合せず、正誤によらず同じ応答とし、既存の失敗回数を変更しません。認証済み管理画面では具体的な状態を確認できます。パスワード計算や保存の障害は基盤エラーとして扱い、入力失敗として加算しません。
911
1050
 
@@ -923,17 +1062,17 @@ Kit/Serv の標準入口は POST /auth/captcha(Kit は同一生成元の /api
923
1062
 
924
1063
  `reauthentication.enabled/operations` で対象操作、`ttlSeconds` で証明期限を選びます。`reauthenticate(token, password, operation)` は単回証明を発行し、入口は権限・資源検証後に `checkSensitiveOperation(token, operation, ticket)` を呼びます。主体・現在セッション・操作に結びつけ、権限を増やしません。Serv IAM 更新操作と admin 守衛は `x-hai-reauthentication`、Kit は固有 ID と `kit.guard.requireSensitive(iam, event, operation)` を使います。証明は短時間メモリに保持し、公開設定は開閉・操作リストのみです。admin/fullstack標準UIでは、ログイン済み利用者が機密操作を選ぶと、現在のアカウントのパスワード確認ダイアログを表示します。取消時は送信せず、誤りなら操作を止め、成功時だけ元の操作を続けます。別のログインセッションは作成しません。
925
1064
 
926
- RBAC 権限は `sensitiveOperations: string[]` を持ち、可空 JSON 列 `sensitive_operations` に保存します。管理側は `iam.authz.setPermissionSensitiveOperations(permissionId, operations)` の `success` を確認します。空配列はその権限の追加規則だけを削除します。`reauthentication.enabled` が有効なら全権限の規則とファイルの `operations` を和集合にして即時検証します。ID は実際の業務ガードと一致させ、既存の認可を省略しません。非同期 `iam.getPublicConfig()` は統合した公開方針を返し、DB 読取障害時は失敗します。同期 `iam.publicConfig` はファイルのスナップショットのみです。RBAC 無効時は DB 規則を無視し、移行は既存の認可関係を保持します。
1065
+ RBAC 権限は `sensitiveOperations: string[]` を持ち、可空 JSON 列 `sensitive_operations` に保存します。管理側は `iam.authz.setPermissionSensitiveOperations(permissionId, operations)` の `success` を確認します。空配列はその権限の追加規則だけを削除します。`reauthentication.enabled` が有効なら全権限の規則とファイルの `operations` を和集合にして即時検証します。ID は実際の業務ガードと一致させ、既存の認可を省略しません。非同期 `iam.getPublicConfig()` は統合した公開方針を返し、DB 読取障害時は失敗します。同期 `iam.publicConfig` はファイルのスナップショットのみです。RBAC 無効時は DB 規則を無視します。
927
1066
 
928
- ## アカウント種別とライフサイクル
1067
+ ### アカウント種別とライフサイクル
929
1068
 
930
1069
  既定は `accountType: 'permanent'` と `accountStatus: 'active'` です。管理側は `accountExpiresAt` を指定して `temporary` アカウントを作成し、`updateUser` で `dormant` または `closed` に変更できます。状態と認証世代を同じトランザクションで更新し、期限切れまたは非アクティブなアカウントはすべてのログイン、access 検証、refresh で拒否します。管理者が明示的に再有効化しても旧トークンは復活せず、再ログインが必要です。`accountStatus` だけでライフサイクルを表し、独立した有効/無効スイッチはありません。`closed` は記録を保持し、物理削除は `deleteUser` が行います。個人データの保持と退会手順はアプリが決定します。
931
1070
 
932
- beta.1 への更新前に旧バージョンの書き込みを停止し、DB をバックアップしてください。移行 v6 は旧 enabled が false/NULL で active のアカウントを dormant に変更し、既存の dormant/closed を保持したうえで列を削除します。影響する資格情報も無効になります。入力、絞り込み、並び替えは accountStatus に統一します。旧イメージへの切り戻しだけでは DB スキーマは戻らず、検証済みバックアップの復元が必要です。権限のある管理者はどの状態のパスワードもリセットできますが、状態は変わりません。初回変更の要否は password.requireInitialChange で決まります。
1071
+ 権限のある管理者はどの accountStatus でもパスワードをリセットできますが、dormant/closed を active に戻さず、失効した資格情報も復活しません。次回初回変更は `password.requireInitialChange` に従い、一覧の絞込・並替も同じ accountStatus を使います。
933
1072
 
934
1073
  `accounts.dormantAfterDays` の既定値 `0` は自動休止を無効にします。`90` にすると、最後のログイン成功から90日間使用していない有効なアカウントを `accounts.scanIntervalSeconds` ごと(既定3600秒)に確認します。未使用のアカウントは作成時刻から計算し、成功した認証だけが `lastLoginAt` を更新します。条件付きSQLのため複数ノードで繰り返しても重複変更しません。信頼された処理や既存スケジューラーから `iam.user.scanInactiveAccounts()` を呼び出すこともできます。`close()` はタイマーを停止して実行中の処理を待ちます。公開登録APIに管理用の項目を公開しないでください。
935
1074
 
936
- ## 任意のセッションセキュリティ設定
1075
+ ### 任意のセッションセキュリティ設定
937
1076
 
938
1077
  `session.idleTimeout: 1800` は認証済みリクエストが30分間ない場合に再ログインを要求します。`0` は無効です。access の読み取り、活動の保存、refresh の更新で同じ期限を確認し、長期 refresh でも休止したセッションを復活できません。自動 refresh は休止期限を延長しません。バックグラウンド業務のポーリングは認証活動になるため、アプリは利用者の操作方針に合わせて設計してください。
939
1078
 
@@ -941,9 +1080,9 @@ beta.1 への更新前に旧バージョンの書き込みを停止し、DB を
941
1080
 
942
1081
  期限を無効にした場合、access と refresh は独立した有効期間を保ちます。ログイン成功ごとに新しいランダムな認証情報を生成します。`SESSION_LIMIT_EXCEEDED` は設定されたセッション数の上限を示します。
943
1082
 
944
- ## スキーマと認証情報の通知
1083
+ ### スキーマと認証情報の通知
945
1084
 
946
- RelDB 初期化後の `migrateIam({ dryRun: true })` は読み取り専用です。iam.init は対応する認証・リセット列を補い、ユーザー、パスワードと権限を保持します。事前に旧書込を停止しバックアップしてください。安全世代のない旧セッションは再ログインが必要で、認可復元のため世代を戻してはいけません。既定 Reach 通知は sensitive/expiresAt/idempotencyKey で平文保存や遅延再送を防ぎます。独自コールバックも同じ期限と保護を守ってください。
1085
+ RelDB 初期化後の `migrateIam({ dryRun: true })` は読み取り専用です。iam.init は永続構造と認証・リセット項目を準備し、利用者・パスワード・権限を保持します。構造変更前に競合書込を停止しバックアップしてください。セッションは現在の資格世代と一致する必要があり、不一致なら再ログインします。認可復元のため世代を戻してはいけません。既定 Reach 通知は sensitive/expiresAt/idempotencyKey で平文保存や遅延再送を防ぎます。独自コールバックも同じ期限と保護を守ってください。
947
1086
 
948
1087
  audit が初期化済みで capture.enabled が有効なら、IAM はアカウント・認証・パスワード・権限・API Key・セッションの重要イベントと失敗/拒否の安全な理由コードを記録します。audit を IAM より先に初期化し、逆順に終了してください。パスワード・単回証明・トークンは記録しません。ログイン成功後に主体を確定し、信頼できるサービス/CLI は audit.withContext で操作者と関連文脈を設定します。外部権限トランザクション内のイベントは一緒にコミット/ロールバックされ、失敗試行はロールバック後に呼出境界で記録します。監査失敗は診断を出し、反映済み業務を危険な再試行に変えません。
949
1088
 
@@ -951,12 +1090,41 @@ audit が初期化済みで capture.enabled が有効なら、IAM はアカウ
951
1090
 
952
1091
  画面内の復帰は UI の `createSessionRecovery` / `SessionRecoveryDialog` と SDK `recoverSession` を組み合わせます。通常のパスワード・OTP・LDAP 方針を適用し、`expectedUserId` で元の本人に限定してからセッションを発行します。認証拒否が明確な要求だけ1回再送でき、結果不明のネットワーク障害は再送しません。画面再読込や終了で失われたメモリの入力は復元できません。
953
1092
 
954
- ## ページ離脱時のセッション方針
1093
+ ### ページ離脱時のセッション方針
955
1094
 
956
1095
  `session.endOnPageHide` の既定値は `false` です。ホストは `actions.bindPageExit({ enabled: config.session.endOnPageHide })` を呼び、破棄時に戻り値の解除関数を実行します。標準 admin/H5/fullstack テンプレートは接続済みです。BFCache 以外の `pagehide`(再読み込み、文書全体の移動を含む)で POST `/auth/session/close` を最善努力で送信し、保持する access と関連 refresh を失効します。既存の CSRF/暗号化転送を使用し、`keepalive` を付け、自動更新しません。古い応答が新しいログインを上書きしないよう Cookie/ローカル資格情報を消去しません。明示的ログアウトは通常どおり失効と消去を行います。ブラウザーが離脱要求を送らない場合もあるため、業務に応じてサーバーの `idleTimeout`/`absoluteTimeout` を併用してください。閉じた瞬間の失効は保証できません。
957
1096
 
958
1097
  標準 `/auth/me` も `iam.profile.update` / `iam.profile.avatar` を検査します。複合更新では有効な資料変更規則を優先し、それ以外は画像規則を使います。`authz.getSensitiveOperations(operation?)` は単一操作だけ照会でき、省略時はDB規則一覧を返します。超管理者の役割は共有DBから毎回取得し、種子の並行作成競合は再読込、権限割当失敗は上位へ返します。
959
1098
 
1099
+ ## 設定
1100
+
1101
+ ライフサイクル。init にバックエンド/業務設定を渡し成功後に使用します。借用依存先の解放前に close の完了を待ち、正確な戻り値は例と公開署名に従います。
1102
+
1103
+ 設定はホストが読込・検証してから渡します。秘密はサーバーの非公開環境に保存し、利用場面に応じてバックエンド・容量・期限・ログを選びます。変更後は再初期化の結果を確認してください。
1104
+
1105
+ [項目別の用途・適用場面・例](../../docs/usage/configuration/identity.md).
1106
+
1107
+ ## エラー処理
1108
+
1109
+ 領域操作は result.success を確認し、失敗時はモジュールのエラー定義と安全な説明を使います。コード・関連 ID・秘匿した原因をサーバーログに残します。同期ツール・ファクトリー・ストリーム反復・ライフサイクルは各署名に従い、Promise 完了だけで成功と判断しません。回復条件は各機能を参照してください。
1110
+
1111
+ すべての操作は `HaiResult<T>` を返し、`HaiIamError` で分岐します:
1112
+
1113
+ ```ts
1114
+ import { HaiIamError, iam } from '@h-ai/iam'
1115
+
1116
+ const result = await iam.auth.login({ identifier: 'admin', password: 'wrong' })
1117
+ if (!result.success) {
1118
+ if (result.error.code === HaiIamError.INVALID_CREDENTIALS.code) {
1119
+ // 用户名或密码错误
1120
+ }
1121
+ }
1122
+ ```
1123
+
1124
+ 会話と refresh 消費状態を `hai:iam:sessions:{SHA256(userId)}` のバージョン付き文書にまとめ、独立した対応キーと索引を廃止します。Bearer Token は認証には使えない分区ヒントと256ビットの乱数を含み、クライアントは不透明な文字列として扱います。更新時は Session.version を再検証します。Memory は単一プロセスのテスト用で、本番は全ノードが Redis(Cluster 対応)を共有します。競合は最新状態から再試行し、上限で CONFLICT を返します。応答喪失で結果が不明なら認証を拒否し再ログインします。すべての会話は同じユーザー索引で管理されます。
1125
+
1126
+ `iam.session.update(token, patch, expectedVersion)` は読み取った Session.version が必須です。旧版は CONFLICT、失効 Token は SESSION_NOT_FOUND を返し、新しい状態を上書きしません。自動延長の時刻だけの更新は内容版を変更せず、通常の並行リクエストを許容します。権限一括更新は最初の Token/版集合に限定し、後続ログインに古い値を適用しません。
1127
+
960
1128
  ## 開発とテスト
961
1129
 
962
1130
  公開例は `@h-ai/iam` を使用し、既定値を省略できる `IamConfigInput` と解析済み `IamConfig` を区別します。依存を先に初期化し、`HaiResult.success` を確認し、会話更新には読取時の `version`、通知には永続的な冪等キーを使います。「模块内部上下文」の戦略・倉庫例はアプリ向け深い import の契約ではありません。チェッカーは型だけを確認し、Redis/DB・LDAP・配送の動作には対応する統合テストが必要です。[JSDoc 規約](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)も参照してください。