@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 +1 -0
- package/dist/index.js +66 -23
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
-
|
|
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
|
|
1430
|
-
|
|
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:
|
|
1438
|
-
|
|
1448
|
+
iamM("iam_updateOtpAttemptsFailed", { params: { message: incrResult.error.message } }),
|
|
1449
|
+
incrResult.error
|
|
1439
1450
|
);
|
|
1440
1451
|
}
|
|
1441
|
-
|
|
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
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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,
|
|
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 =
|
|
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 =
|
|
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),
|