@h-ai/iam 0.1.0-alpha.66 → 0.1.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +107 -76
- package/README.ja.md +104 -75
- package/README.md +106 -77
- package/dist/browser.d.ts +2320 -0
- package/dist/browser.js +3 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-VCE74BFW.js +243 -0
- package/dist/chunk-VCE74BFW.js.map +1 -0
- package/dist/index.d.ts +19 -1623
- package/dist/index.js +789 -1173
- package/dist/index.js.map +1 -1
- package/package.json +12 -9
package/README.ja.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
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
|
+
|
|
7
9
|
共通の `iam` オブジェクトで認証、セッション、認可、ユーザー管理を提供する Identity and Access Management モジュールです。
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## 主な機能
|
|
10
12
|
|
|
11
13
|
| 機能 | 説明 |
|
|
12
14
|
| ------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
@@ -18,27 +20,29 @@
|
|
|
18
20
|
| **一度限りのチケット** | Bearer Token を付けられない WebSocket などのための短期チケット(発行 / アトミックな消費) |
|
|
19
21
|
| **フロントエンドクライアント** | 独立フロントエンド向けの HTTP API 契約 |
|
|
20
22
|
|
|
21
|
-
##
|
|
23
|
+
## 使い始める
|
|
24
|
+
|
|
25
|
+
### インストール
|
|
22
26
|
|
|
23
27
|
```bash
|
|
24
28
|
pnpm add @h-ai/iam
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
### 依存関係
|
|
28
32
|
|
|
29
33
|
- `@h-ai/reldb`:ユーザー・ロール・権限の永続化。**iam.init() より先に初期化**。
|
|
30
|
-
- `@h-ai/cache`:セッション・OTP
|
|
34
|
+
- `@h-ai/cache`:セッション・OTP・一度限りのチケット。**iam.init() より先に初期化**。
|
|
31
35
|
- `@h-ai/crypto`:内部で使用するパスワードハッシュ。自動初期化。
|
|
32
36
|
- `@h-ai/audit`:主要な RBAC 操作の監査記録。
|
|
33
37
|
|
|
34
38
|
- `@h-ai/reach`:**任意**。アプリがインストール、初期化、注入して通知テンプレートを使用します。独自送信コールバックでは不要です。
|
|
35
39
|
- `ldapts`:**任意**。LDAP を明示的に有効化し、`ldapClientFactory` がない場合だけ読み込みます。
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
### 任意の LDAP と通知連携
|
|
38
42
|
|
|
39
43
|
パスワード認証、ユーザー管理、RBAC は LDAP/Reach なしで動作します。両方とも任意の peer dependency で、必要な連携だけをインストールします。以下は reldb/cache 初期化後の起動・認証関数内の例で、`username/password` は検証済み入力です。
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
#### LDAP / Active Directory
|
|
42
46
|
|
|
43
47
|
既定クライアントは `pnpm add ldapts` で追加し、`login.ldap: true` と接続設定を明示します。接続設定だけでは LDAP は有効になりません。SDK なしで既定クライアントを有効にすると初期化が設定エラーを返します。
|
|
44
48
|
|
|
@@ -58,7 +62,7 @@ ldap:
|
|
|
58
62
|
connectTimeout: 5000
|
|
59
63
|
```
|
|
60
64
|
|
|
61
|
-
`ldaps://` は TLS、`ldap://` と `useTls: true` は StartTLS を使用します。Active Directory では `searchFilter: "(sAMAccountName={{username}})"` と `usernameAttribute: sAMAccountName` を指定できます。検索前に入力をエスケープします。初回ログインはローカルユーザーを同期して既定ロールを割り当て、無効化とロックも適用します。`ldapSyncUser: false`
|
|
65
|
+
`ldaps://` は TLS、`ldap://` と `useTls: true` は StartTLS を使用します。Active Directory では `searchFilter: "(sAMAccountName={{username}})"` と `usernameAttribute: sAMAccountName` を指定できます。検索前に入力をエスケープします。初回ログインはローカルユーザーを同期して既定ロールを割り当て、無効化とロックも適用します。`ldapSyncUser: false` は DB 資格情報世代を持つ有効なローカルユーザーを必要とし、仮想 DN ユーザーは作りません。 `bind/search/unbind` を HaiResult で返す `ldapClientFactory` を注入できます。独自ファクトリでは ldapts は不要です。
|
|
62
66
|
|
|
63
67
|
```ts
|
|
64
68
|
import type { IamConfigSettingsInput } from '@h-ai/iam'
|
|
@@ -75,7 +79,7 @@ if (!result.success)
|
|
|
75
79
|
return result
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
#### 任意の Reach による自動送信
|
|
79
83
|
|
|
80
84
|
`pnpm add @h-ai/reach` で追加します。設定、初期化、終了はアプリが管理し、IAM は注入されたインスタンスだけを使用します。SMTP/HTTP は例で、SMS ゲートウェイは [Reach 通知仕様](../reach/README.ja.md) を受け取ります。Console はメタデータのみを記録し、実際の送信は行いません。
|
|
81
85
|
|
|
@@ -133,7 +137,7 @@ if (!initialized.success)
|
|
|
133
137
|
return iam.auth.sendOtp('user@example.com')
|
|
134
138
|
```
|
|
135
139
|
|
|
136
|
-
|
|
140
|
+
#### 独自メール・SMS 送信
|
|
137
141
|
|
|
138
142
|
`onOtpSendEmail`、`onOtpSendSms`、`onPasswordResetRequest` を注入して既存の送信サービスを使用します。必要なものだけ実装でき、Reach と併用する場合は明示コールバックを優先します。例のサービスはアプリ実装で、送信成功時のみ Promise を解決します。HaiResult を返すサービスは `success` を検査し、失敗時はコールバック内で例外にします。IAM は例外を捕捉して業務エラーを返し、コードや Token、送信内部情報をユーザーに表示しません。
|
|
139
143
|
|
|
@@ -145,26 +149,26 @@ import { mailService, smsService } from './notifications.js'
|
|
|
145
149
|
// 调用方须检查服务投递结果,失败时拒绝 Promise,不能吞掉错误。
|
|
146
150
|
const initialized = await iam.init({
|
|
147
151
|
otp: { email: true, sms: true },
|
|
148
|
-
onOtpSendEmail: async (email, code) => {
|
|
149
|
-
await mailService.send({ to: email, subject: 'Verification code', text: code })
|
|
152
|
+
onOtpSendEmail: async (email, code, context) => {
|
|
153
|
+
await mailService.send({ to: email, subject: 'Verification code', text: code, idempotencyKey: context.idempotencyKey })
|
|
150
154
|
},
|
|
151
|
-
onOtpSendSms: async (phone, code) => {
|
|
152
|
-
await smsService.send({ to: phone, code })
|
|
155
|
+
onOtpSendSms: async (phone, code, context) => {
|
|
156
|
+
await smsService.send({ to: phone, code, idempotencyKey: context.idempotencyKey })
|
|
153
157
|
},
|
|
154
158
|
passwordReset: { mode: 'email' },
|
|
155
|
-
onPasswordResetRequest: async (user, token,
|
|
159
|
+
onPasswordResetRequest: async (user, token, context) => {
|
|
156
160
|
if (!user.email)
|
|
157
161
|
throw new Error('Reset recipient unavailable')
|
|
158
162
|
const url = new URL('https://app.example.com/auth/reset-password')
|
|
159
163
|
url.searchParams.set('token', token)
|
|
160
|
-
await mailService.send({ to: user.email, subject: 'Password reset', text: url.toString(), expiresAt })
|
|
164
|
+
await mailService.send({ to: user.email, subject: 'Password reset', text: url.toString(), expiresAt: context.expiresAt, idempotencyKey: context.idempotencyKey })
|
|
161
165
|
},
|
|
162
166
|
})
|
|
163
167
|
if (!initialized.success)
|
|
164
168
|
return initialized
|
|
165
169
|
```
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
### クイックスタート
|
|
168
172
|
|
|
169
173
|
```ts
|
|
170
174
|
import { cache } from '@h-ai/cache'
|
|
@@ -209,9 +213,9 @@ LDAP は既定で無効です。公開 OTP は `otp` 設定と送信コールバ
|
|
|
209
213
|
|
|
210
214
|
`iam.config` は**機密情報をマスクした設定スナップショット**を返します。LDAP の `url` / `bindPassword` などは自動的に非表示になります。状態確認用で、元の設定の代わりにはなりません。
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
### 設計
|
|
213
217
|
|
|
214
|
-
|
|
218
|
+
#### アーキテクチャ
|
|
215
219
|
|
|
216
220
|
IAM は `iam.init()` / `iam.close()` で状態を管理する**ライフサイクル付きシングルトン**です。内部の 5 つのサブモジュールを依存順に初期化します:
|
|
217
221
|
|
|
@@ -228,29 +232,29 @@ iam.init()
|
|
|
228
232
|
|
|
229
233
|
初期化前のサブモジュール呼び出しは `NotInitializedKit` Proxy により `NOT_INITIALIZED` を返します。
|
|
230
234
|
|
|
231
|
-
|
|
235
|
+
#### データの保存先
|
|
232
236
|
|
|
233
|
-
| データ種別
|
|
234
|
-
|
|
|
235
|
-
| ユーザー、ロール、権限、関連付け、API Key
|
|
236
|
-
| セッション(accessToken → Session)
|
|
237
|
-
|
|
|
238
|
-
| OTP コード
|
|
239
|
-
| パスワードリセット Token
|
|
240
|
-
| API Key
|
|
237
|
+
| データ種別 | 保存先 | 説明 |
|
|
238
|
+
| ----------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
239
|
+
| ユーザー、ロール、権限、関連付け、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` |
|
|
240
|
+
| セッション(accessToken → Session) | キャッシュ | TTL 付き、任意の自動延長 |
|
|
241
|
+
| Refresh 消費状態 | Redis ユーザー分区 | セッション・索引と単一キー CAS で更新 |
|
|
242
|
+
| OTP コード | キャッシュ | TTL + 試行回数制限 |
|
|
243
|
+
| パスワードリセット Token | ユーザー DB 行 | SM3 hash・DB 期限・試行回数・条件付き改密 |
|
|
244
|
+
| API Key | DB | `hai_iam_api_keys`。平文は保存せず hash のみ。接頭辞で候補検索 |
|
|
241
245
|
|
|
242
246
|
---
|
|
243
247
|
|
|
244
|
-
|
|
248
|
+
#### 認証(authn)— `iam.auth`
|
|
245
249
|
|
|
246
|
-
|
|
250
|
+
##### 主要な型
|
|
247
251
|
|
|
248
252
|
- **`AuthnOperations`**:認証操作。`login` / `loginWithOtp` / `loginWithLdap` / `loginWithApiKey` / `logout` / `verifyToken` / `sendOtp` / `registerAndLogin`。
|
|
249
253
|
- **`AuthStrategy`**:認証方式のインターフェース(`type` + `authenticate` + `challenge?`)。パスワード・OTP・LDAP・API Key の 4 実装。
|
|
250
254
|
- **`AuthResult`**:ユーザー情報、`TokenPair`(accessToken + refreshToken)、ロール・権限を含むログイン結果。
|
|
251
255
|
- **`Credentials`**:`{ type: 'password' | 'otp' | 'ldap' | 'apikey' }` と各方式の認証情報を組み合わせた共通の共用体型。
|
|
252
256
|
|
|
253
|
-
|
|
257
|
+
##### 主要な処理
|
|
254
258
|
|
|
255
259
|
**パスワードによるログイン:**
|
|
256
260
|
|
|
@@ -274,13 +278,14 @@ login(credentials)
|
|
|
274
278
|
```
|
|
275
279
|
verifyToken(accessToken)
|
|
276
280
|
→ session.get(accessToken) → キャッシュ検索
|
|
281
|
+
→ 永続ユーザーの有効状態とセッションに紐付く credentialVersion を検証
|
|
277
282
|
→ 自動延長モードなら TTL を延長
|
|
278
|
-
→
|
|
279
|
-
→
|
|
283
|
+
→ 現在のユーザーを再読込し、有効状態と credentialVersion を再確認して、ロール JOIN・権限一括検索(ロールなしなら権限検索を省略)
|
|
284
|
+
→ 永続データの読取失敗、ユーザー不在・無効、資格情報の世代不一致は拒否し、API Key scope を再適用
|
|
280
285
|
→ 現在の Session を返す(userId、roles、permissions)
|
|
281
286
|
```
|
|
282
287
|
|
|
283
|
-
|
|
288
|
+
##### 使用方法
|
|
284
289
|
|
|
285
290
|
例は reldb/cache/IAM の初期化に成功したサーバーで実行し、検証済みの入力を使います。パスワード認証は既定で有効です。OTP には `login.otp: true`、`otp` チャネル、送信コールバック/Reach テンプレートが必要です。LDAP は `login.ldap: true`、API Key は `login.apikey: true` を有効にします。ログインと登録後ログインは `AuthResult` を返します。登録とパスワード認証のスイッチは独立しています。
|
|
286
291
|
|
|
@@ -312,47 +317,47 @@ async function registerWithSession(username: string, password: string) {
|
|
|
312
317
|
}
|
|
313
318
|
```
|
|
314
319
|
|
|
315
|
-
|
|
320
|
+
##### 注意点
|
|
316
321
|
|
|
317
322
|
- **タイミング攻撃対策**:未存在ユーザーにも dummy password hash を実行し、応答時間をそろえます。
|
|
318
323
|
- **アカウントロック**:連続 `maxLoginAttempts` 回の失敗で `lockoutDuration` 秒ロック。
|
|
319
324
|
- **方式ごとの有効化**:`login.password` / `login.otp` / `login.ldap` / `login.apikey` で制御。
|
|
320
325
|
- **OTP の安全性**:rejection sampling で剰余偏りを除去、定時間比較でタイミング攻撃を防ぎ、`resendInterval` で頻度制限。
|
|
321
326
|
|
|
327
|
+
`iam.auth.logout(accessToken)` は完全な不透明 Token を使い、ユーザー文書の一回の CAS で access と refresh を同時に取り消します。access 期限後も実行します。不正・不存在 Token は冪等成功ですが、キャッシュの読取・確定障害は失敗で返し、復旧後に同じ Token で再試行できます。HTTP 入口は保持している Token だけを取り消し、userId を対象にせず、有効な access 認証を前提にしません。呼出側は `success` を確認し、Cookie・安全ストレージのローカル消去だけでサーバー失効を成功と扱わず、失敗を表示します。
|
|
328
|
+
|
|
322
329
|
---
|
|
323
330
|
|
|
324
|
-
|
|
331
|
+
#### セッション(session)— `iam.session`
|
|
325
332
|
|
|
326
|
-
|
|
333
|
+
##### 主要な型
|
|
327
334
|
|
|
328
335
|
- **`SessionOperations`**:`create` / `get` / `verifyToken` / `update` / `delete` / `deleteByUserId` / `refresh` / `revokeRefresh` / `patchUserSessions`。
|
|
329
336
|
- **`Session`**:userId、roles、permissions、accessToken、期限などを含むセッション。
|
|
330
337
|
- **`TokenPair`**:Token のペア。`{ accessToken, refreshToken, expiresIn, tokenType: 'Bearer' }`。
|
|
331
338
|
|
|
332
|
-
|
|
339
|
+
##### 主要な処理
|
|
333
340
|
|
|
334
341
|
**Token 更新(Rotation):**
|
|
335
342
|
|
|
336
343
|
```
|
|
337
344
|
refresh(refreshToken)
|
|
338
|
-
→
|
|
339
|
-
→
|
|
340
|
-
→
|
|
341
|
-
→
|
|
342
|
-
→ 旧ユーザー context で新 session を作成
|
|
343
|
-
→ 新 TokenPair を返す
|
|
345
|
+
→ ユーザー分区と会話バージョンを読み、認可を検証
|
|
346
|
+
→ 未公開の新 Token 候補を生成
|
|
347
|
+
→ 単一キー CAS で version/refresh を再検証し、旧会話と原子的に置換
|
|
348
|
+
→ コミット後のみ唯一の後続 TokenPair を返す
|
|
344
349
|
```
|
|
345
350
|
|
|
346
351
|
**単一端末のログイン:**
|
|
347
352
|
|
|
348
353
|
```
|
|
349
354
|
create(options) [singleDevice=true]
|
|
350
|
-
→
|
|
351
|
-
→
|
|
352
|
-
→
|
|
355
|
+
→ 新 accessToken + refreshToken の候補を生成
|
|
356
|
+
→ 単一キー CAS で既存会話と索引を同時に置換
|
|
357
|
+
→ コミット失敗時は旧会話を維持
|
|
353
358
|
```
|
|
354
359
|
|
|
355
|
-
|
|
360
|
+
##### 使用方法
|
|
356
361
|
|
|
357
362
|
`tokens.accessToken` でリクエストを認証し、`tokens.refreshToken` で新しいペアを取得します。`expiresIn` は秒です。独立 API は `Authorization: Bearer <accessToken>`、SvelteKit の Cookie 入口は後述の kit 標準連携を使います。
|
|
358
363
|
|
|
@@ -387,27 +392,35 @@ async function forceSignOut(userId: string) {
|
|
|
387
392
|
}
|
|
388
393
|
```
|
|
389
394
|
|
|
390
|
-
|
|
395
|
+
##### 注意点
|
|
391
396
|
|
|
392
397
|
- **キャッシュのみの保存**:Memory は再起動時に消失します。共有 Redis のセッションは IAM サービス再起動後も TTL または失効まで保持します。
|
|
393
398
|
- **自動延長**:`sliding: true` なら各 `get()` で TTL を延長。
|
|
394
|
-
- **Refresh Rotation
|
|
399
|
+
- **Refresh Rotation**:refresh は一度だけ消費します。重複は正常な並行処理や応答喪失でも発生し、それだけで漏洩と判断しません。
|
|
395
400
|
- **Token 強度**:256-bit の暗号学的乱数(`crypto.getRandomValues(new Uint8Array(32))`)、base64url 形式。
|
|
396
401
|
|
|
402
|
+
`create({ maxAge })` は設定の既定値を正の安全な整数秒で上書きし、`Session.maxAge` に保存します。発行と更新の `expiresIn` はこの値を使います。自動延長は同じ期間を使い、確定済みの `lastActiveAt/expiresAt` を返し、遅いリクエストが新しい延長を短縮しません。明示的な `update` は有効な日時を反映して内容版を更新します。クライアントは返された `expiresIn` から更新時刻を決めます。
|
|
403
|
+
|
|
404
|
+
access と refresh は独立して期限切れになります。ユーザー文書は両期限の最大まで保持しますが、access 認証は自身の expiresAt のみを確認します。access 期限後も有効な refresh で更新でき、更新ごとに新しい refreshTokenMaxAge を開始して旧資格情報を失効させます。access 削除・全ログアウト・単一端末置換は更新待ち記録も取り消し、revokeRefresh は有効な access を残します。更新経路で旧 access の認証成功を前提にしません。
|
|
405
|
+
|
|
406
|
+
セッションの読取・確定障害は失敗 HaiResult として返し、不存在や期限切れと解釈せず補償削除もしません。索引と会話は同じ文書で保持し、個別 get/ttl や二重索引を廃止します。確定した期限だけで整理し、復旧後の再読取・全ログアウトは元の全記録を対象にします。破損状態は保持して認証を拒否し、空文書で上書きせず運用者が修復します。
|
|
407
|
+
|
|
408
|
+
Session.credentialVersion は作成時の DB 資格情報世代に結び付けます。CreateSessionOptions.expectedCredentialVersion は検証済みの信頼する戦略だけが指定し、旧世代は拒否します。省略した低レベル create でも実在する有効なローカルユーザーを DB から読み、現世代に結び付けるため、DB 記録のない認証済み ID は作れません。
|
|
409
|
+
|
|
397
410
|
---
|
|
398
411
|
|
|
399
|
-
|
|
412
|
+
#### 認可(authz)— `iam.authz`
|
|
400
413
|
|
|
401
414
|
ロール一覧は `iam.authz.getRoleUserCounts(roleIds)` の 1 回の集計で現在のページの実際の所属人数を取得し、`HaiResult<Map<string, number>>` を返します。関連がなければ 0、検索失敗は読み込みエラーを表示し、0 件の統計を作りません。
|
|
402
415
|
|
|
403
|
-
|
|
416
|
+
##### 主要な型
|
|
404
417
|
|
|
405
418
|
- **`AuthzOperations`**:ロール・権限 CRUD、ロールと権限・ユーザーとロールの割り当て、権限確認。
|
|
406
419
|
- **`Role`**:ロール。`{ id, code, name, description, isSystem }`。
|
|
407
420
|
- **`Permission`**:権限。`{ id, code, name, type, resource, action }`、type は `menu` / `api` / `button`。
|
|
408
421
|
- **`PermissionType`**:権限種別の enum。
|
|
409
422
|
|
|
410
|
-
|
|
423
|
+
##### 主要な処理
|
|
411
424
|
|
|
412
425
|
**権限確認(`checkPermission`):**
|
|
413
426
|
|
|
@@ -430,7 +443,7 @@ assignPermissionToRole(roleId, permId)
|
|
|
430
443
|
(best-effort:失敗は log.error のみで割り当て結果に影響しない)
|
|
431
444
|
```
|
|
432
445
|
|
|
433
|
-
|
|
446
|
+
##### 使用方法
|
|
434
447
|
|
|
435
448
|
権限を作成し、その **ID** を `permissionIds` に保存してロールを作成し、ロール **ID** でユーザーに割り当てます。セッションとガードは安定した **code** を使います。表示名は独立して変更できます。例は RBAC が有効であることを前提とし、管理操作の権限とリソース範囲はアプリで確認します。
|
|
436
449
|
|
|
@@ -478,19 +491,19 @@ async function currentAuthorization(userId: string) {
|
|
|
478
491
|
}
|
|
479
492
|
```
|
|
480
493
|
|
|
481
|
-
|
|
494
|
+
##### 注意点
|
|
482
495
|
|
|
483
496
|
- **ワイルドカード**:`admin:*` は `admin:read`、`admin:write:detail` などに一致。1 つの `*` がコロン以降全体に一致します。
|
|
484
497
|
- **最高管理者**:`rbac.superAdminRole`(既定 `super_admin`)はすべての権限を持ちます。
|
|
485
498
|
- **トランザクション**:`deleteRole` / `deletePermission` は DB トランザクションで関連を削除してから本体を削除。
|
|
486
|
-
- **セッション同期**:キャッシュ更新は best-effort です。保護されたリクエストは `iam.auth.verifyToken`
|
|
499
|
+
- **セッション同期**:キャッシュ更新は best-effort です。保護されたリクエストは `iam.auth.verifyToken` でセッションに紐付く永続資格情報の世代を検証し、現在のユーザー状態・資格情報の世代を再確認して最新の認可を読みます。いずれかの読取に失敗すれば拒否します。`iam.session.get/verifyToken` も永続ユーザーの有効状態と `credentialVersion` を検証しますが、ロール・権限はセッションのスナップショットであり、現在の認可を代替しません。`iam.authz.getUserAuthorization(userId)` はロール JOIN と権限一括検索を共有します。
|
|
487
500
|
- **初期データ**:`seedDefaultData: true` で admin/user/guest と基本権限を冪等に作成。
|
|
488
501
|
|
|
489
502
|
---
|
|
490
503
|
|
|
491
|
-
|
|
504
|
+
#### ユーザー管理(user)— `iam.user`
|
|
492
505
|
|
|
493
|
-
|
|
506
|
+
##### 主要な型
|
|
494
507
|
|
|
495
508
|
- **`UserOperations`**:登録、検索、更新、削除、パスワード変更・リセット、強度検証。
|
|
496
509
|
- **`User`**(公開フィールド):`{ id, username, email, phone, displayName, avatarUrl, enabled, roles? }`。
|
|
@@ -498,7 +511,7 @@ async function currentAuthorization(userId: string) {
|
|
|
498
511
|
- **`RegisterOptions`** / **`RegisterResult`**:登録の入力・出力。
|
|
499
512
|
- **`UpdateCurrentUserInput`**:現在のユーザーが変更できるフィールド(`username` / `email` / `displayName` / `avatarUrl` / `phone` / `metadata`)。
|
|
500
513
|
|
|
501
|
-
|
|
514
|
+
##### 主要な処理
|
|
502
515
|
|
|
503
516
|
**ユーザー登録:**
|
|
504
517
|
|
|
@@ -528,7 +541,7 @@ confirmPasswordReset(token, newPassword)
|
|
|
528
541
|
→ 使用済みリセット Token を削除
|
|
529
542
|
```
|
|
530
543
|
|
|
531
|
-
|
|
544
|
+
##### 使用方法
|
|
532
545
|
|
|
533
546
|
`register` はユーザーだけを返し、セッションは作りません。同時ログインには `auth.registerAndLogin` を使います。管理用の `createUser` は自己登録設定と独立しています。自己プロフィールは `getCurrentUser/updateCurrentUser` で username/email/displayName/avatarUrl/phone/metadata を更新します。安全性に関わるフィールドとロールは認可された管理入口を使います。
|
|
534
547
|
|
|
@@ -586,7 +599,7 @@ async function disableUser(userId: string) {
|
|
|
586
599
|
}
|
|
587
600
|
```
|
|
588
601
|
|
|
589
|
-
|
|
602
|
+
##### 注意点
|
|
590
603
|
|
|
591
604
|
- **`toUser()` の機密除去**:公開ユーザー情報から `passwordHash`、`loginFailedCount` などを除外。
|
|
592
605
|
- **セッション連動**:`deleteUser` / `updateUser({ enabled: false })` / `changePassword` / `confirmPasswordReset` は対象ユーザーの全セッションを削除。
|
|
@@ -597,17 +610,17 @@ async function disableUser(userId: string) {
|
|
|
597
610
|
|
|
598
611
|
---
|
|
599
612
|
|
|
600
|
-
|
|
613
|
+
#### API Key — `iam.apiKey`
|
|
601
614
|
|
|
602
615
|
`scopes` は主体の権限との共通範囲を適用し、`*` と名前空間 `:*` に対応します。既定の空配列は本人確認のみで操作権限を付与しません。制限付き key のセッション `roles` は空で permission ガードを使用し、明示的な `*` も主体の権限を超えません。ログインは key ID に結び付き、get/verifyToken/refresh は永続状態を再確認し、失効・無効・期限切れ・DB エラーを拒否します。verifyApiKey 単独は鍵のメタデータ検証のみで、主体・操作の認可は別途必要です。
|
|
603
616
|
|
|
604
|
-
|
|
617
|
+
##### 主要な型
|
|
605
618
|
|
|
606
619
|
- **`ApiKeyOperations`**:`createApiKey` / `listApiKeys` / `getApiKey` / `revokeApiKey` / `verifyApiKey`。
|
|
607
620
|
- **`ApiKey`**(公開フィールド):`{ id, userId, name, keyPrefix, enabled, expiresAt, scopes }`。
|
|
608
621
|
- **`CreateApiKeyResult`**:`{ apiKey, rawKey }`。平文の鍵を返すのは一度のみ。
|
|
609
622
|
|
|
610
|
-
|
|
623
|
+
##### 主要な処理
|
|
611
624
|
|
|
612
625
|
**API Key の検証:**
|
|
613
626
|
|
|
@@ -621,7 +634,7 @@ verifyApiKey(rawKey)
|
|
|
621
634
|
→ ApiKey を返す
|
|
622
635
|
```
|
|
623
636
|
|
|
624
|
-
|
|
637
|
+
##### 使用方法
|
|
625
638
|
|
|
626
639
|
初期化設定で API Key を有効にします。Key はユーザーの制限付き認証情報です:**有効権限 = 現在のユーザー権限 ∩ scopes**。scope は権限 code で、ロール名、表示名、リソース ID ではありません。権限を作成せず、テナント/レコード所有権の検証も代替しません。
|
|
627
640
|
|
|
@@ -707,7 +720,7 @@ Key セッションは `iam.session.refresh` 後も Key に紐付きます。取
|
|
|
707
720
|
|
|
708
721
|
`expirationDays: 0` は無期限、省略時は `apikey.defaultExpirationDays` を使用します。期限が必要なら明示します。生 Key/Token を URL、ログ、公開設定、通常の localStorage に保存しません。
|
|
709
722
|
|
|
710
|
-
|
|
723
|
+
##### 注意点
|
|
711
724
|
|
|
712
725
|
- **平文は保存しない**:DB は hash のみを保存し、作成時の `rawKey` が唯一の取得機会。
|
|
713
726
|
- **接頭辞検索**:`keyPrefix` で候補を絞り、全テーブル走査を避けます。
|
|
@@ -716,11 +729,11 @@ Key セッションは `iam.session.refresh` 後も Key に紐付きます。取
|
|
|
716
729
|
|
|
717
730
|
---
|
|
718
731
|
|
|
719
|
-
|
|
732
|
+
### その他の使い方
|
|
720
733
|
|
|
721
734
|
API 引数・エラーコード・統合パターンの詳細は中国語の Skill テンプレート(`packages/cli/templates/skills/hai-iam/SKILL.md`)を参照してください。
|
|
722
735
|
|
|
723
|
-
|
|
736
|
+
### フロントエンドの API 呼び出し
|
|
724
737
|
|
|
725
738
|
公開 HTTP API は `@h-ai/api-contract` で定義し、`@h-ai/api-client` の型付きクライアントで呼び出します:
|
|
726
739
|
|
|
@@ -745,7 +758,9 @@ await apiClient.iam.auth.changePassword({
|
|
|
745
758
|
})
|
|
746
759
|
```
|
|
747
760
|
|
|
748
|
-
|
|
761
|
+
browser 条件の入口は設定 Schema、型、HaiIamError のみを公開し、サーバーの `iam` インスタンスは含みません。認証には HTTP/typed client を使用します。
|
|
762
|
+
|
|
763
|
+
### パスワードリセット
|
|
749
764
|
|
|
750
765
|
リセットには `passwordReset.enabled` と送信コールバック/Reach テンプレートが必要です。成功はアカウントの存在や受信を証明しません。送信・内部処理の HaiResult エラーも処理します。期限/試行回数制限があり、一度だけ成功消費でき、成功時に旧セッションを取り消します。管理者リセットに旧パスワードは不要ですが、アプリで管理権限を確認します。
|
|
751
766
|
|
|
@@ -768,7 +783,9 @@ async function resetByAdministrator(userId: string, newPassword: string) {
|
|
|
768
783
|
}
|
|
769
784
|
```
|
|
770
785
|
|
|
771
|
-
|
|
786
|
+
キャッシュ削除が失敗を返しても、確定した新パスワードと資格情報世代は巻き戻しません。旧資格情報で自動再試行せず、再ログインしてください。
|
|
787
|
+
|
|
788
|
+
### 一度限りのチケット — `iam.ticket`
|
|
772
789
|
|
|
773
790
|
WebSocket URL など Bearer Token を付けられない経路向けの短期チケットです。認証済み HTTP リクエストで、主体・用途・操作に結び付けて発行します。サーバーは初回検証時に**アトミックに消費**し、一度のみ有効です。
|
|
774
791
|
|
|
@@ -801,7 +818,9 @@ async function consumeAudioTicket(ticket: string) {
|
|
|
801
818
|
|
|
802
819
|
特徴:暗号学的に安全な乱数、TTL、アトミックな単回消費(並行消費では 1 件のみ成功)、用途の固定による別用途の再使用防止。
|
|
803
820
|
|
|
804
|
-
|
|
821
|
+
### 設定による認証画面
|
|
822
|
+
|
|
823
|
+
パスワードログインの送信項目は `identifier` と `password` のみです。`AuthPanel` は `LoginForm` の `username` を `identifier` に変換します。有効期間はサーバーの `session.maxAge` / `session.refreshTokenMaxAge` で設定し、フォームにログインごとの永続セッション選択肢はありません。
|
|
805
824
|
|
|
806
825
|
`iam.publicConfig` とクライアントの HTTP `client.iam.auth.publicConfig()` は秘密情報を含まない設定だけを公開し、実際に利用可能な認証方式を表示します。`login.password/otp/ldap`、`otp.email/sms`、`register.enabled/defaultEnabled`、`passwordReset.enabled/mode/resetUrl` と規約表示を個別に設定できます。`ui` はカード/分割レイアウト、タイトル、説明、ブランド、Logo、画像を指定します。LDAP は任意で既定は無効です。既定クライアントを選択する時だけ ldapts を追加するか、独自ファクトリを注入します。
|
|
807
826
|
|
|
@@ -811,13 +830,13 @@ SvelteKit は `kit.auth.createApi(iam)` と匿名用の `kit.auth.publicPaths`
|
|
|
811
830
|
|
|
812
831
|
規約は入力を保持したまま Modal で開きます。URL のページは iframe 埋め込みを許可する必要があり、スクリプトを無効にし別ウィンドウ表示も用意します。LoginForm/RegisterForm は `header/extraFields/footer`、AuthPanel は `loginHeader/loginFields/loginFooter` と `registerHeader/registerFields/registerFooter` を提供します。追加フィールドはアプリの状態と actions で処理します。[UI の使い方](../ui/README.ja.md) を参照してください。
|
|
813
832
|
|
|
814
|
-
|
|
833
|
+
全ノードで同じ DB と Redis を共有します。Memory は単一プロセスのテスト用です。OTP のコード・試行回数・再送間隔は単一キーの CAS で公開・消費し、旧世代の競合を拒否します。消費後も再送制限を保持します。リセットの hash・期限・試行回数・resetVersion はユーザーの DB 行に保存し、ポリシー違反も計数します。最終 SQL が条件を検証してパスワード変更・Token 消費・credentialVersion 更新を同時に行い、新 Token・後続試行・無効化・期限切れ後の遅延書込を拒否します。リセット要求は resetVersion のみ進め、改密成功時に credentialVersion を進めます。認証で取得した資格情報世代を保持し、セッション作成・認証・更新で DB の現世代を照合するため、キャッシュ削除失敗でも旧会話を許可しません。通知は公開成功後に実行し、第3引数 { expiresAt, idempotencyKey } の安定キーをアプリと Reach Provider が永続的に重複排除します。例外・タイムアウトは送達済みの可能性があるため結果不明とし、自動再送や公開済み資格情報の巻き戻しはしません。外部副作用の厳密な一回実行には受信側の冪等性が必要です。
|
|
815
834
|
|
|
816
835
|
標準 HTTP 入口は `GET /auth/config`、`POST /auth/login/ldap`、`POST /auth/password-reset/request`、`POST /auth/password-reset/confirm` です。セキュリティエラーは `SECURITY_BUSY (hai:iam:023)` と `PASSWORD_RESET_DISABLED (hai:iam:024)` です。`ldap` などの任意項目は YAML に宣言してから環境変数で上書きするか、`HAI_IAM` の JSON 値でモジュール設定全体を置き換えます。既存項目には `HAI_IAM_LOGIN_OTP` などを使用できます。
|
|
817
836
|
|
|
818
837
|
`passwordReset.mode` は `email`(既定、`password_reset`)または `phone`(`password_reset_sms`)を選び、公開設定に応じて復旧フォームが入力欄を切り替えます。
|
|
819
838
|
|
|
820
|
-
|
|
839
|
+
#### Markdown の規約
|
|
821
840
|
|
|
822
841
|
`agreements.userAgreementMarkdown` と `privacyPolicyMarkdown` で設定から直接本文を読み込みます。YAML の `|` は段落と改行を保持します。Modal は安全な MarkdownRenderer を再利用し、生 HTML、スクリプト、コードツールを無効にします。同じ規約に本文と URL を指定した場合は本文を優先し、URL は任意の外部表示リンクとして使用します。URL のみの場合は隔離 iframe で表示します。
|
|
823
842
|
|
|
@@ -840,7 +859,7 @@ agreements:
|
|
|
840
859
|
# privacyPolicyUrl: https://example.com/privacy
|
|
841
860
|
```
|
|
842
861
|
|
|
843
|
-
|
|
862
|
+
### エラー処理
|
|
844
863
|
|
|
845
864
|
すべての操作は `HaiResult<T>` を返し、`HaiIamError` で分岐します:
|
|
846
865
|
|
|
@@ -855,10 +874,20 @@ if (!result.success) {
|
|
|
855
874
|
}
|
|
856
875
|
```
|
|
857
876
|
|
|
858
|
-
|
|
877
|
+
会話と refresh 消費状態を `hai:iam:sessions:{SHA256(userId)}` のバージョン付き文書にまとめ、独立した対応キーと索引を廃止します。Bearer Token は認証には使えない分区ヒントと256ビットの乱数を含み、クライアントは不透明な文字列として扱います。更新時は Session.version を再検証します。Memory は単一プロセスのテスト用で、本番は全ノードが Redis(Cluster 対応)を共有します。競合は最新状態から再試行し、上限で CONFLICT を返します。応答喪失で結果が不明なら認証を拒否し再ログインします。すべての会話は同じユーザー索引で管理されます。
|
|
878
|
+
|
|
879
|
+
`iam.session.update(token, patch, expectedVersion)` は読み取った Session.version が必須です。旧版は CONFLICT、失効 Token は SESSION_NOT_FOUND を返し、新しい状態を上書きしません。自動延長の時刻だけの更新は内容版を変更せず、通常の並行リクエストを許容します。権限一括更新は最初の Token/版集合に限定し、後続ログインに古い値を適用しません。
|
|
880
|
+
|
|
881
|
+
## 開発とテスト
|
|
882
|
+
|
|
883
|
+
公開例は `@h-ai/iam` を使用し、既定値を省略できる `IamConfigInput` と解析済み `IamConfig` を区別します。依存を先に初期化し、`HaiResult.success` を確認し、会話更新には読取時の `version`、通知には永続的な冪等キーを使います。「模块内部上下文」の戦略・倉庫例はアプリ向け深い import の契約ではありません。チェッカーは型だけを確認し、Redis/DB・LDAP・配送の動作には対応する統合テストが必要です。[JSDoc 規約](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)も参照してください。
|
|
859
884
|
|
|
860
885
|
```bash
|
|
861
|
-
pnpm
|
|
886
|
+
pnpm --filter @h-ai/iam typecheck
|
|
887
|
+
pnpm --filter @h-ai/iam lint
|
|
888
|
+
pnpm --filter @h-ai/iam build
|
|
889
|
+
pnpm --filter @h-ai/iam test
|
|
890
|
+
node scripts/check-module-jsdoc.mjs --package iam
|
|
862
891
|
```
|
|
863
892
|
|
|
864
893
|
## ライセンス
|