@h-ai/iam 0.1.0-alpha.10 → 0.1.0-alpha.12

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.d.ts CHANGED
@@ -755,6 +755,7 @@ interface UserOperations {
755
755
  *
756
756
  * @param userId - 用户 ID
757
757
  * @param options - 查询选项(可选 include: ['roles'] 同时返回角色列表)
758
+ * @param options.include - 指定需要额外展开的关联字段;传入 ['roles'] 时同时返回角色列表
758
759
  * @returns 成功返回用户信息或 null(用户不存在时)
759
760
  */
760
761
  getUser: (userId: string, options?: {
package/dist/index.js CHANGED
@@ -1362,8 +1362,18 @@ function createLdapStrategy(config) {
1362
1362
  };
1363
1363
  }
1364
1364
  var OTP_KEY_PREFIX = "hai:iam:otp:";
1365
+ var OTP_ATTEMPTS_KEY_PREFIX = "hai:iam:otp:attempts:";
1366
+ function normalizeOtpIdentifier(identifier) {
1367
+ const trimmed = identifier.trim();
1368
+ if (trimmed.includes("@"))
1369
+ return trimmed.toLowerCase();
1370
+ return trimmed.replace(/[\s-]/g, "");
1371
+ }
1365
1372
  function buildOtpKey(identifier) {
1366
- return `${OTP_KEY_PREFIX}${identifier}`;
1373
+ return `${OTP_KEY_PREFIX}${normalizeOtpIdentifier(identifier)}`;
1374
+ }
1375
+ function buildOtpAttemptsKey(identifier) {
1376
+ return `${OTP_ATTEMPTS_KEY_PREFIX}${normalizeOtpIdentifier(identifier)}`;
1367
1377
  }
1368
1378
  function restoreOtpDates(record) {
1369
1379
  return {
@@ -1383,7 +1393,7 @@ function createCacheOtpRepository() {
1383
1393
  async saveOtp(identifier, code, expiresIn) {
1384
1394
  const now = Date.now();
1385
1395
  const record = {
1386
- identifier,
1396
+ identifier: normalizeOtpIdentifier(identifier),
1387
1397
  code,
1388
1398
  expiresAt: new Date(now + expiresIn * 1e3),
1389
1399
  attempts: 0,
@@ -1397,6 +1407,7 @@ function createCacheOtpRepository() {
1397
1407
  result.error
1398
1408
  );
1399
1409
  }
1410
+ await cache.kv.del(buildOtpAttemptsKey(identifier));
1400
1411
  return ok(void 0);
1401
1412
  },
1402
1413
  async fetchOtp(identifier) {
@@ -1411,10 +1422,14 @@ function createCacheOtpRepository() {
1411
1422
  if (!result.data) {
1412
1423
  return ok(null);
1413
1424
  }
1414
- return ok(restoreOtpDates(result.data));
1425
+ const attemptsResult = await cache.kv.get(buildOtpAttemptsKey(identifier));
1426
+ const attempts = attemptsResult.success && typeof attemptsResult.data === "number" ? attemptsResult.data : 0;
1427
+ const restored = restoreOtpDates(result.data);
1428
+ return ok({ ...restored, attempts });
1415
1429
  },
1416
1430
  async incrementOtpAttempts(identifier) {
1417
1431
  const otpKey = buildOtpKey(identifier);
1432
+ const attemptsKey = buildOtpAttemptsKey(identifier);
1418
1433
  const current = await cache.kv.get(otpKey);
1419
1434
  if (!current.success) {
1420
1435
  return err(
@@ -1426,19 +1441,18 @@ function createCacheOtpRepository() {
1426
1441
  if (!current.data) {
1427
1442
  return ok(0);
1428
1443
  }
1429
- const record = restoreOtpDates(current.data);
1430
- const nextAttempts = record.attempts + 1;
1431
- const ttlResult = await cache.kv.ttl(otpKey);
1432
- const ttl = ttlResult.success && ttlResult.data > 0 ? ttlResult.data : 1;
1433
- const updateResult = await cache.kv.set(otpKey, { ...record, attempts: nextAttempts }, { ex: ttl });
1434
- if (!updateResult.success) {
1444
+ const incrResult = await cache.kv.incr(attemptsKey);
1445
+ if (!incrResult.success) {
1435
1446
  return err(
1436
1447
  HaiIamError.REPOSITORY_ERROR,
1437
- iamM("iam_updateOtpAttemptsFailed", { params: { message: updateResult.error.message } }),
1438
- updateResult.error
1448
+ iamM("iam_updateOtpAttemptsFailed", { params: { message: incrResult.error.message } }),
1449
+ incrResult.error
1439
1450
  );
1440
1451
  }
1441
- return ok(nextAttempts);
1452
+ const ttlResult = await cache.kv.ttl(otpKey);
1453
+ const ttl = ttlResult.success && ttlResult.data > 0 ? ttlResult.data : 1;
1454
+ await cache.kv.expire(attemptsKey, ttl);
1455
+ return ok(incrResult.data);
1442
1456
  },
1443
1457
  async removeOtp(identifier) {
1444
1458
  const result = await cache.kv.del(buildOtpKey(identifier));
@@ -1449,6 +1463,7 @@ function createCacheOtpRepository() {
1449
1463
  result.error
1450
1464
  );
1451
1465
  }
1466
+ await cache.kv.del(buildOtpAttemptsKey(identifier));
1452
1467
  return ok(void 0);
1453
1468
  }
1454
1469
  };
@@ -3562,6 +3577,7 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3562
3577
  }
3563
3578
  const saddResult = await cache.set_.sadd(buildUserTokensKey(userId), accessToken);
3564
3579
  if (!saddResult.success) {
3580
+ await cache.kv.del(buildTokenKey(accessToken));
3565
3581
  return err(
3566
3582
  HaiIamError.REPOSITORY_ERROR,
3567
3583
  iamM("iam_saveUserSessionCacheFailed", { params: { message: saddResult.error.message } }),
@@ -3575,6 +3591,8 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3575
3591
  { ex: refreshTokenMaxAge }
3576
3592
  );
3577
3593
  if (!refreshResult.success) {
3594
+ await cache.kv.del(buildTokenKey(accessToken));
3595
+ await cache.set_.srem(buildUserTokensKey(userId), accessToken);
3578
3596
  return err(
3579
3597
  HaiIamError.REPOSITORY_ERROR,
3580
3598
  iamM("iam_saveSessionMappingCacheFailed", { params: { message: refreshResult.error.message } }),
@@ -3635,13 +3653,18 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3635
3653
  },
3636
3654
  async removeByUserId(userId) {
3637
3655
  const tokensResult = await cache.set_.smembers(buildUserTokensKey(userId));
3638
- if (tokensResult.success) {
3639
- for (const token of tokensResult.data) {
3640
- await repo.removeByAccessToken(token);
3656
+ let removed = 0;
3657
+ if (tokensResult.success && tokensResult.data.length > 0) {
3658
+ const results = await Promise.allSettled(
3659
+ tokensResult.data.map((token) => repo.removeByAccessToken(token))
3660
+ );
3661
+ for (const r of results) {
3662
+ if (r.status === "fulfilled" && r.value.success)
3663
+ removed += 1;
3641
3664
  }
3642
3665
  }
3643
3666
  await cache.kv.del(buildUserTokensKey(userId));
3644
- return ok(void 0);
3667
+ return ok(removed);
3645
3668
  },
3646
3669
  async getByRefreshToken(refreshToken) {
3647
3670
  const mappingResult = await cache.kv.get(buildRefreshKey(refreshToken));
@@ -3720,6 +3743,13 @@ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3720
3743
  }
3721
3744
 
3722
3745
  // src/session/iam-session-functions.ts
3746
+ function stripInternalSessionFields(session) {
3747
+ if (!session.data || !("_tokenPair" in session.data))
3748
+ return session;
3749
+ const { _tokenPair, ...rest } = session.data;
3750
+ const hasExtraKeys = Object.keys(rest).length > 0;
3751
+ return { ...session, data: hasExtraKeys ? rest : void 0 };
3752
+ }
3723
3753
  var logger8 = core.logger.child({ module: "iam", scope: "session" });
3724
3754
  async function createSessionOperations(deps) {
3725
3755
  try {
@@ -3805,7 +3835,7 @@ function buildSessionFunctions(config) {
3805
3835
  expiresAt: new Date(now.getTime() + maxAge * 1e3)
3806
3836
  });
3807
3837
  }
3808
- return ok(session);
3838
+ return ok(stripInternalSessionFields(session));
3809
3839
  },
3810
3840
  async verifyToken(accessToken) {
3811
3841
  const sessionResult = await this.get(accessToken);
@@ -3824,7 +3854,7 @@ function buildSessionFunctions(config) {
3824
3854
  return repo.updateByAccessToken(accessToken, data);
3825
3855
  },
3826
3856
  async delete(accessToken) {
3827
- logger8.debug("Session deleted", { accessToken });
3857
+ logger8.debug("Session delete requested");
3828
3858
  return repo.removeByAccessToken(accessToken);
3829
3859
  },
3830
3860
  async deleteByUserId(userId) {
@@ -3832,7 +3862,7 @@ function buildSessionFunctions(config) {
3832
3862
  if (!result.success) {
3833
3863
  return result;
3834
3864
  }
3835
- return ok(0);
3865
+ return ok(result.data);
3836
3866
  },
3837
3867
  async refresh(refreshToken) {
3838
3868
  try {
@@ -4454,6 +4484,12 @@ function buildPasswordChangeOps(ctx) {
4454
4484
  );
4455
4485
  }
4456
4486
  const user = userResult.data;
4487
+ if (!user.enabled) {
4488
+ return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
4489
+ }
4490
+ if (isAccountLocked(user)) {
4491
+ return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
4492
+ }
4457
4493
  if (!user.passwordHash) {
4458
4494
  return err(
4459
4495
  HaiIamError.INVALID_CREDENTIALS,
@@ -4462,11 +4498,16 @@ function buildPasswordChangeOps(ctx) {
4462
4498
  }
4463
4499
  const verifyResult = crypto$1.password.verify(oldPassword, user.passwordHash);
4464
4500
  if (!verifyResult.success || !verifyResult.data) {
4501
+ await recordLoginFailure(userRepository, user, {
4502
+ maxLoginAttempts: ctx.loginConfig.maxLoginAttempts,
4503
+ lockoutDuration: ctx.loginConfig.lockoutDuration
4504
+ });
4465
4505
  return err(
4466
4506
  HaiIamError.INVALID_CREDENTIALS,
4467
4507
  iamM("iam_originalPasswordWrong")
4468
4508
  );
4469
4509
  }
4510
+ await resetLoginFailures(userRepository, user);
4470
4511
  const validateResult = validatePassword(newPassword);
4471
4512
  if (!validateResult.success)
4472
4513
  return validateResult;
@@ -4502,12 +4543,12 @@ function buildPasswordChangeOps(ctx) {
4502
4543
  };
4503
4544
  }
4504
4545
  function buildPasswordResetOps(ctx) {
4505
- const { userRepository, resetTokenRepository, sessionFunctions, config, onPasswordResetRequest } = ctx;
4546
+ const { userRepository, resetTokenRepository, sessionFunctions, onPasswordResetRequest } = ctx;
4506
4547
  const { validatePassword, hashPassword } = ctx;
4507
4548
  return {
4508
4549
  async requestPasswordReset(identifier) {
4509
4550
  logger9.debug("Password reset requested", { identifier });
4510
- const resetConfig = PasswordResetConfigSchema.parse(config.passwordReset ?? {});
4551
+ const resetConfig = ctx.resetConfig;
4511
4552
  const userResult = await userRepository.findByIdentifier(identifier);
4512
4553
  if (!userResult.success) {
4513
4554
  logger9.warn("Failed to look up user for password reset", { identifier });
@@ -4538,7 +4579,7 @@ function buildPasswordResetOps(ctx) {
4538
4579
  },
4539
4580
  async confirmPasswordReset(token, newPassword) {
4540
4581
  logger9.debug("Confirming password reset");
4541
- const resetConfig = PasswordResetConfigSchema.parse(config.passwordReset ?? {});
4582
+ const resetConfig = ctx.resetConfig;
4542
4583
  const validateResult = validatePassword(newPassword);
4543
4584
  if (!validateResult.success)
4544
4585
  return validateResult;
@@ -4581,7 +4622,9 @@ function buildUserFunctions(deps) {
4581
4622
  validatePassword,
4582
4623
  hashPassword,
4583
4624
  registerConfig: RegisterConfigSchema.parse(deps.config.register ?? {}),
4584
- agreementConfig: AgreementConfigSchema.parse(deps.config.agreements ?? {})
4625
+ agreementConfig: AgreementConfigSchema.parse(deps.config.agreements ?? {}),
4626
+ resetConfig: PasswordResetConfigSchema.parse(deps.config.passwordReset ?? {}),
4627
+ loginConfig: SecurityConfigSchema.parse(deps.config.security ?? {})
4585
4628
  };
4586
4629
  return {
4587
4630
  ...buildRegistrationOps(ctx),