@h-ai/iam 0.1.0-alpha.74 → 0.1.0-alpha.76

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { cache } from '@h-ai/cache';
4
4
  import { core, err, ok } from '@h-ai/core';
5
5
  import { crypto as crypto$1 } from '@h-ai/crypto';
6
6
  import { migrateSchema, reldb, schemaColumns, BaseReldbCrudRepository, HaiReldbError } from '@h-ai/reldb';
7
- import { createHash, randomBytes, randomInt } from 'crypto';
7
+ import { randomUUID, createHash, randomBytes, randomInt } from 'crypto';
8
8
  import { isIP, BlockList } from 'net';
9
9
  import { verifySolution, pbkdf2, createChallenge } from 'altcha/lib';
10
10
  import { z } from 'zod';
@@ -641,6 +641,118 @@ var iamM = core.i18n.createMessageGetter({
641
641
  "ja-JP": ja_JP_default
642
642
  });
643
643
 
644
+ // src/iam-cache.ts
645
+ var revisionKey = "hai:iam:read_cache:revision";
646
+ var transactions = /* @__PURE__ */ new WeakMap();
647
+ var logger = core.logger.child({ module: "iam", scope: "read-cache" });
648
+ var degraded = false;
649
+ function failure() {
650
+ return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_sessionExpired"));
651
+ }
652
+ async function revision() {
653
+ const current = await cache.kv.get(revisionKey);
654
+ if (!current.success)
655
+ return current;
656
+ if (current.data)
657
+ return ok(current.data);
658
+ const created = await cache.kv.set(revisionKey, { epoch: randomUUID(), writers: 0 }, { nx: true });
659
+ if (!created.success)
660
+ return created;
661
+ const loaded = await cache.kv.get(revisionKey);
662
+ return loaded.success && loaded.data ? ok(loaded.data) : failure();
663
+ }
664
+ async function change(delta) {
665
+ for (let attempt = 0; attempt < 32; attempt++) {
666
+ const current = await revision();
667
+ if (!current.success)
668
+ return current;
669
+ const written = await cache.kv.compareAndSwap(revisionKey, current.data, { epoch: randomUUID(), writers: Math.max(0, current.data.writers + delta) });
670
+ if (!written.success)
671
+ return written;
672
+ if (written.data)
673
+ return ok(void 0);
674
+ }
675
+ return failure();
676
+ }
677
+ async function readIamCache(key, load) {
678
+ const before = await revision();
679
+ if (!before.success || before.data.writers)
680
+ return load();
681
+ const address = `hai:iam:read_cache:${before.data.epoch}:${key}`;
682
+ const cached = await cache.kv.get(address);
683
+ if (cached.success && cached.data) {
684
+ const after2 = await revision();
685
+ if (after2.success && !after2.data.writers && after2.data.epoch === before.data.epoch)
686
+ return ok(cached.data.value);
687
+ return load();
688
+ }
689
+ const result = await load();
690
+ if (!result.success)
691
+ return result;
692
+ const after = await revision();
693
+ if (after.success && !after.data.writers && after.data.epoch === before.data.epoch)
694
+ await cache.kv.set(address, { value: result.data }, { ex: 60 });
695
+ return result;
696
+ }
697
+ async function finish() {
698
+ const completed = await change(-1);
699
+ if (!completed.success && !degraded) {
700
+ degraded = true;
701
+ logger.warn("IAM cache invalidation incomplete; authorization reads use database", { code: completed.error.code });
702
+ }
703
+ if (completed.success)
704
+ degraded = false;
705
+ }
706
+ async function enroll(tx) {
707
+ const existing = transactions.get(tx);
708
+ if (existing)
709
+ return existing;
710
+ const pending = (async () => {
711
+ const started = await change(1);
712
+ if (!started.success)
713
+ return started;
714
+ const commit = tx.commit.bind(tx);
715
+ const rollback = tx.rollback.bind(tx);
716
+ tx.commit = async () => {
717
+ const result = await commit();
718
+ if (result.success)
719
+ await finish();
720
+ return result;
721
+ };
722
+ tx.rollback = async () => {
723
+ const result = await rollback();
724
+ if (result.success)
725
+ await finish();
726
+ return result;
727
+ };
728
+ return ok(void 0);
729
+ })();
730
+ transactions.set(tx, pending);
731
+ return pending;
732
+ }
733
+ function guardIamMutation(operation, txIndex) {
734
+ return async (...args) => {
735
+ const candidate = txIndex === void 0 ? void 0 : args[txIndex];
736
+ const tx = candidate;
737
+ let started;
738
+ try {
739
+ started = tx ? await enroll(tx) : await change(1);
740
+ } catch {
741
+ return failure();
742
+ }
743
+ if (!started.success)
744
+ return started;
745
+ try {
746
+ return await operation(...args);
747
+ } catch {
748
+ return failure();
749
+ } finally {
750
+ if (!tx)
751
+ await finish();
752
+ }
753
+ };
754
+ }
755
+
644
756
  // src/authn/apikey/iam-authn-apikey-repository.ts
645
757
  var TABLE_NAME = "hai_iam_api_keys";
