mirror of https://github.com/certd/certd
parent
2a9a513d85
commit
fe03f9942b
|
@ -47,10 +47,6 @@
|
||||||
<div class="helper" v-html="t('certd.commonCnameHelper')"></div>
|
<div class="helper" v-html="t('certd.commonCnameHelper')"></div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.enableCommonSelfServicePasswordRetrieval')" :name="['public', 'selfServicePasswordRetrievalEnabled']">
|
|
||||||
<a-switch v-model:checked="formState.public.selfServicePasswordRetrievalEnabled" />
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
||||||
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
|
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
<a-form-item :label="t('certd.enableSelfRegistration')" :name="['public', 'registerEnabled']">
|
<a-form-item :label="t('certd.enableSelfRegistration')" :name="['public', 'registerEnabled']">
|
||||||
<a-switch v-model:checked="formState.public.registerEnabled" />
|
<a-switch v-model:checked="formState.public.registerEnabled" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('certd.enableCommonSelfServicePasswordRetrieval')" :name="['public', 'selfServicePasswordRetrievalEnabled']">
|
||||||
|
<a-switch v-model:checked="formState.public.selfServicePasswordRetrievalEnabled" />
|
||||||
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.enableUserValidityPeriod')" :name="['public', 'userValidTimeEnabled']">
|
<a-form-item :label="t('certd.enableUserValidityPeriod')" :name="['public', 'userValidTimeEnabled']">
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<a-switch v-model:checked="formState.public.userValidTimeEnabled" :disabled="!settingsStore.isPlus" />
|
<a-switch v-model:checked="formState.public.userValidTimeEnabled" :disabled="!settingsStore.isPlus" />
|
||||||
|
|
|
@ -16,6 +16,9 @@ export class SmsCodeReq {
|
||||||
|
|
||||||
@Rule(RuleType.string().required().max(4))
|
@Rule(RuleType.string().required().max(4))
|
||||||
imgCode: string;
|
imgCode: string;
|
||||||
|
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
verificationType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmailCodeReq {
|
export class EmailCodeReq {
|
||||||
|
@ -32,6 +35,9 @@ export class EmailCodeReq {
|
||||||
verificationType: string;
|
verificationType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 找回密码的验证码有效期
|
||||||
|
const FORGOT_PASSWORD_CODE_DURATION = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
|
@ -48,8 +54,18 @@ export class BasicController extends BaseController {
|
||||||
@Body(ALL)
|
@Body(ALL)
|
||||||
body: SmsCodeReq
|
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.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);
|
return this.ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +76,7 @@ export class BasicController extends BaseController {
|
||||||
) {
|
) {
|
||||||
const opts = {
|
const opts = {
|
||||||
verificationType: body.verificationType,
|
verificationType: body.verificationType,
|
||||||
|
verificationCodeLength: undefined,
|
||||||
title: undefined,
|
title: undefined,
|
||||||
content: undefined,
|
content: undefined,
|
||||||
duration: undefined,
|
duration: undefined,
|
||||||
|
@ -67,7 +84,8 @@ export class BasicController extends BaseController {
|
||||||
if(body?.verificationType === 'forgotPassword') {
|
if(body?.verificationType === 'forgotPassword') {
|
||||||
opts.title = '找回密码';
|
opts.title = '找回密码';
|
||||||
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
||||||
opts.duration = 3;
|
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
||||||
|
opts.verificationCodeLength = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
||||||
|
|
|
@ -28,6 +28,8 @@ export class LoginController extends BaseController {
|
||||||
if(!sysSettings.selfServicePasswordRetrievalEnabled) {
|
if(!sysSettings.selfServicePasswordRetrievalEnabled) {
|
||||||
throw new CommonException('暂未开启自助找回');
|
throw new CommonException('暂未开启自助找回');
|
||||||
}
|
}
|
||||||
|
// 找回密码的验证码允许错误次数
|
||||||
|
const errorNum = 5;
|
||||||
|
|
||||||
if(body.type === 'email') {
|
if(body.type === 'email') {
|
||||||
this.codeService.checkEmailCode({
|
this.codeService.checkEmailCode({
|
||||||
|
@ -35,6 +37,7 @@ export class LoginController extends BaseController {
|
||||||
email: body.input,
|
email: body.input,
|
||||||
randomStr: body.randomStr,
|
randomStr: body.randomStr,
|
||||||
validateCode: body.validateCode,
|
validateCode: body.validateCode,
|
||||||
|
errorNum,
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
} else if(body.type === 'mobile') {
|
} else if(body.type === 'mobile') {
|
||||||
|
@ -44,6 +47,7 @@ export class LoginController extends BaseController {
|
||||||
randomStr: body.randomStr,
|
randomStr: body.randomStr,
|
||||||
phoneCode: body.phoneCode,
|
phoneCode: body.phoneCode,
|
||||||
smsCode: body.validateCode,
|
smsCode: body.validateCode,
|
||||||
|
errorNum,
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -63,7 +63,8 @@ export class CodeService {
|
||||||
randomStr: string,
|
randomStr: string,
|
||||||
opts?: {
|
opts?: {
|
||||||
duration?: number,
|
duration?: number,
|
||||||
verificationType?: string
|
verificationType?: string,
|
||||||
|
verificationCodeLength?: number,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (!mobile) {
|
if (!mobile) {
|
||||||
|
@ -73,7 +74,8 @@ export class CodeService {
|
||||||
throw new Error('randomStr不能为空');
|
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();
|
const sysSettings = await this.sysSettingsService.getPrivateSettings();
|
||||||
if (!sysSettings.sms?.config?.accessId) {
|
if (!sysSettings.sms?.config?.accessId) {
|
||||||
|
@ -87,7 +89,7 @@ export class CodeService {
|
||||||
accessService: accessGetter,
|
accessService: accessGetter,
|
||||||
config: smsConfig,
|
config: smsConfig,
|
||||||
});
|
});
|
||||||
const smsCode = randomNumber(4);
|
const smsCode = randomNumber(verificationCodeLength);
|
||||||
await sender.sendSmsCode({
|
await sender.sendSmsCode({
|
||||||
mobile,
|
mobile,
|
||||||
code: smsCode,
|
code: smsCode,
|
||||||
|
@ -114,7 +116,8 @@ export class CodeService {
|
||||||
title?: string,
|
title?: string,
|
||||||
content?: string,
|
content?: string,
|
||||||
duration?: number,
|
duration?: number,
|
||||||
verificationType?: string
|
verificationType?: string,
|
||||||
|
verificationCodeLength?: number,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
@ -132,8 +135,10 @@ export class CodeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = randomNumber(4);
|
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||||
const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1);
|
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 title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`;
|
||||||
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
|
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
|
||||||
|
@ -154,12 +159,12 @@ export class CodeService {
|
||||||
/**
|
/**
|
||||||
* checkSms
|
* 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);
|
const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType);
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
return true;
|
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) {
|
buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) {
|
||||||
|
@ -169,22 +174,38 @@ export class CodeService {
|
||||||
buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) {
|
buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) {
|
||||||
return ['email', verificationType, email, randomStr].filter(item => !!item).join(':');
|
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);
|
const code = cache.get(key);
|
||||||
if (code == null || code !== userCode) {
|
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) {
|
if (throwError) {
|
||||||
throw new CodeErrorException('验证码错误');
|
throw new CodeErrorException(!maxRetryCount ? '验证码错误': '验证码错误请获取新的验证码');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
cache.delete(key);
|
cache.delete(key);
|
||||||
|
cache.delete(err_num_key);
|
||||||
return true;
|
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);
|
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) {
|
compile(templateString: string) {
|
||||||
|
|
Loading…
Reference in New Issue