@h-ai/iam 0.1.0-alpha.73 → 0.1.0-alpha.75
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 +29 -15
- package/README.ja.md +28 -14
- package/README.md +28 -14
- package/dist/browser.d.ts +51 -2
- package/dist/browser.js +1 -1
- package/dist/{chunk-22SERPFD.js → chunk-KPG3M34Q.js} +37 -15
- package/dist/chunk-KPG3M34Q.js.map +1 -0
- package/dist/index.js +452 -190
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/dist/chunk-22SERPFD.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm add @h-ai/iam
|
|
|
33
33
|
- `@h-ai/reldb`: persistence for users/roles/permissions; **initialize before iam.init()**.
|
|
34
34
|
- `@h-ai/cache`: sessions/OTP/one-time tickets; **initialize before iam.init()**.
|
|
35
35
|
- `@h-ai/crypto`: password hashing, used internally and initialized automatically.
|
|
36
|
-
- `@h-ai/audit`: audit
|
|
36
|
+
- `@h-ai/audit`: automatic IAM security events after host initialization. An unused collector is not called; enabled audit failures remain observable and caller-owned transactions must roll back.
|
|
37
37
|
|
|
38
38
|
- `@h-ai/reach`: **optional**, installed, initialized and injected by the application for standard notifications; custom callbacks do not require Reach.
|
|
39
39
|
- `ldapts`: **optional**, loaded only when LDAP is explicitly enabled without an injected `ldapClientFactory`.
|
|
@@ -221,7 +221,7 @@ IAM is a **lifecycle singleton** managed through `iam.init()` / `iam.close()`. F
|
|
|
221
221
|
|
|
222
222
|
```
|
|
223
223
|
iam.init()
|
|
224
|
-
├─ session (cached sessions;
|
|
224
|
+
├─ session (cached sessions; current credential state uses shared cache/DB fallback)
|
|
225
225
|
├─ authz (RBAC roles/permissions; batched DB reads)
|
|
226
226
|
├─ authn (authentication; depends on session + authz)
|
|
227
227
|
├─ user (users; depends on session + authz + passwordStrategy)
|
|
@@ -278,10 +278,10 @@ login(credentials)
|
|
|
278
278
|
```
|
|
279
279
|
verifyToken(accessToken)
|
|
280
280
|
→ session.get(accessToken) → cache lookup
|
|
281
|
-
→ Read the
|
|
281
|
+
→ Read current user/credential state from the shared epoch cache; check status, expiry and session-bound credentialVersion, falling back to DB when the cache is not trustworthy
|
|
282
282
|
→ Extend TTL automatically when sliding renewal is enabled
|
|
283
|
-
→
|
|
284
|
-
→ Reject
|
|
283
|
+
→ Reuse that user profile and read current roles/permissions from the shared epoch cache; cold authorization reads use one JOIN
|
|
284
|
+
→ Reject required state read failures, missing/disabled/expired users or credential-version mismatch; intersect API Key scopes again
|
|
285
285
|
→ Return current Session (userId, roles, permissions)
|
|
286
286
|
```
|
|
287
287
|
|
|
@@ -427,9 +427,8 @@ Role lists use `iam.authz.getRoleUserCounts(roleIds)` to aggregate actual member
|
|
|
427
427
|
```
|
|
428
428
|
checkPermission(userId, permission)
|
|
429
429
|
→ Return true if RBAC is disabled
|
|
430
|
-
→
|
|
431
|
-
→
|
|
432
|
-
→ Read these roles' permission codes in one DB query; return errors on read failure
|
|
430
|
+
→ Read current roles/permissions from the shared epoch cache; cold reads use one JOIN
|
|
431
|
+
→ Return true for the super-admin role; return errors on required state read failure
|
|
433
432
|
→ Match each permission exactly or by wildcard (`admin:*` matches `admin:read`)
|
|
434
433
|
→ Return true / false
|
|
435
434
|
```
|
|
@@ -496,7 +495,7 @@ async function currentAuthorization(userId: string) {
|
|
|
496
495
|
- **Wildcard matching**: `admin:*` matches `admin:read`, `admin:write:detail`, etc.; one `*` matches everything after the colon.
|
|
497
496
|
- **Super-admin role**: `rbac.superAdminRole` (default `super_admin`) automatically grants all permissions.
|
|
498
497
|
- **Transactions**: `deleteRole` / `deletePermission` clear associations before deleting entities in a DB transaction.
|
|
499
|
-
- **Session synchronization**: cache updates are best-effort. Protected requests use `iam.auth.verifyToken` to
|
|
498
|
+
- **Session synchronization**: cache updates are best-effort. Protected requests use `iam.auth.verifyToken` to check current user/credential state and authorization through the shared epoch cache, with DB fallback when needed. Required state read failure rejects authentication. `iam.session.get/verifyToken` reuses the same current user/credential read and checks status, expiry and `credentialVersion`, but its role/permission fields remain a session snapshot and do not replace current authorization. `iam.authz.getUserAuthorization(userId)` uses a single JOIN cold read and shared cache.
|
|
500
499
|
- **Seed data**: `seedDefaultData: true` idempotently creates admin/user/guest roles and basic permissions.
|
|
501
500
|
|
|
502
501
|
---
|
|
@@ -612,7 +611,7 @@ async function disableUser(userId: string) {
|
|
|
612
611
|
|
|
613
612
|
#### API keys — `iam.apiKey`
|
|
614
613
|
|
|
615
|
-
`scopes` intersect the subject's permissions and support `*` and namespace `:*`. The default empty array grants identity only, without operation permissions. Restricted keys have empty session `roles` and use permission guards; explicit `*` never exceeds subject authorization. API Key login binds the key ID: get/verifyToken/refresh recheck its persistent state and reject revoked, disabled, expired keys or
|
|
614
|
+
`scopes` intersect the subject's permissions and support `*` and namespace `:*`. The default empty array grants identity only, without operation permissions. Restricted keys have empty session `roles` and use permission guards; explicit `*` never exceeds subject authorization. API Key login binds the key ID: get/verifyToken/refresh recheck its persistent state and reject revoked, disabled, expired keys or required state read failures. Standalone verifyApiKey returns verified key metadata and still requires subject/operation authorization.
|
|
616
615
|
|
|
617
616
|
##### Key types
|
|
618
617
|
|
|
@@ -626,7 +625,8 @@ async function disableUser(userId: string) {
|
|
|
626
625
|
|
|
627
626
|
```
|
|
628
627
|
verifyApiKey(rawKey)
|
|
629
|
-
→
|
|
628
|
+
→ Read the shared epoch cache by SHA-256(rawKey) fingerprint; check expiry on hits
|
|
629
|
+
→ On a cold read, extract prefix from rawKey (prefix + first 8 characters)
|
|
630
630
|
→ Query candidate keys by prefix from DB
|
|
631
631
|
→ Verify each hash synchronously
|
|
632
632
|
→ If valid, check expiration/disabled state
|
|
@@ -830,7 +830,7 @@ SvelteKit routes export handlers from `kit.auth.createApi(iam)`; use `kit.auth.p
|
|
|
830
830
|
|
|
831
831
|
Agreements open in a Modal without losing form input. URL pages must allow iframe embedding; scripts are disabled and a new-window fallback is available. LoginForm/RegisterForm expose `header/extraFields/footer` snippets. AuthPanel forwards `loginHeader/loginFields/loginFooter` and `registerHeader/registerFields/registerFooter`; application state and actions handle additional fields. See [UI usage](../ui/README.en.md#configuration-driven-authentication).
|
|
832
832
|
|
|
833
|
-
All nodes share the same database and Redis; Memory is for single-process tests. OTP code, attempts and cooldown share one identity key with CAS publication and consumption. Conflicts reject stale generations and consumption preserves cooldown. Password reset hash, expiry, attempts and resetVersion live in the user database row. Policy failures count, and one conditional SQL statement changes the password, consumes the token and advances credentialVersion. New tokens, later attempts, disablement or expiry reject delayed commits. Requesting a reset only advances resetVersion; successful password changes advance credentialVersion. Authentication captures the verified credential generation; session creation, authentication and refresh check
|
|
833
|
+
All nodes share the same database and Redis; Memory is for single-process tests. OTP code, attempts and cooldown share one identity key with CAS publication and consumption. Conflicts reject stale generations and consumption preserves cooldown. Password reset hash, expiry, attempts and resetVersion live in the user database row. Policy failures count, and one conditional SQL statement changes the password, consumes the token and advances credentialVersion. New tokens, later attempts, disablement or expiry reject delayed commits. Requesting a reset only advances resetVersion; successful password changes advance credentialVersion. Authentication captures the verified credential generation; session creation, authentication and refresh check current state via shared generation cache or safe DB fallback, so failed cache cleanup cannot authorize old sessions. Notification callbacks run after publication and receive { expiresAt, idempotencyKey } as their third argument. Applications and Reach providers must durably deduplicate that key. Exceptions/timeouts mean delivery may have succeeded: the result is unknown, with no automatic resend or rollback. Exactly-once external effects require receiver-side idempotency.
|
|
834
834
|
|
|
835
835
|
Standard endpoints: `GET /auth/config`, `POST /auth/login/ldap`, `POST /auth/password-reset/request`, `POST /auth/password-reset/confirm`. Security errors: `SECURITY_BUSY (hai:iam:023)` and `PASSWORD_RESET_DISABLED (hai:iam:024)`. Declare optional fields such as `ldap` in YAML before overriding them, or replace the module configuration with a JSON value in `HAI_IAM`. Existing fields support overrides such as `HAI_IAM_LOGIN_OTP`.
|
|
836
836
|
|
|
@@ -882,6 +882,12 @@ Sessions and refresh consumption share one versioned `hai:iam:sessions:{SHA256(u
|
|
|
882
882
|
|
|
883
883
|
`iam.session.update(token, patch, expectedVersion)` requires the Session.version from the read. Stale versions return CONFLICT; revoked tokens return SESSION_NOT_FOUND. Timestamp-only sliding updates keep the content version, allowing normal parallel requests. Batch permission updates bind the initial token/version set and never apply delayed snapshots to later sign-ins; newer content versions require re-reading authorization before retry.
|
|
884
884
|
|
|
885
|
+
### Shared authorization cache
|
|
886
|
+
|
|
887
|
+
Redis caches nonsecret account/credential state, role/permission summaries, sensitive operations and verified API Key DTOs for 60 seconds. Cache values omit database password/Key verification hashes, raw keys and reset tokens; API Key cache addresses use a one-way SHA-256 fingerprint of the raw Key. Account/password/Key expiry is evaluated against current time on every check; authorization cold reads use one JOIN. IAM writers establish a shared dirty marker before DB mutation and advance the epoch after commit/rollback, including caller-owned transactions. A racing cold read cannot publish an obsolete cache entry. Normal hot checks avoid PG; writes, unconfirmed invalidation or cache read failure use the database. Failure of the session Redis store still rejects authentication.
|
|
888
|
+
|
|
889
|
+
Failure to register invalidation rejects a write before mutation. Failed invalidation after commit preserves the successful mutation and persistent dirty marker, so later checks keep using DB. Crashed writers or unknown commit results never expire that marker by TTL; alert and repair cache state in a maintenance window after confirming no IAM write transaction remains. All nodes must upgrade together and write via IAM APIs. Raw SQL, old writers and independent table edits require quiesced writes and full IAM cache invalidation. Verified raw Key cache hits avoid repeated expensive hashes; lastUsedAt is sampled during cold verification, with up to about one minute lag.
|
|
890
|
+
|
|
885
891
|
## Self-hosted interactive challenges
|
|
886
892
|
|
|
887
893
|
captcha.enabled defaults to false. Enable self-hosted ALTCHA proof of work with ttlSeconds (default120, 1–600 seconds), cost (default5000, 100–50000) and maxCounter (default100, 1–2000). No external CAPTCHA account is needed. HMAC secrets and one-use state remain in the existing shared Cache; production still requires a shared backend. Computational challenges increase automation cost and do not guarantee a human requester.
|
|
@@ -892,17 +898,19 @@ Standard Kit/Serv expose POST /auth/captcha (Kit same-origin /api prefix) and pr
|
|
|
892
898
|
|
|
893
899
|
## Optional password and login protection
|
|
894
900
|
|
|
895
|
-
`password.minLength/maxLength` and individual `require*` flags control strength. `minCharacterClasses` accepts any N of uppercase, lowercase, digits and special characters; 0 adds no count requirement. CLI/admin examples
|
|
901
|
+
`password.minLength/maxLength` and individual `require*` flags control strength. `minCharacterClasses` accepts any N of uppercase, lowercase, digits and special characters; 0 adds no count requirement. CLI/admin examples default to 8–128 characters with no extra class count; set `minCharacterClasses: 3` to require any three classes. All server creation/change/reset paths share validation.
|
|
896
902
|
|
|
897
903
|
`expirationDays: 0` disables expiry; positive values are days. `preventSamePassword` is independent. Expiry/initial-change policies reject the current password. `requireInitialChange` applies to administrator-created/reset credentials, rather than self-chosen registration passwords. Initial/expired credentials cannot authorize sessions or refresh existing ones. `iam.user.renewPassword({ identifier, password, newPassword })` verifies the old password and requires fresh login; Kit/Serv and AuthPanel provide the completion flow.
|
|
898
904
|
|
|
899
|
-
`security.lockoutEnabled` defaults to true. `failureWindow` defaults to 86400 seconds from the first failure, `maxLoginAttempts` is the threshold, and `lockoutDuration` is seconds locked. Examples use 1200 seconds. Disabling ignores historical locks. Shared database updates atomically enforce counts/thresholds.
|
|
905
|
+
`security.lockoutEnabled` defaults to true in the framework Schema; simplified CLI/admin examples explicitly disable it until enabled by the project. `failureWindow` defaults to 86400 seconds from the first failure, `maxLoginAttempts` is the threshold, and `lockoutDuration` is seconds locked. Examples use 1200 seconds. Disabling ignores historical locks. Shared database updates atomically enforce counts/thresholds.
|
|
900
906
|
|
|
901
907
|
`initialCredentials.enabled` enables `iam.user.inviteUser()`: private random initial secrets are never returned or sent; only short-lived single-use setup invitations are delivered. This reuses passwordReset mode, expiry, attempt limits and sensitive delivery. Missing delivery/disabled reset prevents creation. Unconfirmed delivery returns failure while retaining the account/invitation; verify delivery before requesting another reset notification.
|
|
902
908
|
|
|
903
909
|
`loginAccess.enabled` enables restrictions. `allowIps/denyIps` accept IPv4/IPv6/CIDR with deny precedence; empty lists leave that dimension unrestricted. `windows` use `days` (Monday1–Sunday7), `start/end` (HH:mm, inclusive start/exclusive end, overnight supported), and IANA `timezone`. Login methods take trusted `AuthRequestContext` as their second argument: Kit uses platform getClientAddress(), Serv uses context.ip resolved through trusted proxies. Never copy arbitrary forwarding headers or credential JSON addresses. Address restrictions fail closed without a trusted address and cover automatic registration login/API Key login.
|
|
904
910
|
|
|
905
|
-
`reauthentication.enabled/operations` select sensitive operations; `ttlSeconds` bounds proof lifetime. `reauthenticate(token, password, operation)` issues a single-use proof; authorized handlers call `checkSensitiveOperation(token, operation, ticket)` after permission/resource checks. Proofs bind subject/session/operation and grant no extra permissions. Serv IAM writes/admin guards read `x-hai-reauthentication`; Kit business handlers use `kit.guard.requireSensitive(iam, event, operation)` with project-defined IDs. Keep proof tickets briefly in memory. Public configuration exposes switches/operation IDs only.
|
|
911
|
+
`reauthentication.enabled/operations` select sensitive operations; `ttlSeconds` bounds proof lifetime. `reauthenticate(token, password, operation)` issues a single-use proof; authorized handlers call `checkSensitiveOperation(token, operation, ticket)` after permission/resource checks. Proofs bind subject/session/operation and grant no extra permissions. Serv IAM writes/admin guards read `x-hai-reauthentication`; Kit business handlers use `kit.guard.requireSensitive(iam, event, operation)` with project-defined IDs. Keep proof tickets briefly in memory. Public configuration exposes switches/operation IDs only. In the standard admin/fullstack UI, an already signed-in user who selects a sensitive action confirms their current account password in a dialog. Cancel does not submit, an incorrect password stops the action, and success continues the original action without creating another login session.
|
|
912
|
+
|
|
913
|
+
RBAC permissions accept `sensitiveOperations: string[]`, persisted as nullable `sensitive_operations`. Trusted management code can call `iam.authz.setPermissionSensitiveOperations(permissionId, operations)` and must check `success`. An empty array removes only that permission's additions. With `reauthentication.enabled`, rules from all permission records are combined with file `operations` and enforced live. IDs must match actual business guards; they grant no permissions. Async `iam.getPublicConfig()` returns the combined public policy and fails on database read errors; synchronous `iam.publicConfig` retains the file snapshot only. Disabling RBAC ignores database rules. Migration adds a nullable JSON column while preserving authorization relationships.
|
|
906
914
|
|
|
907
915
|
## Account type and lifecycle
|
|
908
916
|
|
|
@@ -924,10 +932,16 @@ After RelDB initialization, `migrateIam({ dryRun: true })` previews changes with
|
|
|
924
932
|
|
|
925
933
|
When audit is initialized and capture.enabled is on, IAM records important account, authentication, password, authorization, API Key and session events, including failed/denied outcomes and safe reason codes. Initialize audit before IAM and close in reverse order. Passwords, one-time proofs and tokens are never copied into events. Only successful login establishes an actor; trusted service/CLI callers supply actor/correlation through audit.withContext. Events inside an external authorization transaction commit or roll back with that transaction; record failed attempts after rollback at the caller boundary. Audit failures emit diagnostics without converting an applied business result into an unsafe retry.
|
|
926
934
|
|
|
935
|
+
`session.terminalLimits: { web: 2, app: 1 }` limits sessions per terminal type; `{}` disables this policy. Unlisted types are rejected when enabled, and older sessions count as `web`. `terminalLimitMode` defaults to `reject`; `replace` evicts only the oldest sessions of the same type. Global `maxSessions` and `singleDevice` still apply in the same atomic user CAS. Server login context `terminalType` and standard client `auth.terminalType` select a group; this is not hardware identification or authorization.
|
|
936
|
+
|
|
937
|
+
For in-place recovery, use UI `createSessionRecovery` / `SessionRecoveryDialog` with SDK `recoverSession`. Normal password/OTP/LDAP login policies still apply; `expectedUserId` restricts session issuance to the original principal. Only an explicitly authentication-rejected request may be retried once. Ambiguous network failures are not replayed. In-memory drafts lost through document reload/closure cannot be restored.
|
|
938
|
+
|
|
927
939
|
## Page exit session policy
|
|
928
940
|
|
|
929
941
|
`session.endOnPageHide` defaults to `false`. Hosts call `actions.bindPageExit({ enabled: config.session.endOnPageHide })` and invoke its returned cleanup on unmount; standard admin/H5/fullstack templates already bind it. A non-BFCache `pagehide` (including reload and full document navigation) makes a best effort POST to `/auth/session/close`, revoking the held access and related refresh through the existing CSRF/encrypted transport with `keepalive` and no automatic refresh. This response does not clear cookies/local credentials, avoiding a late old response overwriting a new login. Explicit logout still revokes and clears current credentials normally. Browsers may omit page exit requests; configure server `idleTimeout`/`absoluteTimeout` as appropriate. Immediate revocation on browser close is not guaranteed.
|
|
930
942
|
|
|
943
|
+
The standard `/auth/me` endpoint also checks `iam.profile.update` / `iam.profile.avatar`. Mixed updates prefer an enabled profile rule, otherwise the avatar rule. `authz.getSensitiveOperations(operation?)` can query one operation; omit it to list database rules. Super-administrator identity is read from shared roles on every check. Default seeding rereads concurrent unique conflicts and propagates permission-assignment failures.
|
|
944
|
+
|
|
931
945
|
## Development and testing
|
|
932
946
|
|
|
933
947
|
Public examples import from `@h-ai/iam` and distinguish defaultable `IamConfigInput` from parsed `IamConfig`. Initialize server dependencies, check `HaiResult.success`, update sessions with their observed `version`, and reuse persistent notification idempotency keys. Strategy/repository examples marked as internal context do not define application deep imports. The checker verifies types only; Redis/database, LDAP and delivery behavior still require their integration tests. See the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证).
|
package/README.ja.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm add @h-ai/iam
|
|
|
33
33
|
- `@h-ai/reldb`:ユーザー・ロール・権限の永続化。**iam.init() より先に初期化**。
|
|
34
34
|
- `@h-ai/cache`:セッション・OTP・一度限りのチケット。**iam.init() より先に初期化**。
|
|
35
35
|
- `@h-ai/crypto`:内部で使用するパスワードハッシュ。自動初期化。
|
|
36
|
-
- `@h-ai/audit
|
|
36
|
+
- `@h-ai/audit`:ホストが初期化した場合に IAM の安全イベントを自動記録します。未使用時は収集せず、使用中の書込失敗は記録し、外部トランザクションはロールバックします。
|
|
37
37
|
|
|
38
38
|
- `@h-ai/reach`:**任意**。アプリがインストール、初期化、注入して通知テンプレートを使用します。独自送信コールバックでは不要です。
|
|
39
39
|
- `ldapts`:**任意**。LDAP を明示的に有効化し、`ldapClientFactory` がない場合だけ読み込みます。
|
|
@@ -221,7 +221,7 @@ IAM は `iam.init()` / `iam.close()` で状態を管理する**ライフサイ
|
|
|
221
221
|
|
|
222
222
|
```
|
|
223
223
|
iam.init()
|
|
224
|
-
├─ session
|
|
224
|
+
├─ session (キャッシュによるセッション、現在の資格情報は共有キャッシュ/DB参照)
|
|
225
225
|
├─ authz (RBAC ロール・権限、DB 一括検索)
|
|
226
226
|
├─ authn (認証、session + authz に依存)
|
|
227
227
|
├─ user (ユーザー管理、session + authz + passwordStrategy に依存)
|
|
@@ -278,10 +278,10 @@ login(credentials)
|
|
|
278
278
|
```
|
|
279
279
|
verifyToken(accessToken)
|
|
280
280
|
→ session.get(accessToken) → キャッシュ検索
|
|
281
|
-
→
|
|
281
|
+
→ 共有世代キャッシュから現在のユーザー/資格情報を読み、有効状態・期限・セッションの credentialVersion を検証。不確かな場合は DB を参照
|
|
282
282
|
→ 自動延長モードなら TTL を延長
|
|
283
|
-
→
|
|
284
|
-
→
|
|
283
|
+
→ 上記のユーザー情報を再利用し、共有世代キャッシュから現在の認可を取得。コールド読込は単一 JOIN
|
|
284
|
+
→ 必要な状態の読取失敗、ユーザー不在・無効・期限切れ、資格情報の世代不一致は拒否し、API Key scope を再適用
|
|
285
285
|
→ 現在の Session を返す(userId、roles、permissions)
|
|
286
286
|
```
|
|
287
287
|
|
|
@@ -427,9 +427,8 @@ Session.credentialVersion は作成時の DB 資格情報世代に結び付け
|
|
|
427
427
|
```
|
|
428
428
|
checkPermission(userId, permission)
|
|
429
429
|
→ RBAC 無効なら true
|
|
430
|
-
→
|
|
431
|
-
→
|
|
432
|
-
→ 各ロールの権限コードを DB で一括取得。読み取り失敗はエラーを返す
|
|
430
|
+
→ 共有世代キャッシュから現在のロール・権限を取得。コールド読込は単一 JOIN
|
|
431
|
+
→ 最高管理者なら true。必要な状態の読取失敗はエラーを返す
|
|
433
432
|
→ 厳密・ワイルドカード照合(`admin:*` は `admin:read` に一致)
|
|
434
433
|
→ true / false を返す
|
|
435
434
|
```
|
|
@@ -496,7 +495,7 @@ async function currentAuthorization(userId: string) {
|
|
|
496
495
|
- **ワイルドカード**:`admin:*` は `admin:read`、`admin:write:detail` などに一致。1 つの `*` がコロン以降全体に一致します。
|
|
497
496
|
- **最高管理者**:`rbac.superAdminRole`(既定 `super_admin`)はすべての権限を持ちます。
|
|
498
497
|
- **トランザクション**:`deleteRole` / `deletePermission` は DB トランザクションで関連を削除してから本体を削除。
|
|
499
|
-
- **セッション同期**:キャッシュ更新は best-effort です。保護されたリクエストは `iam.auth.verifyToken`
|
|
498
|
+
- **セッション同期**:キャッシュ更新は best-effort です。保護されたリクエストは `iam.auth.verifyToken` で共有世代キャッシュで現在のユーザー/資格情報と認可を検証し、不確かな場合は DB を参照します。必要な読取が失敗すれば拒否します。`iam.session.get/verifyToken` は同じユーザー/資格情報の読取を再利用し、有効状態・期限・`credentialVersion` を検証しますが、ロール・権限はセッションのスナップショットであり、現在の認可を代替しません。`iam.authz.getUserAuthorization(userId)` はロール JOIN と権限一括検索を共有します。
|
|
500
499
|
- **初期データ**:`seedDefaultData: true` で admin/user/guest と基本権限を冪等に作成。
|
|
501
500
|
|
|
502
501
|
---
|
|
@@ -612,7 +611,7 @@ async function disableUser(userId: string) {
|
|
|
612
611
|
|
|
613
612
|
#### API Key — `iam.apiKey`
|
|
614
613
|
|
|
615
|
-
`scopes` は主体の権限との共通範囲を適用し、`*` と名前空間 `:*` に対応します。既定の空配列は本人確認のみで操作権限を付与しません。制限付き key のセッション `roles` は空で permission ガードを使用し、明示的な `*` も主体の権限を超えません。ログインは key ID に結び付き、get/verifyToken/refresh
|
|
614
|
+
`scopes` は主体の権限との共通範囲を適用し、`*` と名前空間 `:*` に対応します。既定の空配列は本人確認のみで操作権限を付与しません。制限付き key のセッション `roles` は空で permission ガードを使用し、明示的な `*` も主体の権限を超えません。ログインは key ID に結び付き、get/verifyToken/refresh は共有キャッシュまたは DB で現在の状態を確認し、失効・無効・期限切れ・必要な状態の読取失敗を拒否します。verifyApiKey 単独は鍵のメタデータ検証のみで、主体・操作の認可は別途必要です。
|
|
616
615
|
|
|
617
616
|
##### 主要な型
|
|
618
617
|
|
|
@@ -626,7 +625,8 @@ async function disableUser(userId: string) {
|
|
|
626
625
|
|
|
627
626
|
```
|
|
628
627
|
verifyApiKey(rawKey)
|
|
629
|
-
→ rawKey
|
|
628
|
+
→ SHA-256(rawKey) 指紋で共有世代キャッシュを参照し、ヒット時も期限を確認
|
|
629
|
+
→ コールド読込では rawKey から接頭辞取得(prefix + 最初の 8 文字)
|
|
630
630
|
→ DB で接頭辞による候補検索
|
|
631
631
|
→ 各 hash を同期検証
|
|
632
632
|
→ 成功後に期限・無効化を確認
|
|
@@ -878,6 +878,12 @@ if (!result.success) {
|
|
|
878
878
|
|
|
879
879
|
`iam.session.update(token, patch, expectedVersion)` は読み取った Session.version が必須です。旧版は CONFLICT、失効 Token は SESSION_NOT_FOUND を返し、新しい状態を上書きしません。自動延長の時刻だけの更新は内容版を変更せず、通常の並行リクエストを許容します。権限一括更新は最初の Token/版集合に限定し、後続ログインに古い値を適用しません。
|
|
880
880
|
|
|
881
|
+
### 共有認可キャッシュ
|
|
882
|
+
|
|
883
|
+
Redisは秘密を含まないアカウント/資格情報の状態、ロール・権限、機密操作、検証済みAPI Key DTOを60秒保存します。キャッシュ値にはDBのパスワード/Key検証ハッシュ、平文Key、再設定トークンを保存しません。API Keyのキャッシュアドレスには平文Keyの一方向SHA-256指紋を使います。期限は毎回現在時刻で検証し、認可のコールド読込は単一JOINです。IAM書込はDB更新前に共有dirtyマーカーを登録し、外部トランザクションもcommit/rollback完了後に世代を更新します。競合したコールド読込は古い値を公開しません。通常の高頻度チェックはPGを照会せず、更新中・無効化未確認・キャッシュ読込障害時はDBを参照します。セッションRedis自体の障害は認証を拒否します。
|
|
884
|
+
|
|
885
|
+
無効化登録に失敗した書込はDB更新前に拒否します。commit後の無効化失敗は成功済み更新を取り消した扱いにせず、永続dirtyマーカーでDB参照を継続します。プロセスクラッシュやcommit結果不明をTTLで解除せず、IAM書込がない保守時間帯に確認して修復してください。全ノード同時更新とIAM API経由の書込が必要です。直接SQL、旧版ノード、外部テーブル更新は書込停止とIAMキャッシュ全体の無効化が必要です。平文Key検証のヒットは高負荷ハッシュを省略し、lastUsedAtはコールド検証時に記録するため最大約1分遅れます。
|
|
886
|
+
|
|
881
887
|
## 自己ホスト型の対話チャレンジ
|
|
882
888
|
|
|
883
889
|
captcha.enabled は既定で false。ALTCHA の計算チャレンジを自己ホストし、ttlSeconds(既定120、1–600秒)、cost(既定5000、100–50000)、maxCounter(既定100、1–2000)で期限と計算量を指定します。外部 CAPTCHA アカウントは不要で、HMAC 秘密と一度限りの状態は既存の共有 Cache にのみ保存します。本番では共有バックエンドが必要です。計算量は自動化のコストを増やしますが、利用者が人間であることは保証しません。
|
|
@@ -888,17 +894,19 @@ Kit/Serv の標準入口は POST /auth/captcha(Kit は同一生成元の /api
|
|
|
888
894
|
|
|
889
895
|
## 任意のパスワード・ログイン保護
|
|
890
896
|
|
|
891
|
-
`password.minLength/maxLength` と各 `require*` は強度を指定します。`minCharacterClasses` は大文字、小文字、数字、特殊文字の任意 N 種類を要求し、0は追加制約なしです。CLI/admin
|
|
897
|
+
`password.minLength/maxLength` と各 `require*` は強度を指定します。`minCharacterClasses` は大文字、小文字、数字、特殊文字の任意 N 種類を要求し、0は追加制約なしです。CLI/admin 例の既定は8–128文字で、追加の文字種数制限はありません。任意3種類を要求する場合は `minCharacterClasses: 3` を明示します。作成・変更・リセットで同じ検証を使います。
|
|
892
898
|
|
|
893
899
|
`expirationDays: 0` は定期変更を無効にし、正数は日数です。`preventSamePassword` は単独でも有効化でき、有効期限制約・初回変更時は旧パスワードを拒否します。`requireInitialChange` は管理者作成・リセットの認証情報に適用され、自分で登録時に設定したパスワードは再変更不要です。初期・期限切れパスワードでは通常セッションを取得・更新できません。`iam.user.renewPassword({ identifier, password, newPassword })` で旧パスワードを確認し、変更後に再ログインします。Kit/Serv と AuthPanel はこの完了経路を提供します。
|
|
894
900
|
|
|
895
|
-
`security.lockoutEnabled`
|
|
901
|
+
`security.lockoutEnabled` はフレームワークSchemaでは既定で有効ですが、簡易CLI/admin例では明示的に無効にし、必要に応じて有効化します。`failureWindow` は最初の失敗からの固定期間(既定86400秒)、`maxLoginAttempts` は閾値、`lockoutDuration` はロック秒数です。例は1200秒です。無効時は既存ロックも無視します。共有DBで計数・閾値を原子的に処理します。
|
|
896
902
|
|
|
897
903
|
`initialCredentials.enabled` は `iam.user.inviteUser()` を有効にします。利用者ごとに非公開のランダム秘密を作り、短期・単回の設定招待だけ送ります。初期秘密は返却・送信しません。passwordReset の通知・期限・試行回数を再利用し、受信者は再設定画面でパスワードを選びます。通知未設定・リセット無効なら作成しません。配送未確認時は失敗とし、作成済みアカウント・招待を保持します。配送を確認後に再設定通知を要求してください。
|
|
898
904
|
|
|
899
905
|
`loginAccess.enabled` は制限を有効にします。`allowIps/denyIps` は IPv4/IPv6/CIDR を受け付け、拒否を優先します。空リストは対象を制限しません。`windows` は `days`(月曜1〜日曜7)、`start/end`(HH:mm、開始を含み終了を含まず、日跨ぎ対応)、IANA `timezone` を指定します。ログインの第2引数は信頼できる `AuthRequestContext` です。Kit はプラットフォーム getClientAddress()、Serv は信頼プロキシで解決した context.ip を使います。任意の転送ヘッダー・入力 JSON の IP を信用してはいけません。アドレス制限時に信頼できるアドレスがなければ拒否し、自動ログイン・API Key にも適用します。
|
|
900
906
|
|
|
901
|
-
`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)` を使います。証明は短時間メモリに保持し、公開設定は開閉・操作リストのみです。
|
|
907
|
+
`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では、ログイン済み利用者が機密操作を選ぶと、現在のアカウントのパスワード確認ダイアログを表示します。取消時は送信せず、誤りなら操作を止め、成功時だけ元の操作を続けます。別のログインセッションは作成しません。
|
|
908
|
+
|
|
909
|
+
RBAC 権限は `sensitiveOperations: string[]` を持ち、可空 JSON 列 `sensitive_operations` に保存します。管理側は `iam.authz.setPermissionSensitiveOperations(permissionId, operations)` の `success` を確認します。空配列はその権限の追加規則だけを削除します。`reauthentication.enabled` が有効なら全権限の規則とファイルの `operations` を和集合にして即時検証します。ID は実際の業務ガードと一致させ、既存の認可を省略しません。非同期 `iam.getPublicConfig()` は統合した公開方針を返し、DB 読取障害時は失敗します。同期 `iam.publicConfig` はファイルのスナップショットのみです。RBAC 無効時は DB 規則を無視し、移行は既存の認可関係を保持します。
|
|
902
910
|
|
|
903
911
|
## アカウント種別とライフサイクル
|
|
904
912
|
|
|
@@ -920,10 +928,16 @@ RelDB 初期化後の `migrateIam({ dryRun: true })` は読み取り専用です
|
|
|
920
928
|
|
|
921
929
|
audit が初期化済みで capture.enabled が有効なら、IAM はアカウント・認証・パスワード・権限・API Key・セッションの重要イベントと失敗/拒否の安全な理由コードを記録します。audit を IAM より先に初期化し、逆順に終了してください。パスワード・単回証明・トークンは記録しません。ログイン成功後に主体を確定し、信頼できるサービス/CLI は audit.withContext で操作者と関連文脈を設定します。外部権限トランザクション内のイベントは一緒にコミット/ロールバックされ、失敗試行はロールバック後に呼出境界で記録します。監査失敗は診断を出し、反映済み業務を危険な再試行に変えません。
|
|
922
930
|
|
|
931
|
+
`session.terminalLimits: { web: 2, app: 1 }` は端末種別ごとの上限で、既定 `{}` は無効です。有効時は未記載の種別を拒否し、旧セッションは `web` とします。`terminalLimitMode` の既定 `reject` は新規ログインを拒否し、`replace` は同種の古いセッションだけを置換します。全体の `maxSessions` と `singleDevice` も同じ CAS 内で適用します。ログインコンテキスト `terminalType` / 標準クライアント `auth.terminalType` は分類用であり、機器の真正性や認可を保証しません。
|
|
932
|
+
|
|
933
|
+
画面内の復帰は UI の `createSessionRecovery` / `SessionRecoveryDialog` と SDK `recoverSession` を組み合わせます。通常のパスワード・OTP・LDAP 方針を適用し、`expectedUserId` で元の本人に限定してからセッションを発行します。認証拒否が明確な要求だけ1回再送でき、結果不明のネットワーク障害は再送しません。画面再読込や終了で失われたメモリの入力は復元できません。
|
|
934
|
+
|
|
923
935
|
## ページ離脱時のセッション方針
|
|
924
936
|
|
|
925
937
|
`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` を併用してください。閉じた瞬間の失効は保証できません。
|
|
926
938
|
|
|
939
|
+
標準 `/auth/me` も `iam.profile.update` / `iam.profile.avatar` を検査します。複合更新では有効な資料変更規則を優先し、それ以外は画像規則を使います。`authz.getSensitiveOperations(operation?)` は単一操作だけ照会でき、省略時はDB規則一覧を返します。超管理者の役割は共有DBから毎回取得し、種子の並行作成競合は再読込、権限割当失敗は上位へ返します。
|
|
940
|
+
|
|
927
941
|
## 開発とテスト
|
|
928
942
|
|
|
929
943
|
公開例は `@h-ai/iam` を使用し、既定値を省略できる `IamConfigInput` と解析済み `IamConfig` を区別します。依存を先に初期化し、`HaiResult.success` を確認し、会話更新には読取時の `version`、通知には永続的な冪等キーを使います。「模块内部上下文」の戦略・倉庫例はアプリ向け深い import の契約ではありません。チェッカーは型だけを確認し、Redis/DB・LDAP・配送の動作には対応する統合テストが必要です。[JSDoc 規約](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)も参照してください。
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm add @h-ai/iam
|
|
|
33
33
|
- `@h-ai/reldb` — 数据库(用户/角色/权限持久化),**需在 iam.init() 前初始化**
|
|
34
34
|
- `@h-ai/cache` — 缓存(会话/OTP/一次性票据),**需在 iam.init() 前初始化**
|
|
35
35
|
- `@h-ai/crypto` — 密码哈希(内部使用,自动初始化)
|
|
36
|
-
- `@h-ai/audit` —
|
|
36
|
+
- `@h-ai/audit` — 宿主初始化后自动记录 IAM 安全事件;未选用时不调用采集器。启用后的审计写失败仍记录错误,外部事务应回滚。
|
|
37
37
|
|
|
38
38
|
- `@h-ai/reach` — **可选**,应用安装、初始化并注入后可使用标准邮件/短信模板;自定义发送回调无需 Reach。
|
|
39
39
|
- `ldapts` — **可选**,仅在显式启用 LDAP 且没有注入 `ldapClientFactory` 时加载。
|
|
@@ -221,7 +221,7 @@ IAM 是**生命周期单例**模块,通过 `iam.init()` / `iam.close()` 管理
|
|
|
221
221
|
|
|
222
222
|
```
|
|
223
223
|
iam.init()
|
|
224
|
-
├─ session
|
|
224
|
+
├─ session (缓存会话管理,当前凭据状态共享缓存/DB 回源)
|
|
225
225
|
├─ authz (RBAC 角色/权限,DB 批量读取)
|
|
226
226
|
├─ authn (认证策略,依赖 session + authz)
|
|
227
227
|
├─ user (用户管理,依赖 session + authz + passwordStrategy)
|
|
@@ -278,10 +278,10 @@ login(credentials)
|
|
|
278
278
|
```
|
|
279
279
|
verifyToken(accessToken)
|
|
280
280
|
→ session.get(accessToken) → 缓存查询
|
|
281
|
-
→
|
|
281
|
+
→ 读取共享缓存中的当前用户/凭据状态,检查启用状态、期限与会话绑定的 credentialVersion;不可信时回源 DB
|
|
282
282
|
→ 滑动续期模式下自动延长 TTL
|
|
283
|
-
→
|
|
284
|
-
→
|
|
283
|
+
→ 复用上述用户资料,读取共享代际缓存中的当前角色/权限;冷读使用单条 JOIN
|
|
284
|
+
→ 必要状态读取失败、用户缺失/停用/到期或凭据代不匹配时拒绝;API Key 会话再次收敛 scope
|
|
285
285
|
→ 返回当前 Session 对象(含 userId、roles、permissions)
|
|
286
286
|
```
|
|
287
287
|
|
|
@@ -427,9 +427,8 @@ Session.credentialVersion 绑定创建时的数据库凭据代际;CreateSessio
|
|
|
427
427
|
```
|
|
428
428
|
checkPermission(userId, permission)
|
|
429
429
|
→ RBAC 未启用 → 直接返回 true
|
|
430
|
-
→
|
|
431
|
-
→
|
|
432
|
-
→ 一次批量查询这些角色的权限代码(DB);读取失败返回错误
|
|
430
|
+
→ 从共享代际缓存读取当前角色与权限,冷读使用单条 JOIN
|
|
431
|
+
→ 包含超管角色则返回 true;必要状态读取失败返回错误
|
|
433
432
|
→ 逐权限匹配:精确匹配 或 通配符匹配(`admin:*` 匹配 `admin:read`)
|
|
434
433
|
→ 返回 true / false
|
|
435
434
|
```
|
|
@@ -496,7 +495,7 @@ async function currentAuthorization(userId: string) {
|
|
|
496
495
|
- **通配符匹配**:`admin:*` 匹配 `admin:read`、`admin:write:detail` 等(单层 `*` 匹配冒号后全部)
|
|
497
496
|
- **超管角色**:配置的 `rbac.superAdminRole`(默认 `super_admin`)自动拥有所有权限
|
|
498
497
|
- **事务保护**:`deleteRole` / `deletePermission` 使用 DB 事务,先清关联再删实体
|
|
499
|
-
- **会话同步**:缓存更新为 best-effort;受保护请求使用 `iam.auth.verifyToken
|
|
498
|
+
- **会话同步**:缓存更新为 best-effort;受保护请求使用 `iam.auth.verifyToken`,使用共享代际缓存校验当前用户状态、期限及会话绑定的凭据代,再读取当前授权;缓存不可信时回源,必要读取失败即拒绝。`iam.session.get/verifyToken` 复用同一当前用户/凭据读取并检查启用状态与 `credentialVersion`,但角色/权限字段仍是会话快照,不能替代当前授权。`iam.authz.getUserAuthorization(userId)` 复用单次 JOIN 冷读与共享缓存。
|
|
500
499
|
- **种子数据**:`seedDefaultData: true` 时自动创建 admin/user/guest 三个默认角色及基础权限,幂等执行
|
|
501
500
|
|
|
502
501
|
---
|
|
@@ -626,7 +625,8 @@ async function disableUser(userId: string) {
|
|
|
626
625
|
|
|
627
626
|
```
|
|
628
627
|
verifyApiKey(rawKey)
|
|
629
|
-
→
|
|
628
|
+
→ 以 SHA-256(rawKey) 指纹读取共享代际缓存,命中时校验有效期并返回
|
|
629
|
+
→ 冷读从 rawKey 提取前缀(prefix + 前 8 字符)
|
|
630
630
|
→ 按前缀从 DB 检索候选 API Key 列表(缩小范围)
|
|
631
631
|
→ 逐候选项进行 hash 验证(同步操作)
|
|
632
632
|
→ 验证通过 → 检查是否过期/禁用
|
|
@@ -866,7 +866,7 @@ agreements:
|
|
|
866
866
|
|
|
867
867
|
#### 分布式安全与边界
|
|
868
868
|
|
|
869
|
-
所有节点共享同一数据库和 Redis;Memory 仅用于单进程测试。OTP 的验证码、尝试次数和冷却期保存在同一身份 key,通过 CAS 发布和消费;冲突拒绝旧代,消费/耗尽保留冷却期。密码重置的摘要、有效期、尝试次数和 resetVersion 保存在用户数据库行,策略失败计次,最终一条条件 SQL 同时改密、消费令牌并推进 credentialVersion;新令牌、后续尝试、停用或到期都会阻止迟到提交。发送重置请求只推进 resetVersion,不撤销现有会话;成功改密才推进 credentialVersion
|
|
869
|
+
所有节点共享同一数据库和 Redis;Memory 仅用于单进程测试。OTP 的验证码、尝试次数和冷却期保存在同一身份 key,通过 CAS 发布和消费;冲突拒绝旧代,消费/耗尽保留冷却期。密码重置的摘要、有效期、尝试次数和 resetVersion 保存在用户数据库行,策略失败计次,最终一条条件 SQL 同时改密、消费令牌并推进 credentialVersion;新令牌、后续尝试、停用或到期都会阻止迟到提交。发送重置请求只推进 resetVersion,不撤销现有会话;成功改密才推进 credentialVersion。认证策略保存已验证的凭据代际,建立会话/认证/刷新均校验共享代际缓存或安全回源的当前状态;缓存批量清理失败也不能使旧会话有效。通知只在成功发布后调用,第三参数为 { expiresAt, idempotencyKey },应用及 Reach Provider 必须用该稳定键持久化去重。异常/超时可能已送达,返回结果未知,不自动重发或回滚已发布凭据;没有接收端幂等保障时不能承诺外部副作用恰好一次。
|
|
870
870
|
|
|
871
871
|
可选字段(如 `ldap`)若尚未写入 YAML,请先声明该字段,或通过 `HAI_IAM` 的 JSON 值整体覆盖模块配置;已有字段支持 `HAI_IAM_LOGIN_OTP` 等环境变量覆盖。
|
|
872
872
|
|
|
@@ -891,6 +891,12 @@ if (!result.success) {
|
|
|
891
891
|
|
|
892
892
|
`iam.session.update(token, patch, expectedVersion)` 必须传入读取到的 Session.version。旧版本返回 CONFLICT,已撤销 token 返回 SESSION_NOT_FOUND;不会将旧内容重新写回或覆盖新会话。仅滑动时间戳更新不改变内容版本,允许正常并行请求。批量权限更新绑定首次读取的 token/版本集合,不将迟到数据应用到后续登录;遇到较新内容版本失败,调用方重新查询授权后再提交。
|
|
893
893
|
|
|
894
|
+
### 共享鉴权缓存
|
|
895
|
+
|
|
896
|
+
验签所需账号/凭据状态、角色权限、敏感操作和已验证 API Key 使用 Redis 共享缓存(60 秒 DTO TTL),缓存值不保存数据库中的密码/API Key 验证哈希、明文密钥或重置令牌;API Key 缓存地址使用明文 Key 的单向 SHA-256 指纹;账号/密码/Key 到期时间每次按当前时间核验。权限冷读使用单次 JOIN。所有 IAM 写入口在数据库变更前建立共享脏标记,提交/回滚后推进代际,外部事务同样等到完成才允许回填;冷读与代际变化并发时不发布旧缓存。正常热路径不查 PG,写入中、失效未确认或缓存读失败时回源;会话 Redis 本身不可用仍拒绝验签。
|
|
897
|
+
|
|
898
|
+
写前无法登记失效时拒绝变更;已提交后失效失败不会伪装回滚,持久脏标记使后续授权继续读数据库。进程崩溃或提交结果未知时不通过 TTL 擅自清除此标记;应告警并在确认没有 IAM 写事务的维护窗口修复缓存状态。多节点必须同时升级并通过 IAM API 写权限/账号;直接 SQL、旧版本写节点或独立系统改表不触发缓存失效,需停写并使 IAM 缓存整体失效后再恢复,不能与在线缓存写入混用。API Key 明文验证命中缓存后避免重复昂贵哈希,lastUsedAt 在冷验证时采样更新(最多约一分钟延迟)。
|
|
899
|
+
|
|
894
900
|
## 自托管交互挑战
|
|
895
901
|
|
|
896
902
|
`captcha.enabled` 默认关闭。开启后使用 ALTCHA 自托管计算挑战,配置 `ttlSeconds`(默认120,1–600秒)、`cost`(默认5000,100–50000)和 `maxCounter`(默认100,1–2000)控制期限与工作量。不需要外部验证码账号,HMAC 秘密与单次消费状态只保存在既有共享 Cache;生产仍须使用共享后端。计算挑战增加自动化尝试成本,不保证请求来自真人。
|
|
@@ -901,17 +907,19 @@ if (!result.success) {
|
|
|
901
907
|
|
|
902
908
|
## 可选密码与登录保护
|
|
903
909
|
|
|
904
|
-
`password.minLength/maxLength` 控制长度,各 `require*` 指定必需类别;`minCharacterClasses` 支持大小写、数字、特殊字符中任意 N 类,0不额外限制。CLI/admin 示例默认8
|
|
910
|
+
`password.minLength/maxLength` 控制长度,各 `require*` 指定必需类别;`minCharacterClasses` 支持大小写、数字、特殊字符中任意 N 类,0不额外限制。CLI/admin 示例默认8–128位、不额外要求字符类别;需要任意3类时显式设置 `minCharacterClasses: 3`。所有服务端创建、改密和重置入口共用校验。
|
|
905
911
|
|
|
906
912
|
`expirationDays: 0` 关闭定期改密,正数为天数;`preventSamePassword` 可独立开启。启用期限或首次强改时,新旧密码必须不同。`requireInitialChange` 作用于管理端创建/重置的密码,自助注册设密不重复强改。首次/过期密码不能授权普通会话或刷新旧会话;用户通过 `iam.user.renewPassword({ identifier, password, newPassword })` 验证原密码并更换后重新登录,Kit/Serv 与 AuthPanel 提供完成流程。
|
|
907
913
|
|
|
908
|
-
`security.lockoutEnabled`
|
|
914
|
+
`security.lockoutEnabled` 的框架 Schema 默认开启,CLI/admin 简化示例显式关闭,按需启用;`failureWindow` 默认86400秒,从首次失败起按固定窗口计数,`maxLoginAttempts` 为阈值,`lockoutDuration` 为锁定秒数。示例锁定1200秒。关闭时忽略历史锁;计数与阈值在共享数据库中原子处理。
|
|
909
915
|
|
|
910
916
|
`initialCredentials.enabled` 开启管理端 `iam.user.inviteUser()`:内部生成独立随机秘密,仅发送短期单次设密邀请,不返回/发送初始秘密。沿用 `passwordReset` 的模式、期限、尝试次数和敏感通知通道,接收人通过重置页面设密。关闭重置或缺少通道时不创建账号。投递未知返回失败并保留已创建账号及邀请,核对后再请求重置通知,不自动回滚或重发。
|
|
911
917
|
|
|
912
918
|
`loginAccess.enabled` 开启登录限制;`allowIps/denyIps` 支持 IPv4/IPv6/CIDR,拒绝优先,空列表不限制对应维度。`windows` 用 `days`(周一1至周日7)、`start/end`(HH:mm,含起点不含终点,支持跨午夜)和 IANA `timezone`。登录方法第二参为可信 `AuthRequestContext`:Kit 使用平台 `getClientAddress()`,Serv 使用可信代理策略解析的 `context.ip`。不得复制任意转发头或凭据 JSON 的 IP;地址限制启用但无可信地址时拒绝。注册后自动登录、API Key 登录同样执行。
|
|
913
919
|
|
|
914
|
-
`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 由项目定义。票据只短暂保存在内存,公开配置仅暴露开关/操作清单。
|
|
920
|
+
`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 标准界面在已登录用户点击敏感动作时弹出当前账号密码验证窗口;取消不提交、密码错误不继续,正确后才执行原操作,不创建另一个登录会话。
|
|
921
|
+
|
|
922
|
+
RBAC 权限支持 `sensitiveOperations: string[]`(持久字段 `sensitive_operations`)。可信管理端可调用 `iam.authz.setPermissionSensitiveOperations(permissionId, operations)`;检查结果的 `success`,空列表仅移除该权限的附加规则。开启 `reauthentication.enabled` 后,所有权限记录的规则与文件 `operations` 取并集,服务端实时执行。规则 ID 必须对应实际业务守卫,不能替代原权限检查。异步 `iam.getPublicConfig()` 返回合并后的公开策略,DB 读取失败返回失败;同步 `iam.publicConfig` 只保留文件快照。RBAC 关闭时忽略数据库规则。权限表迁移只添加可空 JSON 字段,保留既有角色/权限关联。
|
|
915
923
|
|
|
916
924
|
## 账号类型和生命周期
|
|
917
925
|
|
|
@@ -933,10 +941,16 @@ if (!result.success) {
|
|
|
933
941
|
|
|
934
942
|
审计模块已初始化且 capture.enabled 开启时,IAM 自动记录关键账号、鉴别、密码、授权、API Key 和会话事件,包含失败/拒绝与安全原因码。先初始化 audit 再初始化 IAM,关闭时反序。不会记录密码、一次性票据或令牌;成功登录才确认主体,可信服务/CLI 调用应以 audit.withContext 提供操作者和关联信息。外部授权事务的事件随该事务提交或回滚,回滚失败尝试由调用入口在事务结束后记录。审计写失败输出诊断,不把已生效业务改成可盲目重试的失败。
|
|
935
943
|
|
|
944
|
+
`session.terminalLimits: { web: 2, app: 1 }` 设置分终端会话数,默认 `{}` 关闭。非空时未列出的 `desktop/api` 等类型不能新登录;旧会话按 `web` 处理。`terminalLimitMode: reject` 为默认,`replace` 仅替换同类最早会话。全局 `maxSessions` 和 `singleDevice` 继续约束所有终端,配额同一 CAS 原子提交。服务端登录上下文的 `terminalType` 和标准客户端 `auth.terminalType` 选择分组;类型不是硬件身份或授权凭据。
|
|
945
|
+
|
|
946
|
+
Web 原地恢复使用 UI 的 `createSessionRecovery` / `SessionRecoveryDialog` 与 SDK 的 `recoverSession`,复用正常登录策略。`expectedUserId` 在密码、OTP、LDAP 登录签发会话前限制原主体;验证失败不签发恢复会话。仅重发明确未认证拒绝的一笔请求,网络结果不明时不重发。页面刷新/关闭后的内存草稿不在恢复范围内。
|
|
947
|
+
|
|
936
948
|
## 页面离开会话策略
|
|
937
949
|
|
|
938
950
|
`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`;不能据此保证关闭即撤销。
|
|
939
951
|
|
|
952
|
+
标准 `/auth/me` 也检查 `iam.profile.update` / `iam.profile.avatar`;混合修改优先选取已启用的资料用途,否则使用头像用途。`authz.getSensitiveOperations(operation?)` 可只查询一个操作;未传则列出全部有效数据库规则。超管身份每次从共享角色读取,默认种子允许并发创建冲突后重读,并传播权限分配失败。
|
|
953
|
+
|
|
940
954
|
## 开发测试
|
|
941
955
|
|
|
942
956
|
公开示例从 `@h-ai/iam` 导入,区分可省略默认值的 `IamConfigInput` 与 Schema 解析后的 `IamConfig`。服务端依赖先初始化;`HaiResult` 先检查 `success`,会话修改使用读取到的 `version`,通知复用持久幂等键。内部策略/仓储示例标记为“模块内部上下文”,不作为应用的深层导入入口。检查器只验证示例类型;真实 Redis/数据库、LDAP 和通知投递仍需对应集成测试。见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。
|
package/dist/browser.d.ts
CHANGED
|
@@ -387,6 +387,16 @@ declare const SessionConfigSchema: z.ZodObject<{
|
|
|
387
387
|
reject: "reject";
|
|
388
388
|
}>>;
|
|
389
389
|
maxSessions: z.ZodDefault<z.ZodNumber>;
|
|
390
|
+
terminalLimits: z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
391
|
+
web: "web";
|
|
392
|
+
app: "app";
|
|
393
|
+
desktop: "desktop";
|
|
394
|
+
api: "api";
|
|
395
|
+
}> & z.core.$partial, z.ZodNumber>>;
|
|
396
|
+
terminalLimitMode: z.ZodDefault<z.ZodEnum<{
|
|
397
|
+
replace: "replace";
|
|
398
|
+
reject: "reject";
|
|
399
|
+
}>>;
|
|
390
400
|
idleTimeout: z.ZodDefault<z.ZodNumber>;
|
|
391
401
|
absoluteTimeout: z.ZodDefault<z.ZodNumber>;
|
|
392
402
|
refreshTokenMaxAge: z.ZodDefault<z.ZodNumber>;
|
|
@@ -571,6 +581,16 @@ declare const IamConfigSchema: z.ZodObject<{
|
|
|
571
581
|
reject: "reject";
|
|
572
582
|
}>>;
|
|
573
583
|
maxSessions: z.ZodDefault<z.ZodNumber>;
|
|
584
|
+
terminalLimits: z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
585
|
+
web: "web";
|
|
586
|
+
app: "app";
|
|
587
|
+
desktop: "desktop";
|
|
588
|
+
api: "api";
|
|
589
|
+
}> & z.core.$partial, z.ZodNumber>>;
|
|
590
|
+
terminalLimitMode: z.ZodDefault<z.ZodEnum<{
|
|
591
|
+
replace: "replace";
|
|
592
|
+
reject: "reject";
|
|
593
|
+
}>>;
|
|
574
594
|
idleTimeout: z.ZodDefault<z.ZodNumber>;
|
|
575
595
|
absoluteTimeout: z.ZodDefault<z.ZodNumber>;
|
|
576
596
|
refreshTokenMaxAge: z.ZodDefault<z.ZodNumber>;
|
|
@@ -818,6 +838,8 @@ interface PermissionQueryOptions extends PaginationOptionsInput {
|
|
|
818
838
|
* ```
|
|
819
839
|
*/
|
|
820
840
|
interface Permission {
|
|
841
|
+
/** 此资源权限要求二次认证的操作ID;与文件清单取并集,空或null表示不追加。 */
|
|
842
|
+
sensitiveOperations?: string[] | null;
|
|
821
843
|
/** 权限 ID */
|
|
822
844
|
id: string;
|
|
823
845
|
/** 权限代码(如 users:read, posts:write) */
|
|
@@ -878,6 +900,10 @@ interface Role {
|
|
|
878
900
|
* ```
|
|
879
901
|
*/
|
|
880
902
|
interface AuthzOperations {
|
|
903
|
+
/** 实时读取RBAC敏感操作;可仅查询一个操作,未传则返回全部。RBAC关闭时为空,读取失败须拒绝。 */
|
|
904
|
+
getSensitiveOperations: (operation?: string) => Promise<HaiResult<string[]>>;
|
|
905
|
+
/** 设置资源权限的敏感操作清单;空数组清除数据库追加规则,不清除文件中的规则。 */
|
|
906
|
+
setPermissionSensitiveOperations: (permissionId: string, operations: string[]) => Promise<HaiResult<void>>;
|
|
881
907
|
/** 查询当前角色与权限代码,一次读取角色并批量聚合权限,不依赖会话缓存。 */
|
|
882
908
|
getUserAuthorization: (userId: string) => Promise<HaiResult<Pick<Session, 'roles' | 'permissions'>>>;
|
|
883
909
|
/**
|
|
@@ -886,7 +912,7 @@ interface AuthzOperations {
|
|
|
886
912
|
* 超级管理员角色自动拥有所有权限。
|
|
887
913
|
* 支持通配符匹配(如 `user:*` 匹配 `user:read`)。
|
|
888
914
|
*
|
|
889
|
-
*
|
|
915
|
+
* **数据来源**:共享缓存;IAM 写入口在提交前后推进代际,冷读与失效期间查询数据库。
|
|
890
916
|
*
|
|
891
917
|
* @param userId - 用户 ID
|
|
892
918
|
* @param permission - 权限代码(如 `user:read`)
|
|
@@ -1581,6 +1607,8 @@ interface AuthResult {
|
|
|
1581
1607
|
* ```
|
|
1582
1608
|
*/
|
|
1583
1609
|
interface Session {
|
|
1610
|
+
/** 登录时固定的终端分组;旧记录未设置时归为 web,刷新不能切换分组。 */
|
|
1611
|
+
terminalType?: 'web' | 'app' | 'desktop' | 'api';
|
|
1584
1612
|
/** 会话内容版本;显式更新产生新版本,内部滑动续期保留版本。 */
|
|
1585
1613
|
version: string;
|
|
1586
1614
|
/** 认证快照绑定的持久凭据代;改密后的旧会话即使缓存清理失败也拒绝。 */
|
|
@@ -1641,6 +1669,8 @@ type SessionData = Record<string, unknown> & {
|
|
|
1641
1669
|
* ```
|
|
1642
1670
|
*/
|
|
1643
1671
|
interface CreateSessionOptions {
|
|
1672
|
+
/** 可信宿主提供的终端分组;会话签发后不可通过 update/refresh 改变。 */
|
|
1673
|
+
terminalType?: 'web' | 'app' | 'desktop' | 'api';
|
|
1644
1674
|
/** 策略已验证的凭据代;创建前须与当前数据库核对,省略时仅供可信服务端按当前用户签发。 */
|
|
1645
1675
|
expectedCredentialVersion?: string;
|
|
1646
1676
|
/** 服务端已验证的 API Key 绑定;不得来自未验证客户端输入。 */
|
|
@@ -1975,6 +2005,8 @@ interface IamCaptchaChallenge {
|
|
|
1975
2005
|
* ```
|
|
1976
2006
|
*/
|
|
1977
2007
|
interface AuthRequestContext {
|
|
2008
|
+
/** 宿主识别的接入终端分组;不是物理设备证明,省略时普通登录归为 web。 */
|
|
2009
|
+
terminalType?: 'web' | 'app' | 'desktop' | 'api';
|
|
1978
2010
|
/** socket/平台或受信代理解析后的地址;不能直接复制 X-Forwarded-For。 */
|
|
1979
2011
|
ip?: string;
|
|
1980
2012
|
userAgent?: string;
|
|
@@ -1989,6 +2021,8 @@ interface AuthRequestContext {
|
|
|
1989
2021
|
* ```
|
|
1990
2022
|
*/
|
|
1991
2023
|
interface PasswordCredentials {
|
|
2024
|
+
/** 原地恢复会话时限定原主体;鉴别成功但不是此用户时也不签发新会话。 */
|
|
2025
|
+
expectedUserId?: string;
|
|
1992
2026
|
captcha?: string;
|
|
1993
2027
|
/** 用户名/邮箱/手机号 */
|
|
1994
2028
|
identifier: string;
|
|
@@ -2004,6 +2038,8 @@ interface PasswordCredentials {
|
|
|
2004
2038
|
* ```
|
|
2005
2039
|
*/
|
|
2006
2040
|
interface OtpCredentials {
|
|
2041
|
+
/** 会话恢复限定原主体,不能以其他账号的有效验证码恢复原业务动作。 */
|
|
2042
|
+
expectedUserId?: string;
|
|
2007
2043
|
captcha?: string;
|
|
2008
2044
|
/** 邮箱/手机号 */
|
|
2009
2045
|
identifier: string;
|
|
@@ -2019,6 +2055,8 @@ interface OtpCredentials {
|
|
|
2019
2055
|
* ```
|
|
2020
2056
|
*/
|
|
2021
2057
|
interface LdapCredentials {
|
|
2058
|
+
/** 会话恢复限定原主体;其他账号鉴别成功也不签发恢复会话。 */
|
|
2059
|
+
expectedUserId?: string;
|
|
2022
2060
|
captcha?: string;
|
|
2023
2061
|
/** 用户名 */
|
|
2024
2062
|
username: string;
|
|
@@ -2548,8 +2586,19 @@ interface IamFunctions {
|
|
|
2548
2586
|
close: () => Promise<void>;
|
|
2549
2587
|
/** 当前脱敏后的配置快照(未初始化时为 null) */
|
|
2550
2588
|
readonly config: IamConfig | null;
|
|
2551
|
-
/**
|
|
2589
|
+
/** 安全的文件配置快照;含数据库敏感规则的有效配置使用 getPublicConfig()。 */
|
|
2552
2590
|
readonly publicConfig: IamPublicConfig | null;
|
|
2591
|
+
/**
|
|
2592
|
+
* 读取包含文件与当前RBAC数据库敏感操作并集的公开配置;数据库读取失败返回失败。
|
|
2593
|
+
* @returns 独立公开快照;未初始化或启用策略时数据库故障返回 HaiResult 失败。
|
|
2594
|
+
* @example
|
|
2595
|
+
* ```ts
|
|
2596
|
+
* import { iam } from '@h-ai/iam'
|
|
2597
|
+
* const config = await iam.getPublicConfig()
|
|
2598
|
+
* if (config.success) config.data.reauthentication.operations
|
|
2599
|
+
* ```
|
|
2600
|
+
*/
|
|
2601
|
+
getPublicConfig: () => Promise<HaiResult<IamPublicConfig>>;
|
|
2553
2602
|
/** 是否已初始化 */
|
|
2554
2603
|
readonly isInitialized: boolean;
|
|
2555
2604
|
/** 注册功能是否启用(未初始化或未配置时默认启用) */
|
package/dist/browser.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AccountConfigSchema, AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, AuthUiConfigSchema, CaptchaConfigSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginAccessConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema } from './chunk-
|
|
1
|
+
export { AccountConfigSchema, AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, AuthUiConfigSchema, CaptchaConfigSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginAccessConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema } from './chunk-KPG3M34Q.js';
|
|
2
2
|
//# sourceMappingURL=browser.js.map
|
|
3
3
|
//# sourceMappingURL=browser.js.map
|