646
758
  var API_KEY_SORT_COLUMNS = {
@@ -742,6 +854,10 @@ async function createDbApiKeyRepository() {
742
854
  return apiKeyRepoInstance;
743
855
  const repo = new DbApiKeyRepository();
744
856
  await repo.count();
857
+ repo.removeById = guardIamMutation(repo.removeById.bind(repo), 1);
858
+ const update = repo.updateFields.bind(repo);
859
+ const protectedUpdate = guardIamMutation(update, 2);
860
+ repo.updateFields = (id, data, tx) => Object.keys(data).every((key) => key === "lastUsedAt") ? update(id, data, tx) : protectedUpdate(id, data, tx);
745
861
  apiKeyRepoInstance = repo;
746
862
  apiKeyRepoSqlOps = reldb.sql;
747
863
  return repo;
@@ -928,7 +1044,7 @@ function applySessionPatch(session, patch) {
928
1044
  }
929
1045
 
930
1046
  // src/ticket/iam-ticket-functions.ts
931
- var logger = core.logger.child({ module: "iam", scope: "ticket" });
1047
+ var logger2 = core.logger.child({ module: "iam", scope: "ticket" });
932
1048
  var TICKET_KEY_PREFIX = "hai:iam:ticket:";
933
1049
  var DEFAULT_TICKET_TTL_MS = 3e4;
934
1050
  function buildTicketKey(ticket) {
@@ -948,7 +1064,7 @@ function createTicketOperations() {
948
1064
  };
949
1065
  const saved = await cache.kv.set(buildTicketKey(ticket), record, { px: ttlMs, nx: true });
950
1066
  if (!saved.success || !saved.data) {
951
- logger.warn("Failed to issue ticket", { purpose: options.purpose });
1067
+ logger2.warn("Failed to issue ticket", { purpose: options.purpose });
952
1068
  return err(HaiIamError.TICKET_ISSUE_FAILED, iamM("iam_ticketIssueFailed"));
953
1069
  }
954
1070
  return ok({ ticket, expiresAt });
@@ -1152,6 +1268,12 @@ async function createDbUserRepository() {
1152
1268
  return userRepoInstance;
1153
1269
  const repo = new DbUserRepository();
1154
1270
  await repo.count();
1271
+ repo.create = guardIamMutation(repo.create.bind(repo), 1);
1272
+ repo.updateById = guardIamMutation(repo.updateById.bind(repo), 2);
1273
+ repo.deleteById = guardIamMutation(repo.deleteById.bind(repo), 1);
1274
+ repo.suspendInactive = guardIamMutation(repo.suspendInactive.bind(repo));
1275
+ repo.setPassword = guardIamMutation(repo.setPassword.bind(repo));
1276
+ repo.commitPasswordReset = guardIamMutation(repo.commitPasswordReset.bind(repo));
1155
1277
  userRepoInstance = repo;
1156
1278
  userRepoSqlOps = reldb.sql;
1157
1279
  return repo;
@@ -1422,7 +1544,7 @@ async function resetLoginFailures(userRepository, user, policy) {
1422
1544
  }
1423
1545
 
1424
1546
  // src/authn/apikey/iam-authn-apikey-strategy.ts
1425
- var logger2 = core.logger.child({ module: "iam", scope: "apikey-strategy" });
1547
+ var logger3 = core.logger.child({ module: "iam", scope: "apikey-strategy" });
1426
1548
  var KEY_PREFIX_LENGTH = 12;
1427
1549
  function generateRawKey(prefix2) {
1428
1550
  const hex = randomBytes(32).toString("hex");
@@ -1448,7 +1570,7 @@ function createApiKeyStrategy(config) {
1448
1570
  const apikeyConfig = config.apikeyConfig ? ApiKeyConfigSchema.parse(config.apikeyConfig) : ApiKeyConfigSchema.parse({});
1449
1571
  const { userRepository, apiKeyRepository } = config;
1450
1572
  const passwordOps = crypto$1.password;
1451
- async function verifyRawKey(rawKey) {
1573
+ async function loadRawKey(rawKey) {
1452
1574
  const prefix2 = extractKeyPrefix(rawKey);
1453
1575
  const candidatesResult = await apiKeyRepository.findByKeyPrefix(prefix2);
1454
1576
  if (!candidatesResult.success) {
@@ -1467,12 +1589,26 @@ function createApiKeyStrategy(config) {
1467
1589
  return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
1468
1590
  }
1469
1591
  apiKeyRepository.updateFields(candidate.id, { lastUsedAt: /* @__PURE__ */ new Date() }).catch(() => {
1470
- logger2.warn("Failed to update API Key lastUsedAt", { keyId: candidate.id });
1592
+ logger3.warn("Failed to update API Key lastUsedAt", { keyId: candidate.id });
1471
1593
  });
1472
1594
  return ok(toApiKey(candidate));
1473
1595
  }
1474
1596
  return err(HaiIamError.APIKEY_INVALID, iamM("iam_apikeyInvalid"));
1475
1597
  }
1598
+ async function verifyRawKey(rawKey) {
1599
+ const fingerprint = createHash("sha256").update(rawKey).digest("hex");
1600
+ const result = await readIamCache(`verified_apikey:${fingerprint}`, async () => {
1601
+ const verified = await loadRawKey(rawKey);
1602
+ return verified.success ? ok({ ...verified.data, expiresAt: verified.data.expiresAt?.getTime() ?? null, createdAt: verified.data.createdAt.getTime(), lastUsedAt: verified.data.lastUsedAt?.getTime() ?? null }) : verified;
1603
+ });
1604
+ if (!result.success)
1605
+ return result;
1606
+ if (!result.data.enabled)
1607
+ return err(HaiIamError.APIKEY_DISABLED, iamM("iam_apikeyDisabled"));
1608
+ if (result.data.expiresAt !== null && result.data.expiresAt <= Date.now())
1609
+ return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
1610
+ return ok({ ...result.data, expiresAt: result.data.expiresAt === null ? null : new Date(result.data.expiresAt), createdAt: new Date(result.data.createdAt), lastUsedAt: result.data.lastUsedAt === null ? null : new Date(result.data.lastUsedAt) });
1611
+ }
1476
1612
  const strategy = {
1477
1613
  type: "apikey",
1478
1614
  name: "apikey-strategy",
@@ -1497,7 +1633,7 @@ function createApiKeyStrategy(config) {
1497
1633
  if (!isUserActive(userResult.data)) {
1498
1634
  return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
1499
1635
  }
1500
- logger2.debug("API Key authentication succeeded", { userId: userResult.data.id, keyId: apiKey.id });
1636
+ logger3.debug("API Key authentication succeeded", { userId: userResult.data.id, keyId: apiKey.id });
1501
1637
  return ok({ user: { ...toUser(userResult.data), credentialVersion: userResult.data.credentialVersion ?? "" }, apiKey });
1502
1638
  }
1503
1639
  };
@@ -1534,7 +1670,7 @@ function createApiKeyStrategy(config) {
1534
1670
  const insertResult = await apiKeyRepository.insert(storedApiKey);
1535
1671
  if (!insertResult.success)
1536
1672
  return insertResult;
1537
- logger2.info("API Key created", { userId, keyId: id, name: options.name });
1673
+ logger3.info("API Key created", { userId, keyId: id, name: options.name });
1538
1674
  return ok({ apiKey: toApiKey(storedApiKey), rawKey });
1539
1675
  } catch (error) {
1540
1676
  return err(HaiIamError.INTERNAL_ERROR, iamM("iam_apikeyCreateFailed", { params: { message: String(error) } }), error);
@@ -1556,7 +1692,7 @@ function createApiKeyStrategy(config) {
1556
1692
  const result = await apiKeyRepository.removeById(keyId);
1557
1693
  if (!result.success)
1558
1694
  return result;
1559
- logger2.info("API Key revoked", { keyId });
1695
+ logger3.info("API Key revoked", { keyId });
1560
1696
  return ok(void 0);
1561
1697
  },
1562
1698
  async verifyApiKey(rawKey) {
@@ -1607,7 +1743,7 @@ function createLoginAccessCheck(policy) {
1607
1743
  return ok(void 0);
1608
1744
  };
1609
1745
  }
1610
- var logger3 = core.logger.child({ module: "iam", scope: "captcha" });
1746
+ var logger4 = core.logger.child({ module: "iam", scope: "captcha" });
1611
1747
  var prefix = "hai:iam:captcha:";
1612
1748
  var operationSchema = z.enum(["login", "register", "sendOtp", "passwordReset", "renewPassword"]);
1613
1749
  var proofSchema = z.object({
@@ -1639,7 +1775,7 @@ async function createIamCaptcha(config, input) {
1639
1775
  return err(HaiIamError.CAPTCHA_UNAVAILABLE, iamM("iam_captchaUnavailable"));
1640
1776
  return ok({ id, challenge });
1641
1777
  } catch (error) {
1642
- logger3.error("Captcha challenge generation failed", { operation: operation.data, error });
1778
+ logger4.error("Captcha challenge generation failed", { operation: operation.data, error });
1643
1779
  return err(HaiIamError.CAPTCHA_UNAVAILABLE, iamM("iam_captchaUnavailable"));
1644
1780
  }
1645
1781
  }
@@ -1675,7 +1811,7 @@ async function verifyIamCaptcha(config, operation, proof) {
1675
1811
  return err(HaiIamError.CAPTCHA_UNAVAILABLE, iamM("iam_captchaUnavailable"));
1676
1812
  return removed.data === 1 ? ok(void 0) : err(HaiIamError.CAPTCHA_INVALID, iamM("iam_captchaInvalid"));
1677
1813
  } catch (error) {
1678
- logger3.error("Captcha verification failed", { operation, error });
1814
+ logger4.error("Captcha verification failed", { operation, error });
1679
1815
  return err(HaiIamError.CAPTCHA_UNAVAILABLE, iamM("iam_captchaUnavailable"));
1680
1816
  }
1681
1817
  }
@@ -1733,7 +1869,7 @@ async function loadLdapClientFactory() {
1733
1869
  }
1734
1870
  };
1735
1871
  }
1736
- var logger4 = core.logger.child({ module: "iam", scope: "ldap-strategy" });
1872
+ var logger5 = core.logger.child({ module: "iam", scope: "ldap-strategy" });
1737
1873
  function escapeLdapFilterValue(value) {
1738
1874
  return value.replace(/[\\*()/\0]/g, (ch) => {
1739
1875
  const hex = ch.charCodeAt(0).toString(16).padStart(2, "0");
@@ -1836,7 +1972,7 @@ function createLdapStrategy(config) {
1836
1972
  if (!failureResult.success)
1837
1973
  return failureResult;
1838
1974
  }
1839
- logger4.warn("LDAP authentication failed", { username });
1975
+ logger5.warn("LDAP authentication failed", { username });
1840
1976
  return err(
1841
1977
  HaiIamError.INVALID_CREDENTIALS,
1842
1978
  iamM("iam_passwordWrong")
@@ -1874,7 +2010,7 @@ function createLdapStrategy(config) {
1874
2010
  return assigned;
1875
2011
  }
1876
2012
  } catch (callbackError) {
1877
- logger4.warn("onUserAutoRegistered callback failed", { userId: storedUser?.id, error: callbackError });
2013
+ logger5.warn("onUserAutoRegistered callback failed", { userId: storedUser?.id, error: callbackError });
1878
2014
  }
1879
2015
  }
1880
2016
  } else if (storedUser && syncUser) {
@@ -1899,7 +2035,7 @@ function createLdapStrategy(config) {
1899
2035
  const resetResult = await resetLoginFailures(userRepository, storedUser, failurePolicy);
1900
2036
  if (!resetResult.success)
1901
2037
  return resetResult;
1902
- logger4.debug("LDAP authentication succeeded", { userId: storedUser.id });
2038
+ logger5.debug("LDAP authentication succeeded", { userId: storedUser.id });
1903
2039
  return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
1904
2040
  } finally {
1905
2041
  await client.unbind();
@@ -1919,7 +2055,7 @@ function createCacheOtpRepository() {
1919
2055
  if (otpRepoInstance)
1920
2056
  return otpRepoInstance;
1921
2057
  const keyFor = (identifier) => `hai:iam:otp:${normalizeOtpIdentifier(identifier)}`;
1922
- const failure = () => err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"));
2058
+ const failure2 = () => err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"));
1923
2059
  const invalid = () => err(HaiIamError.OTP_INVALID, iamM("iam_otpNotExistOrExpired"));
1924
2060
  otpRepoInstance = {
1925
2061
  async issueOtp(input) {
@@ -1934,7 +2070,7 @@ function createCacheOtpRepository() {
1934
2070
  const key = keyFor(input.identifier);
1935
2071
  const current = await cache.kv.get(key);
1936
2072
  if (!current.success)
1937
- return failure();
2073
+ return failure2();
1938
2074
  if (current.data && current.data.cooldownUntil > Date.now()) {
1939
2075
  return err(HaiIamError.OTP_RESEND_TOO_FAST, iamM("iam_otpResendTooFast", { params: { seconds: Math.ceil((current.data.cooldownUntil - Date.now()) / 1e3) } }));
1940
2076
  }
@@ -1942,7 +2078,7 @@ function createCacheOtpRepository() {
1942
2078
  return invalid();
1943
2079
  const published = await cache.kv.compareAndSwap(key, current.data ?? void 0, state, { px: Math.max(state.expiresAt, state.cooldownUntil) - Date.now() });
1944
2080
  if (!published.success)
1945
- return failure();
2081
+ return failure2();
1946
2082
  if (!published.data)
1947
2083
  return err(HaiIamError.SECURITY_BUSY, iamM("iam_securityBusy"));
1948
2084
  if (Date.now() >= state.expiresAt)
@@ -1955,7 +2091,7 @@ function createCacheOtpRepository() {
1955
2091
  for (let attempt = 0; attempt < 16; attempt++) {
1956
2092
  const current = await cache.kv.get(key);
1957
2093
  if (!current.success)
1958
- return failure();
2094
+ return failure2();
1959
2095
  const state = current.data;
1960
2096
  if (!state || state.code === null || state.expiresAt <= Date.now() || state.attempts >= maxAttempts || version && state.version !== version)
1961
2097
  return invalid();
@@ -1965,7 +2101,7 @@ function createCacheOtpRepository() {
1965
2101
  const next = { ...state, attempts, code: correct || attempts >= maxAttempts ? null : state.code };
1966
2102
  const committed = await cache.kv.compareAndSwap(key, state, next, { keepTtl: true });
1967
2103
  if (!committed.success)
1968
- return failure();
2104
+ return failure2();
1969
2105
  if (!committed.data)
1970
2106
  continue;
1971
2107
  if (!correct || state.expiresAt <= Date.now())
@@ -1977,7 +2113,7 @@ function createCacheOtpRepository() {
1977
2113
  };
1978
2114
  return otpRepoInstance;
1979
2115
  }
1980
- var logger5 = core.logger.child({ module: "iam", scope: "otp-strategy" });
2116
+ var logger6 = core.logger.child({ module: "iam", scope: "otp-strategy" });
1981
2117
  var PHONE_IDENTIFIER_REGEX = /^\+?\d{8,}$/;
1982
2118
  var PHONE_SEPARATOR_REGEX = /[\s-]/g;
1983
2119
  function generateOtpCode(length) {
@@ -2075,7 +2211,7 @@ function createOtpStrategy(config) {
2075
2211
  return assigned;
2076
2212
  }
2077
2213
  } catch (callbackError) {
2078
- logger5.warn("onUserAutoRegistered callback failed", { userId: storedUser.id, error: callbackError });
2214
+ logger6.warn("onUserAutoRegistered callback failed", { userId: storedUser.id, error: callbackError });
2079
2215
  }
2080
2216
  }
2081
2217
  }
@@ -2087,7 +2223,7 @@ function createOtpStrategy(config) {
2087
2223
  const resetResult = await resetLoginFailures(config.userRepository, storedUser, failurePolicy);
2088
2224
  if (!resetResult.success)
2089
2225
  return resetResult;
2090
- logger5.debug("OTP authentication succeeded", { userId: storedUser.id });
2226
+ logger6.debug("OTP authentication succeeded", { userId: storedUser.id });
2091
2227
  return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
2092
2228
  }
2093
2229
  };
@@ -2131,12 +2267,12 @@ function createOtpStrategy(config) {
2131
2267
  );
2132
2268
  }
2133
2269
  const result = { expiresAt };
2134
- logger5.debug("OTP challenge sent", { identifier, expiresAt });
2270
+ logger6.debug("OTP challenge sent", { identifier, expiresAt });
2135
2271
  return ok(result);
2136
2272
  }
2137
2273
  return { strategy, challenge };
2138
2274
  }
2139
- var logger6 = core.logger.child({ module: "iam", scope: "password-strategy" });
2275
+ var logger7 = core.logger.child({ module: "iam", scope: "password-strategy" });
2140
2276
  var PASSWORD_UPPERCASE_REGEX = /[A-Z]/;
2141
2277
  var PASSWORD_LOWERCASE_REGEX = /[a-z]/;
2142
2278
  var PASSWORD_DIGIT_REGEX = /\d/;
@@ -2216,7 +2352,7 @@ function createPasswordStrategy(config) {
2216
2352
  const failureResult = await recordLoginFailure(config.userRepository, user, failurePolicy);
2217
2353
  if (!failureResult.success)
2218
2354
  return failureResult;
2219
- logger6.warn("Password verification failed", { userId: user.id });
2355
+ logger7.warn("Password verification failed", { userId: user.id });
2220
2356
  return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_passwordWrong"));
2221
2357
  }
2222
2358
  return ok(void 0);
@@ -2250,7 +2386,7 @@ function createPasswordStrategy(config) {
2250
2386
  const resetResult = await resetLoginFailures(config.userRepository, storedUser, failurePolicy);
2251
2387
  if (!resetResult.success)
2252
2388
  return resetResult;
2253
- logger6.debug("Password authentication succeeded", { userId: storedUser.id });
2389
+ logger7.debug("Password authentication succeeded", { userId: storedUser.id });
2254
2390
  return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
2255
2391
  }
2256
2392
  };
@@ -2270,7 +2406,7 @@ function createPasswordStrategy(config) {
2270
2406
  }
2271
2407
 
2272
2408
  // src/authn/iam-authn-functions.ts
2273
- var logger7 = core.logger.child({ module: "iam", scope: "authn" });
2409
+ var logger8 = core.logger.child({ module: "iam", scope: "authn" });
2274
2410
  async function createAuthnOperations(deps) {
2275
2411
  try {
2276
2412
  const { config, sessionFunctions, authzFunctions, ldapClientFactory, ldapSyncUser, onOtpSendEmail, onOtpSendSms } = deps;
@@ -2354,10 +2490,10 @@ async function createAuthnOperations(deps) {
2354
2490
  userRepository,
2355
2491
  config
2356
2492
  });
2357
- logger7.info("Authn sub-feature initialized");
2493
+ logger8.info("Authn sub-feature initialized");
2358
2494
  return ok({ authn: operations.authn, loginRegisteredUser: operations.loginRegisteredUser, passwordStrategyResult: passwordResult, apiKeyFunctions });
2359
2495
  } catch (error) {
2360
- logger7.error("Authn sub-feature initialization failed", { error });
2496
+ logger8.error("Authn sub-feature initialization failed", { error });
2361
2497
  return err(
2362
2498
  HaiIamError.CONFIG_ERROR,
2363
2499
  iamM("iam_initComponentFailed"),
@@ -2452,7 +2588,7 @@ function buildAuthnOperations(deps) {
2452
2588
  if (!recorded.success) {
2453
2589
  const revoked = await sessionFunctions.delete(session.accessToken);
2454
2590
  if (!revoked.success)
2455
- logger7.error("Failed to revoke session after login timestamp write failure", { userId: user.id, error: revoked.error });
2591
+ logger8.error("Failed to revoke session after login timestamp write failure", { userId: user.id, error: revoked.error });
2456
2592
  return recorded;
2457
2593
  }
2458
2594
  const { credentialVersion, ...publicUser } = user;
@@ -2481,14 +2617,14 @@ function buildAuthnOperations(deps) {
2481
2617
  ...credentials
2482
2618
  });
2483
2619
  if (!authResult.success) {
2484
- logger7.warn("Login failed", { type, reason: authResult.error.code });
2620
+ logger8.warn("Login failed", { type, reason: authResult.error.code });
2485
2621
  return authResult;
2486
2622
  }
2487
2623
  if (credentials.expectedUserId && credentials.expectedUserId !== authResult.data.id)
2488
2624
  return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_passwordWrong"));
2489
2625
  const result = await buildAuthResult(authResult.data, void 0, context);
2490
2626
  if (result.success) {
2491
- logger7.info("Login succeeded", { type, userId: authResult.data.id });
2627
+ logger8.info("Login succeeded", { type, userId: authResult.data.id });
2492
2628
  }
2493
2629
  return result;
2494
2630
  }
@@ -2496,19 +2632,10 @@ function buildAuthnOperations(deps) {
2496
2632
  const session = await sessionFunctions.verifyToken(accessToken);
2497
2633
  if (!session.success)
2498
2634
  return session;
2499
- const [user, authorization] = await Promise.all([
2500
- userRepository.findById(session.data.userId),
2501
- authzFunctions.getUserAuthorization(session.data.userId)
2502
- ]);
2503
- if (!user.success)
2504
- return user;
2505
- if (!user.data?.enabled)
2506
- return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
2507
- if (session.data.credentialVersion !== (user.data.credentialVersion ?? ""))
2508
- return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
2635
+ const authorization = await authzFunctions.getUserAuthorization(session.data.userId);
2509
2636
  if (!authorization.success)
2510
2637
  return authorization;
2511
- const current = { ...session.data, username: user.data.username, displayName: user.data.displayName, avatarUrl: user.data.avatarUrl, ...authorization.data };
2638
+ const current = { ...session.data, ...authorization.data };
2512
2639
  return ok(current.apiKeyId ? applyApiKeyScopes(current, current.apiKeyScopes ?? []) : current);
2513
2640
  }
2514
2641
  const tickets = createTicketOperations();
@@ -2516,7 +2643,9 @@ function buildAuthnOperations(deps) {
2516
2643
  async function requiresProof(operation) {
2517
2644
  if (!sensitive?.enabled)
2518
2645
  return ok(false);
2519
- const configured = await authzFunctions.getSensitiveOperations();
2646
+ if (sensitive.operations.includes(operation))
2647
+ return ok(true);
2648
+ const configured = await authzFunctions.getSensitiveOperations(operation);
2520
2649
  if (!configured.success)
2521
2650
  return configured;
2522
2651
  return ok(sensitive.operations.includes(operation) || configured.data.includes(operation));
@@ -2703,8 +2832,12 @@ async function createDbPermissionRepository() {
2703
2832
  return repo;
2704
2833
  }
2705
2834
  var DbPermissionRepository = class extends BaseReldbCrudRepository {
2706
- async getSensitiveOperations() {
2707
- const result = await reldb.sql.query("SELECT sensitive_operations FROM hai_iam_permissions WHERE sensitive_operations IS NOT NULL");
2835
+ async getSensitiveOperations(operation) {
2836
+ if (operation !== void 0 && (typeof operation !== "string" || !operation.trim() || operation.length > 128))
2837
+ return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sensitiveOperationsInvalid"));
2838
+ const match = reldb.config?.type === "postgresql" ? "sensitive_operations::jsonb @> CAST(? AS jsonb)" : reldb.config?.type === "mysql" ? "JSON_CONTAINS(sensitive_operations, JSON_QUOTE(?))" : "EXISTS (SELECT 1 FROM json_each(sensitive_operations) WHERE type = 'text' AND value = ?)";
2839
+ const params = operation === void 0 ? [] : [reldb.config?.type === "postgresql" ? JSON.stringify([operation]) : operation];
2840
+ const result = await reldb.sql.query(`SELECT sensitive_operations FROM hai_iam_permissions WHERE sensitive_operations IS NOT NULL${operation === void 0 ? "" : ` AND ${match} LIMIT 1`}`, params);
2708
2841
  if (!result.success)
2709
2842
  return result;
2710
2843
  const operations = /* @__PURE__ */ new Set();
@@ -2716,7 +2849,8 @@ var DbPermissionRepository = class extends BaseReldbCrudRepository {
2716
2849
  for (const value of values) {
2717
2850
  if (typeof value !== "string" || !value.trim() || value.length > 128)
2718
2851
  return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_reauthenticationRequired"));
2719
- operations.add(value.trim());
2852
+ if (operation === void 0 || value.trim() === operation)
2853
+ operations.add(value.trim());
2720
2854
  }
2721
2855
  }
2722
2856
  return ok([...operations].sort());
@@ -3292,7 +3426,7 @@ var DbRoleRepository = class extends BaseReldbCrudRepository {
3292
3426
  };
3293
3427
 
3294
3428
  // src/authz/iam-authz-functions.ts
3295
- var logger8 = core.logger.child({ module: "iam", scope: "authz" });
3429
+ var logger9 = core.logger.child({ module: "iam", scope: "authz" });
3296
3430
  async function createAuthzOperations(deps) {
3297
3431
  try {
3298
3432
  const { config, session } = deps;
@@ -3314,10 +3448,23 @@ async function createAuthzOperations(deps) {
3314
3448
  userRoleRepository: userRoleResult.data,
3315
3449
  session
3316
3450
  });
3317
- logger8.info("Authz sub-feature initialized");
3318
- return ok(manager);
3451
+ logger9.info("Authz sub-feature initialized");
3452
+ return ok({
3453
+ ...manager,
3454
+ assignRole: guardIamMutation(manager.assignRole, 2),
3455
+ removeRole: guardIamMutation(manager.removeRole, 2),
3456
+ syncRoles: guardIamMutation(manager.syncRoles, 2),
3457
+ createRole: guardIamMutation(manager.createRole, 1),
3458
+ updateRole: guardIamMutation(manager.updateRole, 2),
3459
+ deleteRole: guardIamMutation(manager.deleteRole, 1),
3460
+ createPermission: guardIamMutation(manager.createPermission, 1),
3461
+ deletePermission: guardIamMutation(manager.deletePermission, 1),
3462
+ assignPermissionToRole: guardIamMutation(manager.assignPermissionToRole, 2),
3463
+ removePermissionFromRole: guardIamMutation(manager.removePermissionFromRole, 2),
3464
+ setPermissionSensitiveOperations: guardIamMutation(manager.setPermissionSensitiveOperations)
3465
+ });
3319
3466
  } catch (error) {
3320
- logger8.error("Authz sub-feature initialization failed", { error });
3467
+ logger9.error("Authz sub-feature initialization failed", { error });
3321
3468
  return err(
3322
3469
  HaiIamError.CONFIG_ERROR,
3323
3470
  iamM("iam_initComponentFailed"),
@@ -3334,18 +3481,6 @@ function createRbacManager(config) {
3334
3481
  userRoleRepository,
3335
3482
  session
3336
3483
  } = config;
3337
- let superAdminRoleId;
3338
- async function resolveSuperAdminRoleId() {
3339
- if (superAdminRoleId !== void 0) {
3340
- return ok(superAdminRoleId);
3341
- }
3342
- const roleResult = await roleRepository.findByCode(rbacConfig.superAdminRole);
3343
- if (!roleResult.success) {
3344
- return mapRepositoryError2("iam_queryRoleFailed", roleResult.error.message);
3345
- }
3346
- superAdminRoleId = roleResult.data?.id ?? null;
3347
- return ok(superAdminRoleId);
3348
- }
3349
3484
  function mapRepositoryError2(messageKey, message) {
3350
3485
  return err(
3351
3486
  HaiIamError.REPOSITORY_ERROR,
@@ -3362,16 +3497,6 @@ function createRbacManager(config) {
3362
3497
  }
3363
3498
  return false;
3364
3499
  }
3365
- async function hasPermissionInRoles(roleIds, permission) {
3366
- const codesResult = await rolePermissionRepository.getPermissionCodesForRoles(roleIds);
3367
- if (!codesResult.success)
3368
- return codesResult;
3369
- for (const code of codesResult.data) {
3370
- if (matchesPermission(permission, code))
3371
- return ok(true);
3372
- }
3373
- return ok(false);
3374
- }
3375
3500
  async function getUserPermissionsInternal(userId) {
3376
3501
  const roleIdsResult = await userRoleRepository.getRoleIds(userId);
3377
3502
  if (!roleIdsResult.success)
@@ -3413,19 +3538,19 @@ function createRbacManager(config) {
3413
3538
  async function syncSessionPermissionsForRole(roleId) {
3414
3539
  const userIdsResult = await userRoleRepository.getUserIdsByRoleId(roleId);
3415
3540
  if (!userIdsResult.success) {
3416
- logger8.error("Failed to query users for permission sync", { roleId, error: userIdsResult.error.message });
3541
+ logger9.error("Failed to query users for permission sync", { roleId, error: userIdsResult.error.message });
3417
3542
  return;
3418
3543
  }
3419
3544
  await Promise.allSettled(
3420
3545
  userIdsResult.data.map(async (userId) => {
3421
3546
  const permResult = await resolveUserPermissionCodes(userId);
3422
3547
  if (!permResult.success) {
3423
- logger8.error("Failed to resolve permissions for session sync", { userId, roleId, error: permResult.error.message });
3548
+ logger9.error("Failed to resolve permissions for session sync", { userId, roleId, error: permResult.error.message });
3424
3549
  return;
3425
3550
  }
3426
3551
  const syncResult = await session.patchUserSessions(userId, { permissions: permResult.data });
3427
3552
  if (!syncResult.success) {
3428
- logger8.error("Failed to sync session permissions", { userId, roleId, error: syncResult.error.message });
3553
+ logger9.error("Failed to sync session permissions", { userId, roleId, error: syncResult.error.message });
3429
3554
  }
3430
3555
  })
3431
3556
  );
@@ -3439,17 +3564,17 @@ function createRbacManager(config) {
3439
3564
  if (roleCodesResult.success) {
3440
3565
  updates.roles = roleCodesResult.data;
3441
3566
  } else {
3442
- logger8.error("Failed to resolve roles for session sync", { userId, error: roleCodesResult.error.message });
3567
+ logger9.error("Failed to resolve roles for session sync", { userId, error: roleCodesResult.error.message });
3443
3568
  }
3444
3569
  if (permCodesResult.success) {
3445
3570
  updates.permissions = permCodesResult.data;
3446
3571
  } else {
3447
- logger8.error("Failed to resolve permissions for session sync", { userId, error: permCodesResult.error.message });
3572
+ logger9.error("Failed to resolve permissions for session sync", { userId, error: permCodesResult.error.message });
3448
3573
  }
3449
3574
  if (updates.roles !== void 0 || updates.permissions !== void 0) {
3450
3575
  const syncResult = await session.patchUserSessions(userId, updates);
3451
3576
  if (!syncResult.success) {
3452
- logger8.error("Failed to patch user sessions", { userId, error: syncResult.error.message });
3577
+ logger9.error("Failed to patch user sessions", { userId, error: syncResult.error.message });
3453
3578
  }
3454
3579
  }
3455
3580
  }
@@ -3472,8 +3597,14 @@ function createRbacManager(config) {
3472
3597
  }
3473
3598
  return ok(void 0);
3474
3599
  }
3600
+ async function getUserAuthorization(userId) {
3601
+ return readIamCache(`authorization:${userId}`, async () => {
3602
+ const rows = await reldb.sql.query("SELECT r.code AS role_code, p.code AS permission_code FROM hai_iam_user_roles ur JOIN hai_iam_roles r ON r.id = ur.role_id LEFT JOIN hai_iam_role_permissions rp ON rp.role_id = r.id LEFT JOIN hai_iam_permissions p ON p.id = rp.permission_id WHERE ur.user_id = ?", [userId]);
3603
+ return rows.success ? ok({ roles: [...new Set(rows.data.map((row) => row.role_code))], permissions: [...new Set(rows.data.flatMap((row) => row.permission_code ? [row.permission_code] : []))] }) : rows;
3604
+ });
3605
+ }
3475
3606
  return {
3476
- getSensitiveOperations: () => rbacConfig.enabled ? permissionRepository.getSensitiveOperations() : Promise.resolve(ok([])),
3607
+ getSensitiveOperations: (operation) => rbacConfig.enabled ? readIamCache(`sensitive:${operation ?? "*"}`, () => permissionRepository.getSensitiveOperations(operation)) : Promise.resolve(ok([])),
3477
3608
  async setPermissionSensitiveOperations(permissionId, operations) {
3478
3609
  if (!Array.isArray(operations) || operations.length > 1e3 || operations.some((value) => typeof value !== "string" || !value.trim() || value.length > 128))
3479
3610
  return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sensitiveOperationsInvalid"));
@@ -3493,33 +3624,15 @@ function createRbacManager(config) {
3493
3624
  if (!rbacConfig.enabled) {
3494
3625
  return ok(true);
3495
3626
  }
3496
- const roleIdsResult = await userRoleRepository.getRoleIds(userId);
3497
- if (!roleIdsResult.success)
3498
- return roleIdsResult;
3499
- const roleIds = roleIdsResult.data;
3500
- if (roleIds.length === 0) {
3501
- return ok(false);
3502
- }
3503
- const superAdminResult = await resolveSuperAdminRoleId();
3504
- if (!superAdminResult.success)
3505
- return superAdminResult;
3506
- if (superAdminResult.data && roleIds.includes(superAdminResult.data)) {
3507
- return ok(true);
3508
- }
3509
- return hasPermissionInRoles(roleIds, permission);
3627
+ const authorization = await getUserAuthorization(userId);
3628
+ if (!authorization.success)
3629
+ return authorization;
3630
+ return ok(authorization.data.roles.includes(rbacConfig.superAdminRole) || authorization.data.permissions.some((code) => matchesPermission(permission, code)));
3510
3631
  },
3511
3632
  async getUserPermissions(userId) {
3512
3633
  return getUserPermissionsInternal(userId);
3513
3634
  },
3514
- async getUserAuthorization(userId) {
3515
- const roles = await userRoleRepository.getRoleSummaries(userId);
3516
- if (!roles.success)
3517
- return roles;
3518
- const permissions = roles.data.length ? await rolePermissionRepository.getPermissionCodesForRoles(roles.data.map((role) => role.id)) : ok([]);
3519
- if (!permissions.success)
3520
- return permissions;
3521
- return ok({ roles: roles.data.map((role) => role.code), permissions: permissions.data });
3522
- },
3635
+ getUserAuthorization,
3523
3636
  async getUserRoles(userId) {
3524
3637
  return userRoleRepository.getRoles(userId);
3525
3638
  },
@@ -3533,7 +3646,7 @@ function createRbacManager(config) {
3533
3646
  }
3534
3647
  const result = await userRoleRepository.assign(userId, roleId, tx);
3535
3648
  if (result.success) {
3536
- logger8.info("Role assigned to user", { userId, roleId });
3649
+ logger9.info("Role assigned to user", { userId, roleId });
3537
3650
  if (!tx) {
3538
3651
  await syncUserSessionAfterRoleChange(userId);
3539
3652
  }
@@ -3543,7 +3656,7 @@ function createRbacManager(config) {
3543
3656
  async removeRole(userId, roleId, tx) {
3544
3657
  const result = await userRoleRepository.remove(userId, roleId, tx);
3545
3658
  if (result.success) {
3546
- logger8.info("Role removed from user", { userId, roleId });
3659
+ logger9.info("Role removed from user", { userId, roleId });
3547
3660
  if (!tx) {
3548
3661
  await syncUserSessionAfterRoleChange(userId);
3549
3662
  }
@@ -3611,7 +3724,7 @@ function createRbacManager(config) {
3611
3724
  error
3612
3725
  );
3613
3726
  }
3614
- logger8.info("Roles synced for user", { userId, added: toAdd.length, removed: toRemove.length });
3727
+ logger9.info("Roles synced for user", { userId, added: toAdd.length, removed: toRemove.length });
3615
3728
  if (ownTx) {
3616
3729
  await syncUserSessionAfterRoleChange(userId);
3617
3730
  }
@@ -3663,10 +3776,7 @@ function createRbacManager(config) {
3663
3776
  return mapRepositoryError2("iam_createRoleFailed", commitResult.error.message);
3664
3777
  }
3665
3778
  }
3666
- logger8.info("Role created", { roleId: createdResult.data.id, code: role.code });
3667
- if (role.code === rbacConfig.superAdminRole) {
3668
- superAdminRoleId = void 0;
3669
- }
3779
+ logger9.info("Role created", { roleId: createdResult.data.id, code: role.code });
3670
3780
  return ok(createdResult.data);
3671
3781
  } catch (error) {
3672
3782
  if (ownTx)
@@ -3752,7 +3862,6 @@ function createRbacManager(config) {
3752
3862
  return mapRepositoryError2("iam_updateRoleFailed", commitResult.error.message);
3753
3863
  }
3754
3864
  }
3755
- superAdminRoleId = void 0;
3756
3865
  if (ownTx) {
3757
3866
  const affectedUsersResult = await userRoleRepository.getUserIdsByRoleId(roleId);
3758
3867
  if (affectedUsersResult.success) {
@@ -3760,7 +3869,7 @@ function createRbacManager(config) {
3760
3869
  affectedUsersResult.data.map((userId) => syncUserSessionAfterRoleChange(userId))
3761
3870
  );
3762
3871
  } else {
3763
- logger8.error("Failed to query affected users after updateRole", { roleId, error: affectedUsersResult.error.message });
3872
+ logger9.error("Failed to query affected users after updateRole", { roleId, error: affectedUsersResult.error.message });
3764
3873
  }
3765
3874
  }
3766
3875
  return ok(updatedResult.data);
@@ -3834,8 +3943,7 @@ function createRbacManager(config) {
3834
3943
  affectedUserIds.map((userId) => syncUserSessionAfterRoleChange(userId))
3835
3944
  );
3836
3945
  }
3837
- superAdminRoleId = void 0;
3838
- logger8.info("Role deleted", { roleId });
3946
+ logger9.info("Role deleted", { roleId });
3839
3947
  return ok(void 0);
3840
3948
  },
3841
3949
  // ─── 权限管理 ───
@@ -3879,7 +3987,7 @@ function createRbacManager(config) {
3879
3987
  return mapRepositoryError2("iam_createPermissionFailed", commitResult.error.message);
3880
3988
  }
3881
3989
  }
3882
- logger8.info("Permission created", { permissionId: createdResult.data.id, code: permission.code });
3990
+ logger9.info("Permission created", { permissionId: createdResult.data.id, code: permission.code });
3883
3991
  return ok(createdResult.data);
3884
3992
  } catch (error) {
3885
3993
  if (ownTx)
@@ -3983,7 +4091,7 @@ function createRbacManager(config) {
3983
4091
  affectedRoleIds.map((roleId) => syncSessionPermissionsForRole(roleId))
3984
4092
  );
3985
4093
  }
3986
- logger8.info("Permission deleted", { permissionId });
4094
+ logger9.info("Permission deleted", { permissionId });
3987
4095
  return ok(void 0);
3988
4096
  },
3989
4097
  async assignPermissionToRole(roleId, permissionId, tx) {
@@ -4005,7 +4113,7 @@ function createRbacManager(config) {
4005
4113
  }
4006
4114
  const assignResult = await rolePermissionRepository.assign(roleId, permissionId, tx);
4007
4115
  if (assignResult.success) {
4008
- logger8.info("Permission assigned to role", { roleId, permissionId });
4116
+ logger9.info("Permission assigned to role", { roleId, permissionId });
4009
4117
  if (!tx) {
4010
4118
  await syncSessionPermissionsForRole(roleId);
4011
4119
  }
@@ -4034,7 +4142,7 @@ function createRbacManager(config) {
4034
4142
  }
4035
4143
  const removeResult = await rolePermissionRepository.remove(roleId, permissionId, tx);
4036
4144
  if (removeResult.success) {
4037
- logger8.info("Permission removed from role", { roleId, permissionId });
4145
+ logger9.info("Permission removed from role", { roleId, permissionId });
4038
4146
  if (!tx) {
4039
4147
  await syncSessionPermissionsForRole(roleId);
4040
4148
  }
@@ -4052,14 +4160,16 @@ function createRbacManager(config) {
4052
4160
  }
4053
4161
  };
4054
4162
  }
4055
- var logger9 = core.logger.child({ module: "iam", scope: "audit" });
4163
+ var logger10 = core.logger.child({ module: "iam", scope: "audit" });
4056
4164
  async function observe(operation, event, tx) {
4057
4165
  const result = await operation();
4166
+ if (!audit.isInitialized)
4167
+ return result;
4058
4168
  const fields = typeof event === "function" ? event(result) : event;
4059
4169
  if (!fields)
4060
4170
  return result;
4061
4171
  if (tx && !result.success) {
4062
- logger9.warn("IAM transactional operation failed", { action: fields.action, resource: fields.resource, reasonCode: result.error.code });
4172
+ logger10.warn("IAM transactional operation failed", { action: fields.action, resource: fields.resource, reasonCode: result.error.code });
4063
4173
  return result;
4064
4174
  }
4065
4175
  const recorded = await audit.capture({
@@ -4069,7 +4179,7 @@ async function observe(operation, event, tx) {
4069
4179
  details: { ...fields.details, ...!result.success ? { reasonCode: result.error.code } : {} }
4070
4180
  }, { tx });
4071
4181
  if (!recorded.success) {
4072
- logger9.error("IAM event audit failed", { action: fields.action, resource: fields.resource, error: recorded.error });
4182
+ logger10.error("IAM event audit failed", { action: fields.action, resource: fields.resource, error: recorded.error });
4073
4183
  if (tx)
4074
4184
  return recorded;
4075
4185
  }
@@ -4292,7 +4402,29 @@ function buildIamPublicConfig(config, runtime) {
4292
4402
  initialCredentials: { enabled: Boolean(config.initialCredentials?.enabled && config.passwordReset?.enabled !== false && runtime.onPasswordResetRequest) }
4293
4403
  };
4294
4404
  }
4295
- var logger10 = core.logger.child({ module: "iam", scope: "seed" });
4405
+ async function readIamUserSecurity(repository, userId) {
4406
+ const found = await readIamCache(`user:${userId}`, async () => {
4407
+ const row = await repository.findById(userId);
4408
+ if (!row.success)
4409
+ return row;
4410
+ const user = row.data;
4411
+ return ok(user ? {
4412
+ username: user.username,
4413
+ displayName: user.displayName,
4414
+ avatarUrl: user.avatarUrl,
4415
+ enabled: user.enabled,
4416
+ accountType: user.accountType,
4417
+ accountStatus: user.accountStatus,
4418
+ accountExpiresAt: user.accountExpiresAt ? new Date(user.accountExpiresAt).getTime() : null,
4419
+ credentialVersion: user.credentialVersion ?? "",
4420
+ mustChangePassword: user.mustChangePassword,
4421
+ hasPassword: !!user.passwordHash,
4422
+ passwordUpdatedAt: user.passwordUpdatedAt?.getTime() ?? null
4423
+ } : null);
4424
+ });
4425
+ return found.success ? ok(found.data ? { ...found.data, accountExpiresAt: found.data.accountExpiresAt === null ? null : new Date(found.data.accountExpiresAt), passwordUpdatedAt: found.data.passwordUpdatedAt === null ? void 0 : new Date(found.data.passwordUpdatedAt), passwordHash: found.data.hasPassword ? "present" : void 0 } : null) : found;
4426
+ }
4427
+ var logger11 = core.logger.child({ module: "iam", scope: "seed" });
4296
4428
  var DEFAULT_ROLES = [
4297
4429
  { code: "admin", name: () => iamM("iam_seedRoleAdminName"), description: () => iamM("iam_seedRoleAdminDesc"), isSystem: true },
4298
4430
  { code: "user", name: () => iamM("iam_seedRoleUserName"), description: () => iamM("iam_seedRoleUserDesc"), isSystem: true },
@@ -4336,12 +4468,11 @@ var USER_ROLE_PERMISSIONS = ["dashboard:view", "profile:read"];
4336
4468
  async function seedIamData(authz) {
4337
4469
  try {
4338
4470
  const existingRoles = await authz.getAllRoles({ page: 1, pageSize: 1e3 });
4471
+ if (!existingRoles.success)
4472
+ return existingRoles;
4339
4473
  const existingRoleMap = /* @__PURE__ */ new Map();
4340
- if (existingRoles.success) {
4341
- for (const role of existingRoles.data.items) {
4342
- existingRoleMap.set(role.code, role.id);
4343
- }
4344
- }
4474
+ for (const role of existingRoles.data.items)
4475
+ existingRoleMap.set(role.code, role.id);
4345
4476
  const roleMap = /* @__PURE__ */ new Map();
4346
4477
  for (const role of DEFAULT_ROLES) {
4347
4478
  const existingId = existingRoleMap.get(role.code);
@@ -4352,17 +4483,23 @@ async function seedIamData(authz) {
4352
4483
  const result = await authz.createRole({ code: role.code, name: role.name(), description: role.description(), isSystem: role.isSystem });
4353
4484
  if (result.success) {
4354
4485
  roleMap.set(role.code, result.data.id);
4486
+ } else if (result.error.code === HaiIamError.ROLE_ALREADY_EXISTS.code) {
4487
+ const concurrent = await authz.getRoleByCode(role.code);
4488
+ if (!concurrent.success)
4489
+ return concurrent;
4490
+ if (!concurrent.data)
4491
+ return result;
4492
+ roleMap.set(role.code, concurrent.data.id);
4355
4493
  } else {
4356
4494
  return result;
4357
4495
  }
4358
4496
  }
4359
4497
  const existingPerms = await authz.getAllPermissions({ page: 1, pageSize: 1e3 });
4498
+ if (!existingPerms.success)
4499
+ return existingPerms;
4360
4500
  const existingPermMap = /* @__PURE__ */ new Map();
4361
- if (existingPerms.success) {
4362
- for (const perm of existingPerms.data.items) {
4363
- existingPermMap.set(perm.code, perm.id);
4364
- }
4365
- }
4501
+ for (const perm of existingPerms.data.items)
4502
+ existingPermMap.set(perm.code, perm.id);
4366
4503
  const permMap = /* @__PURE__ */ new Map();
4367
4504
  for (const perm of DEFAULT_PERMISSIONS) {
4368
4505
  const existingId = existingPermMap.get(perm.code);
@@ -4373,6 +4510,13 @@ async function seedIamData(authz) {
4373
4510
  const result = await authz.createPermission({ code: perm.code, name: perm.name(), type: perm.type, resource: perm.resource, action: perm.action });
4374
4511
  if (result.success) {
4375
4512
  permMap.set(perm.code, result.data.id);
4513
+ } else if (result.error.code === HaiIamError.PERMISSION_ALREADY_EXISTS.code) {
4514
+ const concurrent = await authz.getPermissionByCode(perm.code);
4515
+ if (!concurrent.success)
4516
+ return concurrent;
4517
+ if (!concurrent.data)
4518
+ return result;
4519
+ permMap.set(perm.code, concurrent.data.id);
4376
4520
  } else {
4377
4521
  return result;
4378
4522
  }
@@ -4380,7 +4524,9 @@ async function seedIamData(authz) {
4380
4524
  const adminRoleId = roleMap.get("admin");
4381
4525
  if (adminRoleId) {
4382
4526
  for (const [, permId] of permMap) {
4383
- await authz.assignPermissionToRole(adminRoleId, permId);
4527
+ const assigned = await authz.assignPermissionToRole(adminRoleId, permId);
4528
+ if (!assigned.success)
4529
+ return assigned;
4384
4530
  }
4385
4531
  }
4386
4532
  const userRoleId = roleMap.get("user");
@@ -4388,14 +4534,16 @@ async function seedIamData(authz) {
4388
4534
  for (const permCode of USER_ROLE_PERMISSIONS) {
4389
4535
  const permId = permMap.get(permCode);
4390
4536
  if (permId) {
4391
- await authz.assignPermissionToRole(userRoleId, permId);
4537
+ const assigned = await authz.assignPermissionToRole(userRoleId, permId);
4538
+ if (!assigned.success)
4539
+ return assigned;
4392
4540
  }
4393
4541
  }
4394
4542
  }
4395
- logger10.info("IAM seed data initialized");
4543
+ logger11.info("IAM seed data initialized");
4396
4544
  return ok(void 0);
4397
4545
  } catch (error) {
4398
- logger10.error("Failed to seed IAM data", { error });
4546
+ logger11.error("Failed to seed IAM data", { error });
4399
4547
  return err(
4400
4548
  HaiIamError.REPOSITORY_ERROR,
4401
4549
  iamM("iam_initSeedDataFailed"),
@@ -4641,7 +4789,7 @@ function stripInternalSessionFields(session) {
4641
4789
  const hasExtraKeys = Object.keys(rest).length > 0;
4642
4790
  return { ...session, data: hasExtraKeys ? rest : void 0 };
4643
4791
  }
4644
- var logger11 = core.logger.child({ module: "iam", scope: "session" });
4792
+ var logger12 = core.logger.child({ module: "iam", scope: "session" });
4645
4793
  async function createSessionOperations(deps) {
4646
4794
  try {
4647
4795
  const { config } = deps;
@@ -4659,10 +4807,10 @@ async function createSessionOperations(deps) {
4659
4807
  sessionRepository,
4660
4808
  apiKeyRepository: config.login.apikey ? await createDbApiKeyRepository() : void 0
4661
4809
  });
4662
- logger11.info("Session sub-feature initialized");
4810
+ logger12.info("Session sub-feature initialized");
4663
4811
  return ok(functions);
4664
4812
  } catch (error) {
4665
- logger11.error("Session sub-feature initialization failed", { error });
4813
+ logger12.error("Session sub-feature initialization failed", { error });
4666
4814
  return err(
4667
4815
  HaiIamError.CONFIG_ERROR,
4668
4816
  iamM("iam_initComponentFailed"),
@@ -4697,7 +4845,7 @@ function buildSessionFunctions(config) {
4697
4845
  return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
4698
4846
  if (typeof current.data.version !== "string" || expected !== void 0 && current.data.version !== expected)
4699
4847
  return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
4700
- return ok(current.data.version);
4848
+ return ok(current.data);
4701
4849
  } catch {
4702
4850
  return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_sessionExpired"));
4703
4851
  }
@@ -4707,7 +4855,10 @@ function buildSessionFunctions(config) {
4707
4855
  return ok(session);
4708
4856
  if (!config.apiKeyRepository)
4709
4857
  return err(HaiIamError.APIKEY_INVALID, iamM("iam_apikeyInvalid"));
4710
- const found = await config.apiKeyRepository.findOneById(session.apiKeyId);
4858
+ const found = await readIamCache(`apikey:${session.apiKeyId}`, async () => {
4859
+ const row = await config.apiKeyRepository.findOneById(session.apiKeyId);
4860
+ return row.success ? ok(row.data ? { userId: row.data.userId, enabled: row.data.enabled, expiresAt: row.data.expiresAt?.getTime() ?? null, scopes: row.data.scopes } : null) : row;
4861
+ });
4711
4862
  if (!found.success)
4712
4863
  return found;
4713
4864
  const key = found.data;
@@ -4715,7 +4866,7 @@ function buildSessionFunctions(config) {
4715
4866
  return err(HaiIamError.APIKEY_INVALID, iamM("iam_apikeyInvalid"));
4716
4867
  if (!key.enabled)
4717
4868
  return err(HaiIamError.APIKEY_DISABLED, iamM("iam_apikeyDisabled"));
4718
- if (key.expiresAt && /* @__PURE__ */ new Date() >= key.expiresAt)
4869
+ if (key.expiresAt && Date.now() >= key.expiresAt)
4719
4870
  return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
4720
4871
  return ok(applyApiKeyScopes({ ...session, apiKeyScopes: key.scopes }, key.scopes));
4721
4872
  }
@@ -4725,7 +4876,7 @@ function buildSessionFunctions(config) {
4725
4876
  const bound = await credential(options.userId, options.expectedCredentialVersion);
4726
4877
  if (!bound.success)
4727
4878
  return bound;
4728
- const built = candidate(options, bound.data);
4879
+ const built = candidate(options, bound.data.version);
4729
4880
  if (!built.success)
4730
4881
  return built;
4731
4882
  const { session, tokens } = built.data;
@@ -4733,7 +4884,7 @@ function buildSessionFunctions(config) {
4733
4884
  if (!saveResult.success) {
4734
4885
  return saveResult;
4735
4886
  }
4736
- logger11.debug("Session created", { userId: options.userId });
4887
+ logger12.debug("Session created", { userId: options.userId });
4737
4888
  return ok(session);
4738
4889
  } catch (error) {
4739
4890
  return err(
@@ -4764,7 +4915,7 @@ function buildSessionFunctions(config) {
4764
4915
  const bound = await credential(session.userId, session.credentialVersion);
4765
4916
  if (!bound.success)
4766
4917
  return bound;
4767
- const validated = await validateApiKey(session);
4918
+ const validated = await validateApiKey({ ...session, ...bound.data.profile });
4768
4919
  if (!validated.success)
4769
4920
  return validated;
4770
4921
  let output = validated.data;
@@ -4794,7 +4945,7 @@ function buildSessionFunctions(config) {
4794
4945
  return repo.updateByAccessToken(accessToken, data, expectedVersion);
4795
4946
  },
4796
4947
  async delete(accessToken) {
4797
- logger11.debug("Session delete requested");
4948
+ logger12.debug("Session delete requested");
4798
4949
  return repo.removeByAccessToken(accessToken);
4799
4950
  },
4800
4951
  async deleteByUserId(userId) {
@@ -4848,7 +4999,7 @@ function buildSessionFunctions(config) {
4848
4999
  const current = await credential(oldSession.userId, oldSession.credentialVersion);
4849
5000
  if (!current.success)
4850
5001
  return current;
4851
- logger11.debug("Token refreshed", { userId: oldSession.userId });
5002
+ logger12.debug("Token refreshed", { userId: oldSession.userId });
4852
5003
  return ok(next.data.tokens);
4853
5004
  } catch (error) {
4854
5005
  return err(
@@ -4866,7 +5017,7 @@ function buildSessionFunctions(config) {
4866
5017
  }
4867
5018
  };
4868
5019
  }
4869
- var logger12 = core.logger.child({ module: "iam", scope: "user" });
5020
+ var logger13 = core.logger.child({ module: "iam", scope: "user" });
4870
5021
  var USER_SORT_COLUMNS = {
4871
5022
  /** 用户名列。 */
4872
5023
  username: "username",
@@ -4891,10 +5042,10 @@ async function createUserOperations(deps) {
4891
5042
  config,
4892
5043
  onPasswordResetRequest
4893
5044
  });
4894
- logger12.info("User sub-feature initialized");
5045
+ logger13.info("User sub-feature initialized");
4895
5046
  return ok(functions);
4896
5047
  } catch (error) {
4897
- logger12.error("User sub-feature initialization failed", { error });
5048
+ logger13.error("User sub-feature initialization failed", { error });
4898
5049
  return err(
4899
5050
  HaiIamError.CONFIG_ERROR,
4900
5051
  iamM("iam_initComponentFailed"),
@@ -5063,7 +5214,7 @@ function buildRegistrationOps(ctx) {
5063
5214
  await userRepository.deleteById(createdUser.id);
5064
5215
  return assigned;
5065
5216
  }
5066
- logger12.info("User registered", { userId: createdUser.id, username: options.username });
5217
+ logger13.info("User registered", { userId: createdUser.id, username: options.username });
5067
5218
  return ok({
5068
5219
  user: toUser(createdUser),
5069
5220
  agreements: buildAgreementDisplay(agreementConfig, agreementConfig.showOnRegister)
@@ -5312,7 +5463,7 @@ function buildUserMutationOps(ctx) {
5312
5463
  if (tx) {
5313
5464
  const revoked = await sessionFunctions.deleteByUserId(userId);
5314
5465
  if (!revoked.success)
5315
- logger12.warn("Account credentials revoked; session cache cleanup failed", { userId, error: revoked.error });
5466
+ logger13.warn("Account credentials revoked; session cache cleanup failed", { userId, error: revoked.error });
5316
5467
  }
5317
5468
  const updatedResult = await userRepository.findById(userId);
5318
5469
  if (!updatedResult.success) {
@@ -5327,7 +5478,7 @@ function buildUserMutationOps(ctx) {
5327
5478
  return ok(toUser(updatedResult.data));
5328
5479
  },
5329
5480
  async deleteUser(userId) {
5330
- logger12.debug("Deleting user", { userId });
5481
+ logger13.debug("Deleting user", { userId });
5331
5482
  const userResult = await userRepository.findById(userId);
5332
5483
  if (!userResult.success) {
5333
5484
  return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
@@ -5367,7 +5518,7 @@ function buildUserMutationOps(ctx) {
5367
5518
  );
5368
5519
  }
5369
5520
  await sessionFunctions.deleteByUserId(userId);
5370
- logger12.info("User deleted", { userId });
5521
+ logger13.info("User deleted", { userId });
5371
5522
  return ok(void 0);
5372
5523
  }
5373
5524
  };
@@ -5377,7 +5528,7 @@ function buildPasswordChangeOps(ctx) {
5377
5528
  const { hashPassword } = ctx;
5378
5529
  return {
5379
5530
  async adminResetPassword(userId, newPassword) {
5380
- logger12.debug("Admin resetting user password", { userId });
5531
+ logger13.debug("Admin resetting user password", { userId });
5381
5532
  const userResult = await userRepository.findById(userId);
5382
5533
  if (!userResult.success) {
5383
5534
  return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
@@ -5402,7 +5553,7 @@ function buildPasswordChangeOps(ctx) {
5402
5553
  const revoked = await sessionFunctions.deleteByUserId(userId);
5403
5554
  if (!revoked.success)
5404
5555
  return revoked;
5405
- logger12.info("Admin reset password", { userId });
5556
+ logger13.info("Admin reset password", { userId });
5406
5557
  return ok(void 0);
5407
5558
  },
5408
5559
  async changePassword(userId, oldPassword, newPassword) {
@@ -5456,7 +5607,7 @@ function buildPasswordChangeOps(ctx) {
5456
5607
  const revoked = await sessionFunctions.deleteByUserId(userId);
5457
5608
  if (!revoked.success)
5458
5609
  return revoked;
5459
- logger12.info("Password changed", { userId });
5610
+ logger13.info("Password changed", { userId });
5460
5611
  return ok(void 0);
5461
5612
  },
5462
5613
  /**
@@ -5494,10 +5645,10 @@ async function issuePasswordReset(ctx, stored) {
5494
5645
  try {
5495
5646
  await onPasswordResetRequest(user, token, { expiresAt, idempotencyKey: `iam-reset:${tokenHash.data}` });
5496
5647
  } catch {
5497
- logger12.error("Password reset callback failed", { userId: user.id });
5648
+ logger13.error("Password reset callback failed", { userId: user.id });
5498
5649
  return err(HaiIamError.INTERNAL_ERROR, iamM("iam_notificationDeliveryUnknown"));
5499
5650
  }
5500
- logger12.info("Password reset token generated", { userId: user.id });
5651
+ logger13.info("Password reset token generated", { userId: user.id });
5501
5652
  return ok(void 0);
5502
5653
  }
5503
5654
  function buildPasswordResetOps(ctx) {
@@ -5508,17 +5659,17 @@ function buildPasswordResetOps(ctx) {
5508
5659
  const captcha = await verifyIamCaptcha(ctx.config.captcha, "passwordReset", options?.captcha);
5509
5660
  if (!captcha.success)
5510
5661
  return captcha;
5511
- logger12.debug("Password reset requested", { identifier });
5662
+ logger13.debug("Password reset requested", { identifier });
5512
5663
  const resetConfig = ctx.resetConfig;
5513
5664
  if (!resetConfig.enabled || !onPasswordResetRequest)
5514
5665
  return err(HaiIamError.PASSWORD_RESET_DISABLED, iamM("iam_passwordResetDisabled"));
5515
5666
  const userResult = await userRepository.findByIdentifier(identifier);
5516
5667
  if (!userResult.success) {
5517
- logger12.warn("Failed to look up user for password reset", { identifier });
5668
+ logger13.warn("Failed to look up user for password reset", { identifier });
5518
5669
  return ok(void 0);
5519
5670
  }
5520
5671
  if (!userResult.data) {
5521
- logger12.debug("User not found for password reset, returning ok to prevent enumeration", { identifier });
5672
+ logger13.debug("User not found for password reset, returning ok to prevent enumeration", { identifier });
5522
5673
  return ok(void 0);
5523
5674
  }
5524
5675
  const user = toUser(userResult.data);
@@ -5527,7 +5678,7 @@ function buildPasswordResetOps(ctx) {
5527
5678
  return issuePasswordReset(ctx, userResult.data);
5528
5679
  },
5529
5680
  async confirmPasswordReset(token, newPassword) {
5530
- logger12.debug("Confirming password reset");
5681
+ logger13.debug("Confirming password reset");
5531
5682
  const resetConfig = ctx.resetConfig;
5532
5683
  if (!resetConfig.enabled)
5533
5684
  return err(HaiIamError.PASSWORD_RESET_DISABLED, iamM("iam_passwordResetDisabled"));
@@ -5551,7 +5702,7 @@ function buildPasswordResetOps(ctx) {
5551
5702
  const revoked = await sessionFunctions.deleteByUserId(claimed.data.id);
5552
5703
  if (!revoked.success)
5553
5704
  return revoked;
5554
- logger12.info("Password reset confirmed", { userId: claimed.data.id });
5705
+ logger13.info("Password reset confirmed", { userId: claimed.data.id });
5555
5706
  return ok(void 0);
5556
5707
  }
5557
5708
  };
@@ -5616,7 +5767,7 @@ function buildUserFunctions(deps) {
5616
5767
  }
5617
5768
 
5618
5769
  // src/iam-main.ts
5619
- var logger13 = core.logger.child({ module: "iam", scope: "main" });
5770
+ var logger14 = core.logger.child({ module: "iam", scope: "main" });
5620
5771
  var initInProgress = false;
5621
5772
  var currentConfig = null;
5622
5773
  var currentPublicConfig = null;
@@ -5647,7 +5798,7 @@ var notInitializedUser = new Proxy({}, {
5647
5798
  var iam = {
5648
5799
  async init(config) {
5649
5800
  if (initInProgress) {
5650
- logger13.warn("IAM init already in progress, skipping concurrent call");
5801
+ logger14.warn("IAM init already in progress, skipping concurrent call");
5651
5802
  return err(
5652
5803
  HaiIamError.CONFIG_ERROR,
5653
5804
  iamM("iam_initInProgress")
@@ -5656,11 +5807,11 @@ var iam = {
5656
5807
  initInProgress = true;
5657
5808
  try {
5658
5809
  if (currentConfig !== null) {
5659
- logger13.warn("IAM module is already initialized, reinitializing");
5810
+ logger14.warn("IAM module is already initialized, reinitializing");
5660
5811
  await iam.close();
5661
5812
  }
5662
5813
  const { ldapClientFactory, ldapSyncUser } = config;
5663
- logger13.info("Initializing IAM module");
5814
+ logger14.info("Initializing IAM module");
5664
5815
  if (!reldb.isInitialized) {
5665
5816
  return err(
5666
5817
  HaiIamError.CONFIG_ERROR,
@@ -5685,7 +5836,7 @@ var iam = {
5685
5836
  }
5686
5837
  const parseResult = IamConfigSchema.safeParse(config);
5687
5838
  if (!parseResult.success) {
5688
- logger13.error("IAM config validation failed", { error: parseResult.error.message });
5839
+ logger14.error("IAM config validation failed", { error: parseResult.error.message });
5689
5840
  return err(
5690
5841
  HaiIamError.CONFIG_ERROR,
5691
5842
  iamM("iam_configError", { params: { error: parseResult.error.message } }),
@@ -5694,7 +5845,7 @@ var iam = {
5694
5845
  }
5695
5846
  const parsed = parseResult.data;
5696
5847
  const { onPasswordResetRequest, onOtpSendEmail, onOtpSendSms } = await resolveIamNotifications(config);
5697
- logger13.info("IAM notification channels resolved", { email: Boolean(onOtpSendEmail), sms: Boolean(onOtpSendSms), passwordReset: Boolean(onPasswordResetRequest) });
5848
+ logger14.info("IAM notification channels resolved", { email: Boolean(onOtpSendEmail), sms: Boolean(onOtpSendSms), passwordReset: Boolean(onPasswordResetRequest) });
5698
5849
  const migration = await migrateIam();
5699
5850
  if (!migration.success)
5700
5851
  return migration;
@@ -5702,7 +5853,7 @@ var iam = {
5702
5853
  const sessionResult = await createSessionOperations({
5703
5854
  config: parsed,
5704
5855
  readCredential: async (userId) => {
5705
- const found = await credentialUsers.findById(userId);
5856
+ const found = await readIamUserSecurity(credentialUsers, userId);
5706
5857
  if (!found.success)
5707
5858
  return found;
5708
5859
  if (found.data) {
@@ -5710,7 +5861,7 @@ var iam = {
5710
5861
  if (!passwordState.success)
5711
5862
  return passwordState;
5712
5863
  }
5713
- return ok(found.data ? { version: found.data.credentialVersion ?? "", enabled: isUserActive(found.data) } : null);
5864
+ return ok(found.data ? { version: found.data.credentialVersion ?? "", enabled: isUserActive(found.data), profile: { username: found.data.username, displayName: found.data.displayName, avatarUrl: found.data.avatarUrl } } : null);
5714
5865
  }
5715
5866
  });
5716
5867
  if (!sessionResult.success) {
@@ -5775,19 +5926,19 @@ var iam = {
5775
5926
  return;
5776
5927
  accountScanRunning = users.scanInactiveAccounts().then((result) => {
5777
5928
  if (!result.success)
5778
- logger13.error("Inactive account scan failed", { error: result.error });
5929
+ logger14.error("Inactive account scan failed", { error: result.error });
5779
5930
  }).catch((error) => {
5780
- logger13.error("Inactive account scan failed", { error });
5931
+ logger14.error("Inactive account scan failed", { error });
5781
5932
  }).finally(() => {
5782
5933
  accountScanRunning = void 0;
5783
5934
  });
5784
5935
  }, (parsed.accounts?.scanIntervalSeconds ?? 3600) * 1e3);
5785
5936
  accountScanTimer.unref();
5786
5937
  }
5787
- logger13.info("IAM module initialized");
5938
+ logger14.info("IAM module initialized");
5788
5939
  return ok(void 0);
5789
5940
  } catch (error) {
5790
- logger13.error("IAM module initialization failed", { error });
5941
+ logger14.error("IAM module initialization failed", { error });
5791
5942
  return err(
5792
5943
  HaiIamError.CONFIG_ERROR,
5793
5944
  iamM("iam_initFailed"),
@@ -5841,10 +5992,10 @@ var iam = {
5841
5992
  },
5842
5993
  async close() {
5843
5994
  if (currentConfig === null && currentAuth === null && currentUser === null && currentAuthz === null && currentSession === null) {
5844
- logger13.info("IAM module already closed, skipping");
5995
+ logger14.info("IAM module already closed, skipping");
5845
5996
  return;
5846
5997
  }
5847
- logger13.info("Closing IAM module");
5998
+ logger14.info("Closing IAM module");
5848
5999
  clearInterval(accountScanTimer);
5849
6000
  accountScanTimer = void 0;
5850
6001
  await accountScanRunning;
@@ -5861,7 +6012,7 @@ var iam = {
5861
6012
  resetUserRepoSingleton();
5862
6013
  resetRoleRepoSingleton();
5863
6014
  resetPermissionRepoSingleton();
5864
- logger13.info("IAM module closed");
6015
+ logger14.info("IAM module closed");
5865
6016
  }
5866
6017
  };
5867
6018