diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue index 8d2138b6..a3024ae4 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue @@ -47,10 +47,6 @@
- - - - {{ t("certd.saveButton") }} diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue index 45a468e9..2272ae8f 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue @@ -11,6 +11,9 @@ + + +
diff --git a/packages/ui/certd-server/src/controller/basic/code-controller.ts b/packages/ui/certd-server/src/controller/basic/code-controller.ts index fb0eedc2..bb7c0f6e 100644 --- a/packages/ui/certd-server/src/controller/basic/code-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/code-controller.ts @@ -16,6 +16,9 @@ export class SmsCodeReq { @Rule(RuleType.string().required().max(4)) imgCode: string; + + @Rule(RuleType.string()) + verificationType: string; } export class EmailCodeReq { @@ -32,6 +35,9 @@ export class EmailCodeReq { verificationType: string; } +// 找回密码的验证码有效期 +const FORGOT_PASSWORD_CODE_DURATION = 3 + /** */ @Provide() @@ -48,8 +54,18 @@ export class BasicController extends BaseController { @Body(ALL) body: SmsCodeReq ) { + const opts = { + verificationType: body.verificationType, + verificationCodeLength: undefined, + duration: undefined, + }; + if(body?.verificationType === 'forgotPassword') { + opts.duration = FORGOT_PASSWORD_CODE_DURATION; + // opts.verificationCodeLength = 6; //部分厂商这里会设置参数长度这里就不改了 + } + await this.codeService.checkCaptcha(body.randomStr, body.imgCode); - await this.codeService.sendSmsCode(body.phoneCode, body.mobile, body.randomStr); + await this.codeService.sendSmsCode(body.phoneCode, body.mobile, body.randomStr, opts); return this.ok(null); } @@ -60,6 +76,7 @@ export class BasicController extends BaseController { ) { const opts = { verificationType: body.verificationType, + verificationCodeLength: undefined, title: undefined, content: undefined, duration: undefined, @@ -67,7 +84,8 @@ export class BasicController extends BaseController { if(body?.verificationType === 'forgotPassword') { opts.title = '找回密码'; opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略'; - opts.duration = 3; + opts.duration = FORGOT_PASSWORD_CODE_DURATION; + opts.verificationCodeLength = 6; } await this.codeService.checkCaptcha(body.randomStr, body.imgCode); diff --git a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts index a497504b..4c60d394 100644 --- a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts @@ -28,6 +28,8 @@ export class LoginController extends BaseController { if(!sysSettings.selfServicePasswordRetrievalEnabled) { throw new CommonException('暂未开启自助找回'); } + // 找回密码的验证码允许错误次数 + const errorNum = 5; if(body.type === 'email') { this.codeService.checkEmailCode({ @@ -35,6 +37,7 @@ export class LoginController extends BaseController { email: body.input, randomStr: body.randomStr, validateCode: body.validateCode, + errorNum, throwError: true, }); } else if(body.type === 'mobile') { @@ -44,6 +47,7 @@ export class LoginController extends BaseController { randomStr: body.randomStr, phoneCode: body.phoneCode, smsCode: body.validateCode, + errorNum, throwError: true, }); } else { diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts index 62d108ca..c84ab3b1 100644 --- a/packages/ui/certd-server/src/modules/basic/service/code-service.ts +++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts @@ -63,7 +63,8 @@ export class CodeService { randomStr: string, opts?: { duration?: number, - verificationType?: string + verificationType?: string, + verificationCodeLength?: number, }, ) { if (!mobile) { @@ -73,7 +74,8 @@ export class CodeService { throw new Error('randomStr不能为空'); } - const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1); + const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4)); + const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1)); const sysSettings = await this.sysSettingsService.getPrivateSettings(); if (!sysSettings.sms?.config?.accessId) { @@ -87,7 +89,7 @@ export class CodeService { accessService: accessGetter, config: smsConfig, }); - const smsCode = randomNumber(4); + const smsCode = randomNumber(verificationCodeLength); await sender.sendSmsCode({ mobile, code: smsCode, @@ -114,7 +116,8 @@ export class CodeService { title?: string, content?: string, duration?: number, - verificationType?: string + verificationType?: string, + verificationCodeLength?: number, }, ) { if (!email) { @@ -132,8 +135,10 @@ export class CodeService { } } - const code = randomNumber(4); - const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1); + const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4)); + const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1)); + + const code = randomNumber(verificationCodeLength); const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`; const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`; @@ -154,12 +159,12 @@ export class CodeService { /** * checkSms */ - async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean }) { + async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean; errorNum?: number }) { const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType); if (isDev()) { return true; } - return this.checkValidateCode(key, opts.smsCode, opts.throwError); + return this.checkValidateCode(key, opts.smsCode, opts.throwError, opts.errorNum); } buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) { @@ -169,22 +174,38 @@ export class CodeService { buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) { return ['email', verificationType, email, randomStr].filter(item => !!item).join(':'); } - checkValidateCode(key: string, userCode: string, throwError = true) { + checkValidateCode(key: string, userCode: string, throwError = true, errorNum = 0) { + // 记录异常次数key + const err_num_key = key + ':err_num'; //验证图片验证码 const code = cache.get(key); if (code == null || code !== userCode) { + let maxRetryCount = false; + if (!!code && errorNum > 0) { + const err_num = cache.get(err_num_key) || 0 + if(err_num >= errorNum - 1) { + maxRetryCount = true; + cache.delete(key); + cache.delete(err_num_key); + } else { + cache.set(err_num_key, err_num + 1, { + ttl: 30 * 60 * 1000 + }); + } + } if (throwError) { - throw new CodeErrorException('验证码错误'); + throw new CodeErrorException(!maxRetryCount ? '验证码错误': '验证码错误请获取新的验证码'); } return false; } cache.delete(key); + cache.delete(err_num_key); return true; } - checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean }) { + checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean; errorNum?: number }) { const key = this.buildEmailCodeKey(opts.email, opts.randomStr, opts.verificationType); - return this.checkValidateCode(key, opts.validateCode, opts.throwError); + return this.checkValidateCode(key, opts.validateCode, opts.throwError, opts.errorNum); } compile(templateString: string) {