@h-ai/iam 0.1.0-alpha.18 → 0.1.0-alpha.19

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.md CHANGED
@@ -86,6 +86,8 @@ const hasPermission = await iam.authz.checkPermission(
86
86
  await iam.close()
87
87
  ```
88
88
 
89
+ `iam.config` 返回的是**脱敏后的配置快照**:LDAP `url` / `bindPassword` 等敏感配置会自动隐藏,便于状态检查但不能代替原始配置源。
90
+
89
91
  ## 设计说明
90
92
 
91
93
  ### 架构概览
package/dist/index.d.ts CHANGED
@@ -1364,7 +1364,7 @@ interface IamFunctions {
1364
1364
  * 关闭后访问 `iam.auth` 等属性将返回未初始化错误。
1365
1365
  */
1366
1366
  close: () => Promise<void>;
1367
- /** 当前配置(未初始化时为 null) */
1367
+ /** 当前脱敏后的配置快照(未初始化时为 null) */
1368
1368
  readonly config: IamConfig | null;
1369
1369
  /** 是否已初始化 */
1370
1370
  readonly isInitialized: boolean;
package/dist/index.js CHANGED
@@ -663,18 +663,18 @@ var API_KEY_FIELDS = [
663
663
  }
664
664
  ];
665
665
  var apiKeyRepoInstance = null;
666
- var apiKeyRepoDbConfig = null;
666
+ var apiKeyRepoSqlOps = null;
667
667
  function resetApiKeyRepoSingleton() {
668
668
  apiKeyRepoInstance = null;
669
- apiKeyRepoDbConfig = null;
669
+ apiKeyRepoSqlOps = null;
670
670
  }
671
671
  async function createDbApiKeyRepository() {
672
- if (apiKeyRepoInstance && apiKeyRepoDbConfig === reldb.config)
672
+ if (apiKeyRepoInstance && apiKeyRepoSqlOps === reldb.sql)
673
673
  return apiKeyRepoInstance;
674
674
  const repo = new DbApiKeyRepository();
675
675
  await repo.count();
676
676
  apiKeyRepoInstance = repo;
677
- apiKeyRepoDbConfig = reldb.config;
677
+ apiKeyRepoSqlOps = reldb.sql;
678
678
  return repo;
679
679
  }
680
680
  var DbApiKeyRepository = class extends BaseReldbCrudRepository {
@@ -893,18 +893,18 @@ var USER_FIELDS = [
893
893
  }
894
894
  ];
895
895
  var userRepoInstance = null;
896
- var userRepoDbConfig = null;
896
+ var userRepoSqlOps = null;
897
897
  function resetUserRepoSingleton() {
898
898
  userRepoInstance = null;
899
- userRepoDbConfig = null;
899
+ userRepoSqlOps = null;
900
900
  }
901
901
  async function createDbUserRepository() {
902
- if (userRepoInstance && userRepoDbConfig === reldb.config)
902
+ if (userRepoInstance && userRepoSqlOps === reldb.sql)
903
903
  return userRepoInstance;
904
904
  const repo = new DbUserRepository();
905
905
  await repo.count();
906
906
  userRepoInstance = repo;
907
- userRepoDbConfig = reldb.config;
907
+ userRepoSqlOps = reldb.sql;
908
908
  return repo;
909
909
  }
910
910
  var DbUserRepository = class extends BaseReldbCrudRepository {
@@ -2103,18 +2103,18 @@ var PERMISSION_FIELDS = [
2103
2103
  }
2104
2104
  ];
2105
2105
  var permRepoInstance = null;
2106
- var permRepoDbConfig = null;
2106
+ var permRepoSqlOps = null;
2107
2107
  function resetPermissionRepoSingleton() {
2108
2108
  permRepoInstance = null;
2109
- permRepoDbConfig = null;
2109
+ permRepoSqlOps = null;
2110
2110
  }
2111
2111
  async function createDbPermissionRepository() {
2112
- if (permRepoInstance && permRepoDbConfig === reldb.config)
2112
+ if (permRepoInstance && permRepoSqlOps === reldb.sql)
2113
2113
  return permRepoInstance;
2114
2114
  const repo = new DbPermissionRepository();
2115
2115
  await repo.count();
2116
2116
  permRepoInstance = repo;
2117
- permRepoDbConfig = reldb.config;
2117
+ permRepoSqlOps = reldb.sql;
2118
2118
  return repo;
2119
2119
  }
2120
2120
  var DbPermissionRepository = class extends BaseReldbCrudRepository {
@@ -2619,18 +2619,18 @@ var ROLE_FIELDS = [
2619
2619
  }
2620
2620
  ];
2621
2621
  var roleRepoInstance = null;
2622
- var roleRepoDbConfig = null;
2622
+ var roleRepoSqlOps = null;
2623
2623
  function resetRoleRepoSingleton() {
2624
2624
  roleRepoInstance = null;
2625
- roleRepoDbConfig = null;
2625
+ roleRepoSqlOps = null;
2626
2626
  }
2627
2627
  async function createDbRoleRepository() {
2628
- if (roleRepoInstance && roleRepoDbConfig === reldb.config)
2628
+ if (roleRepoInstance && roleRepoSqlOps === reldb.sql)
2629
2629
  return roleRepoInstance;
2630
2630
  const repo = new DbRoleRepository();
2631
2631
  await repo.count();
2632
2632
  roleRepoInstance = repo;
2633
- roleRepoDbConfig = reldb.config;
2633
+ roleRepoSqlOps = reldb.sql;
2634
2634
  return repo;
2635
2635
  }
2636
2636
  var DbRoleRepository = class extends BaseReldbCrudRepository {
@@ -3561,6 +3561,13 @@ function restoreSessionDates(session) {
3561
3561
  expiresAt: session.expiresAt instanceof Date ? session.expiresAt : new Date(session.expiresAt)
3562
3562
  };
3563
3563
  }
3564
+ function buildRepositoryError(messageKey, message, cause) {
3565
+ return err(
3566
+ HaiIamError.REPOSITORY_ERROR,
3567
+ iamM(messageKey, { params: { message } }),
3568
+ cause
3569
+ );
3570
+ }
3564
3571
  function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3565
3572
  const repo = {
3566
3573
  async save(session, tokenPair) {
@@ -3639,31 +3646,82 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3639
3646
  return ok(void 0);
3640
3647
  },
3641
3648
  async removeByAccessToken(accessToken) {
3642
- const sessionResult = await cache.kv.get(buildTokenKey(accessToken));
3643
- if (sessionResult.success && sessionResult.data) {
3649
+ const tokenKey = buildTokenKey(accessToken);
3650
+ const sessionResult = await cache.kv.get(tokenKey);
3651
+ if (!sessionResult.success) {
3652
+ return buildRepositoryError(
3653
+ "iam_querySessionMappingCacheFailed",
3654
+ sessionResult.error.message,
3655
+ sessionResult.error
3656
+ );
3657
+ }
3658
+ if (sessionResult.data) {
3644
3659
  const session = sessionResult.data;
3645
- await cache.set_.srem(buildUserTokensKey(session.userId), accessToken);
3660
+ const removeUserTokenResult = await cache.set_.srem(buildUserTokensKey(session.userId), accessToken);
3661
+ if (!removeUserTokenResult.success) {
3662
+ return buildRepositoryError(
3663
+ "iam_deleteUserSessionCacheFailed",
3664
+ removeUserTokenResult.error.message,
3665
+ removeUserTokenResult.error
3666
+ );
3667
+ }
3646
3668
  const tokenPair = session.data?._tokenPair;
3647
3669
  if (tokenPair?.refreshToken) {
3648
- await cache.kv.del(buildRefreshKey(tokenPair.refreshToken));
3670
+ const removeRefreshTokenResult = await cache.kv.del(buildRefreshKey(tokenPair.refreshToken));
3671
+ if (!removeRefreshTokenResult.success) {
3672
+ return buildRepositoryError(
3673
+ "iam_deleteSessionMappingCacheFailed",
3674
+ removeRefreshTokenResult.error.message,
3675
+ removeRefreshTokenResult.error
3676
+ );
3677
+ }
3649
3678
  }
3650
3679
  }
3651
- await cache.kv.del(buildTokenKey(accessToken));
3680
+ const removeSessionResult = await cache.kv.del(tokenKey);
3681
+ if (!removeSessionResult.success) {
3682
+ return buildRepositoryError(
3683
+ "iam_deleteSessionMappingCacheFailed",
3684
+ removeSessionResult.error.message,
3685
+ removeSessionResult.error
3686
+ );
3687
+ }
3652
3688
  return ok(void 0);
3653
3689
  },
3654
3690
  async removeByUserId(userId) {
3655
3691
  const tokensResult = await cache.set_.smembers(buildUserTokensKey(userId));
3692
+ if (!tokensResult.success) {
3693
+ return buildRepositoryError(
3694
+ "iam_queryUserSessionCacheFailed",
3695
+ tokensResult.error.message,
3696
+ tokensResult.error
3697
+ );
3698
+ }
3656
3699
  let removed = 0;
3657
- if (tokensResult.success && tokensResult.data.length > 0) {
3700
+ if (tokensResult.data.length > 0) {
3658
3701
  const results = await Promise.allSettled(
3659
3702
  tokensResult.data.map((token) => repo.removeByAccessToken(token))
3660
3703
  );
3661
3704
  for (const r of results) {
3662
- if (r.status === "fulfilled" && r.value.success)
3663
- removed += 1;
3705
+ if (r.status === "rejected") {
3706
+ return buildRepositoryError(
3707
+ "iam_deleteSessionFailed",
3708
+ String(r.reason),
3709
+ r.reason
3710
+ );
3711
+ }
3712
+ if (!r.value.success)
3713
+ return r.value;
3714
+ removed += 1;
3664
3715
  }
3665
3716
  }
3666
- await cache.kv.del(buildUserTokensKey(userId));
3717
+ const removeUserTokensKeyResult = await cache.kv.del(buildUserTokensKey(userId));
3718
+ if (!removeUserTokensKeyResult.success) {
3719
+ return buildRepositoryError(
3720
+ "iam_deleteUserSessionCacheFailed",
3721
+ removeUserTokensKeyResult.error.message,
3722
+ removeUserTokensKeyResult.error
3723
+ );
3724
+ }
3667
3725
  return ok(removed);
3668
3726
  },
3669
3727
  async getByRefreshToken(refreshToken) {
@@ -3714,8 +3772,13 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3714
3772
  const staleTokens = [];
3715
3773
  const writeOps = [];
3716
3774
  for (const entry of readResults) {
3717
- if (entry.status === "rejected")
3718
- continue;
3775
+ if (entry.status === "rejected") {
3776
+ return buildRepositoryError(
3777
+ "iam_queryUserSessionCacheFailed",
3778
+ String(entry.reason),
3779
+ entry.reason
3780
+ );
3781
+ }
3719
3782
  const { token, sessionKey, sessionResult, ttlResult } = entry.value;
3720
3783
  if (!sessionResult.success || !sessionResult.data || !ttlResult.success || ttlResult.data <= 0) {
3721
3784
  staleTokens.push(token);
@@ -3725,16 +3788,31 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3725
3788
  writeOps.push(cache.kv.set(sessionKey, updated, { ex: ttlResult.data }));
3726
3789
  }
3727
3790
  const writeResults = await Promise.allSettled(writeOps);
3728
- const failedWrite = writeResults.find((r) => r.status === "fulfilled" && !r.value.success);
3729
- if (failedWrite) {
3730
- const failedResult = failedWrite.value;
3731
- return err(
3732
- HaiIamError.REPOSITORY_ERROR,
3733
- iamM("iam_saveUserSessionCacheFailed", { params: { message: failedResult.error.message } })
3734
- );
3791
+ for (const writeResult of writeResults) {
3792
+ if (writeResult.status === "rejected") {
3793
+ return buildRepositoryError(
3794
+ "iam_saveUserSessionCacheFailed",
3795
+ String(writeResult.reason),
3796
+ writeResult.reason
3797
+ );
3798
+ }
3799
+ if (!writeResult.value.success) {
3800
+ return buildRepositoryError(
3801
+ "iam_saveUserSessionCacheFailed",
3802
+ writeResult.value.error.message,
3803
+ writeResult.value.error
3804
+ );
3805
+ }
3735
3806
  }
3736
3807
  if (staleTokens.length > 0) {
3737
- await cache.set_.srem(buildUserTokensKey(userId), ...staleTokens);
3808
+ const staleCleanupResult = await cache.set_.srem(buildUserTokensKey(userId), ...staleTokens);
3809
+ if (!staleCleanupResult.success) {
3810
+ return buildRepositoryError(
3811
+ "iam_deleteUserSessionCacheFailed",
3812
+ staleCleanupResult.error.message,
3813
+ staleCleanupResult.error
3814
+ );
3815
+ }
3738
3816
  }
3739
3817
  return ok(void 0);
3740
3818
  }
@@ -3825,15 +3903,21 @@ function buildSessionFunctions(config) {
3825
3903
  return ok(null);
3826
3904
  }
3827
3905
  if (/* @__PURE__ */ new Date() > session.expiresAt) {
3828
- await repo.removeByAccessToken(accessToken);
3906
+ const removeResult = await repo.removeByAccessToken(accessToken);
3907
+ if (!removeResult.success) {
3908
+ return removeResult;
3909
+ }
3829
3910
  return ok(null);
3830
3911
  }
3831
3912
  if (sliding) {
3832
3913
  const now = /* @__PURE__ */ new Date();
3833
- await repo.updateByAccessToken(accessToken, {
3914
+ const updateResult = await repo.updateByAccessToken(accessToken, {
3834
3915
  lastActiveAt: now,
3835
3916
  expiresAt: new Date(now.getTime() + maxAge * 1e3)
3836
3917
  });
3918
+ if (!updateResult.success) {
3919
+ return updateResult;
3920
+ }
3837
3921
  }
3838
3922
  return ok(stripInternalSessionFields(session));
3839
3923
  },
@@ -3877,7 +3961,10 @@ function buildSessionFunctions(config) {
3877
3961
  iamM("iam_refreshTokenExpired")
3878
3962
  );
3879
3963
  }
3880
- await repo.removeByAccessToken(oldSession.accessToken);
3964
+ const removeOldSessionResult = await repo.removeByAccessToken(oldSession.accessToken);
3965
+ if (!removeOldSessionResult.success) {
3966
+ return removeOldSessionResult;
3967
+ }
3881
3968
  const newSessionResult = await this.create({
3882
3969
  userId: oldSession.userId,
3883
3970
  username: oldSession.username,
@@ -3929,12 +4016,23 @@ function hashResetToken(token) {
3929
4016
  }
3930
4017
  var RESET_TOKEN_KEY_PREFIX = "hai:iam:reset:";
3931
4018
  var RESET_ATTEMPTS_KEY_PREFIX = "hai:iam:reset:attempts:";
4019
+ var RESET_USER_TOKEN_KEY_PREFIX = "hai:iam:reset:user:";
3932
4020
  function buildResetTokenKey(hashedToken) {
3933
4021
  return `${RESET_TOKEN_KEY_PREFIX}${hashedToken}`;
3934
4022
  }
3935
4023
  function buildAttemptsKey(userId) {
3936
4024
  return `${RESET_ATTEMPTS_KEY_PREFIX}${userId}`;
3937
4025
  }
4026
+ function buildUserTokenKey(userId) {
4027
+ return `${RESET_USER_TOKEN_KEY_PREFIX}${userId}`;
4028
+ }
4029
+ function buildResetTokenRepositoryError(messageKey, message, cause) {
4030
+ return err(
4031
+ HaiIamError.REPOSITORY_ERROR,
4032
+ iamM(messageKey, { params: { message } }),
4033
+ cause
4034
+ );
4035
+ }
3938
4036
  var resetTokenRepoInstance = null;
3939
4037
  function resetResetTokenRepoSingleton() {
3940
4038
  resetTokenRepoInstance = null;
@@ -3942,6 +4040,22 @@ function resetResetTokenRepoSingleton() {
3942
4040
  function createCacheResetTokenRepository() {
3943
4041
  if (resetTokenRepoInstance)
3944
4042
  return resetTokenRepoInstance;
4043
+ async function clearTokenState(hashedToken, userId) {
4044
+ const cleanupResults = await Promise.all([
4045
+ cache.kv.del(buildResetTokenKey(hashedToken)),
4046
+ cache.kv.del(buildAttemptsKey(userId)),
4047
+ cache.kv.del(buildUserTokenKey(userId))
4048
+ ]);
4049
+ const failedCleanup = cleanupResults.find((result) => !result.success);
4050
+ if (failedCleanup && !failedCleanup.success) {
4051
+ return buildResetTokenRepositoryError(
4052
+ "iam_saveResetTokenFailed",
4053
+ failedCleanup.error.message,
4054
+ failedCleanup.error
4055
+ );
4056
+ }
4057
+ return ok(void 0);
4058
+ }
3945
4059
  const repo = {
3946
4060
  async saveToken(token, userId, expiresAt) {
3947
4061
  const hashResult = hashResetToken(token);
@@ -3950,6 +4064,25 @@ function createCacheResetTokenRepository() {
3950
4064
  }
3951
4065
  const hashedToken = hashResult.data;
3952
4066
  const ttlSeconds = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
4067
+ const userTokenKey = buildUserTokenKey(userId);
4068
+ const existingTokenResult = await cache.kv.get(userTokenKey);
4069
+ if (!existingTokenResult.success) {
4070
+ return buildResetTokenRepositoryError(
4071
+ "iam_queryResetTokenFailed",
4072
+ existingTokenResult.error.message,
4073
+ existingTokenResult.error
4074
+ );
4075
+ }
4076
+ if (existingTokenResult.data && existingTokenResult.data !== hashedToken) {
4077
+ const removeExistingTokenResult = await cache.kv.del(buildResetTokenKey(existingTokenResult.data));
4078
+ if (!removeExistingTokenResult.success) {
4079
+ return buildResetTokenRepositoryError(
4080
+ "iam_saveResetTokenFailed",
4081
+ removeExistingTokenResult.error.message,
4082
+ removeExistingTokenResult.error
4083
+ );
4084
+ }
4085
+ }
3953
4086
  const setResult = await cache.kv.set(buildResetTokenKey(hashedToken), userId, { ex: ttlSeconds });
3954
4087
  if (!setResult.success) {
3955
4088
  return err(
@@ -3958,11 +4091,24 @@ function createCacheResetTokenRepository() {
3958
4091
  setResult.error
3959
4092
  );
3960
4093
  }
4094
+ const saveUserTokenResult = await cache.kv.set(userTokenKey, hashedToken, { ex: ttlSeconds });
4095
+ if (!saveUserTokenResult.success) {
4096
+ await cache.kv.del(buildResetTokenKey(hashedToken));
4097
+ return buildResetTokenRepositoryError(
4098
+ "iam_saveResetTokenFailed",
4099
+ saveUserTokenResult.error.message,
4100
+ saveUserTokenResult.error
4101
+ );
4102
+ }
3961
4103
  const attemptsResult = await cache.kv.set(buildAttemptsKey(userId), 0, { ex: ttlSeconds });
3962
4104
  if (!attemptsResult.success) {
3963
- return err(
3964
- HaiIamError.REPOSITORY_ERROR,
3965
- iamM("iam_saveResetTokenFailed", { params: { message: attemptsResult.error.message } }),
4105
+ await Promise.all([
4106
+ cache.kv.del(buildResetTokenKey(hashedToken)),
4107
+ cache.kv.del(userTokenKey)
4108
+ ]);
4109
+ return buildResetTokenRepositoryError(
4110
+ "iam_saveResetTokenFailed",
4111
+ attemptsResult.error.message,
3966
4112
  attemptsResult.error
3967
4113
  );
3968
4114
  }
@@ -3992,10 +4138,19 @@ function createCacheResetTokenRepository() {
3992
4138
  const userId = result.data;
3993
4139
  const attemptsKey = buildAttemptsKey(userId);
3994
4140
  const incrResult = await cache.kv.incr(attemptsKey);
3995
- const nextAttempts = incrResult.success ? incrResult.data : 1;
4141
+ if (!incrResult.success) {
4142
+ return buildResetTokenRepositoryError(
4143
+ "iam_saveResetTokenFailed",
4144
+ incrResult.error.message,
4145
+ incrResult.error
4146
+ );
4147
+ }
4148
+ const nextAttempts = incrResult.data;
3996
4149
  if (nextAttempts > maxAttempts) {
3997
- await cache.kv.del(tokenKey);
3998
- await cache.kv.del(attemptsKey);
4150
+ const clearResult = await clearTokenState(hashedToken, userId);
4151
+ if (!clearResult.success) {
4152
+ return clearResult;
4153
+ }
3999
4154
  return err(
4000
4155
  HaiIamError.RESET_TOKEN_MAX_ATTEMPTS,
4001
4156
  iamM("iam_resetTokenMaxAttempts")
@@ -4011,11 +4166,17 @@ function createCacheResetTokenRepository() {
4011
4166
  const hashedToken = hashResult.data;
4012
4167
  const tokenKey = buildResetTokenKey(hashedToken);
4013
4168
  const result = await cache.kv.get(tokenKey);
4014
- if (result.success && result.data) {
4015
- await cache.kv.del(buildAttemptsKey(result.data));
4169
+ if (!result.success) {
4170
+ return buildResetTokenRepositoryError(
4171
+ "iam_queryResetTokenFailed",
4172
+ result.error.message,
4173
+ result.error
4174
+ );
4016
4175
  }
4017
- await cache.kv.del(tokenKey);
4018
- return ok(void 0);
4176
+ if (!result.data) {
4177
+ return ok(void 0);
4178
+ }
4179
+ return clearTokenState(hashedToken, result.data);
4019
4180
  }
4020
4181
  };
4021
4182
  resetTokenRepoInstance = repo;
@@ -4637,6 +4798,9 @@ function buildUserFunctions(deps) {
4637
4798
 
4638
4799
  // src/iam-main.ts
4639
4800
  var logger10 = core.logger.child({ module: "iam", scope: "main" });
4801
+ function sanitizeIamConfig(config) {
4802
+ return core.sanitize.sanitizeSensitiveFields(config);
4803
+ }
4640
4804
  var initInProgress = false;
4641
4805
  var currentConfig = null;
4642
4806
  var currentAuth = null;
@@ -4789,7 +4953,7 @@ var iam = {
4789
4953
  return currentApiKey ?? notInitializedApiKey;
4790
4954
  },
4791
4955
  get config() {
4792
- return currentConfig;
4956
+ return currentConfig ? sanitizeIamConfig(currentConfig) : null;
4793
4957
  },
4794
4958
  get isInitialized() {
4795
4959
  return currentConfig !== null;