mirror of https://github.com/certd/certd
parent
903fe9aa9d
commit
5f98bf24b3
|
@ -21,7 +21,13 @@
|
|||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="validateCode" label="邮件验证码">
|
||||
<email-code v-model:value="formState.validateCode" :img-code="formState.imgCode" :email="formState.input" :random-str="formState.randomStr" />
|
||||
<email-code
|
||||
v-model:value="formState.validateCode"
|
||||
:img-code="formState.imgCode"
|
||||
:email="formState.input"
|
||||
:random-str="formState.randomStr"
|
||||
verification-type="forgotPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="mobile" tab="手机号找回">
|
||||
|
@ -33,7 +39,14 @@
|
|||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="validateCode" label="手机验证码">
|
||||
<sms-code v-model:value="formState.validateCode" :img-code="formState.imgCode" :mobile="formState.input" :phone-code="formState.phoneCode" :random-str="formState.randomStr" />
|
||||
<sms-code
|
||||
v-model:value="formState.validateCode"
|
||||
:img-code="formState.imgCode"
|
||||
:mobile="formState.input"
|
||||
:phone-code="formState.phoneCode"
|
||||
:random-str="formState.randomStr"
|
||||
verification-type="forgotPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
@ -60,7 +73,7 @@
|
|||
<a-button type="primary" size="large" html-type="submit" class="submit-button"> 找回密码</a-button>
|
||||
|
||||
<div class="mt-2">
|
||||
<a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank"> 管理员无绑定邮箱或MFA丢失找回 </a>
|
||||
<a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank"> 管理员无绑定通信方式或MFA丢失找回 </a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
@ -83,7 +96,10 @@ const rules = {
|
|||
input: [{ required: true }],
|
||||
validateCode: [{ required: true }],
|
||||
imgCode: [{ required: true }, { min: 4, max: 4, message: "请输入4位图片验证码" }],
|
||||
password: [{ required: true, trigger: "change", message: "请输入密码" }],
|
||||
password: [
|
||||
{ required: true, trigger: "change", message: "请输入密码" },
|
||||
{ len: 6, message: "至少输入6位密码" },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, trigger: "change", message: "请确认密码" },
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ const props = defineProps<{
|
|||
phoneCode?: string;
|
||||
imgCode?: string;
|
||||
randomStr?: string;
|
||||
verificationType?: string;
|
||||
}>();
|
||||
const emit = defineEmits(["update:value", "change"]);
|
||||
|
||||
|
@ -58,6 +59,7 @@ async function sendSmsCode() {
|
|||
mobile: props.mobile,
|
||||
imgCode: props.imgCode,
|
||||
randomStr: props.randomStr,
|
||||
verificationType: props.verificationType,
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
|
|
@ -22,6 +22,7 @@ const props = defineProps<{
|
|||
email?: string;
|
||||
imgCode?: string;
|
||||
randomStr?: string;
|
||||
verificationType?: string;
|
||||
}>();
|
||||
const emit = defineEmits(["update:value", "change"]);
|
||||
|
||||
|
@ -53,6 +54,7 @@ async function sendSmsCode() {
|
|||
email: props.email,
|
||||
imgCode: props.imgCode,
|
||||
randomStr: props.randomStr,
|
||||
verificationType: props.verificationType,
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
|
|
@ -27,6 +27,9 @@ export class EmailCodeReq {
|
|||
|
||||
@Rule(RuleType.string().required().max(4))
|
||||
imgCode: string;
|
||||
|
||||
@Rule(RuleType.string())
|
||||
verificationType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,8 +58,20 @@ export class BasicController extends BaseController {
|
|||
@Body(ALL)
|
||||
body: EmailCodeReq
|
||||
) {
|
||||
const opts = {
|
||||
verificationType: body.verificationType,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
duration: undefined,
|
||||
};
|
||||
if(body?.verificationType === 'forgotPassword') {
|
||||
opts.title = '找回密码';
|
||||
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
||||
opts.duration = 3;
|
||||
}
|
||||
|
||||
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
||||
await this.codeService.sendEmailCode(body.email, body.randomStr);
|
||||
await this.codeService.sendEmailCode(body.email, body.randomStr, opts);
|
||||
// 设置缓存内容
|
||||
return this.ok(null);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@ import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
|||
import { BaseController, CommonException, Constants, SysSettingsService } from "@certd/lib-server";
|
||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||
import { LoginService } from "../../../modules/login/service/login-service.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api')
|
||||
export class LoginController extends BaseController {
|
||||
@Inject()
|
||||
loginService: LoginService;
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
@Inject()
|
||||
|
@ -23,6 +26,7 @@ export class LoginController extends BaseController {
|
|||
) {
|
||||
if(body.type === 'email') {
|
||||
this.codeService.checkEmailCode({
|
||||
verificationType: 'forgotPassword',
|
||||
email: body.input,
|
||||
randomStr: body.randomStr,
|
||||
validateCode: body.validateCode,
|
||||
|
@ -30,6 +34,7 @@ export class LoginController extends BaseController {
|
|||
});
|
||||
} else if(body.type === 'mobile') {
|
||||
await this.codeService.checkSmsCode({
|
||||
verificationType: 'forgotPassword',
|
||||
mobile: body.input,
|
||||
randomStr: body.randomStr,
|
||||
phoneCode: body.phoneCode,
|
||||
|
@ -39,7 +44,8 @@ export class LoginController extends BaseController {
|
|||
} else {
|
||||
throw new CommonException('暂不支持的找回类型,请联系管理员找回');
|
||||
}
|
||||
await this.userService.forgotPassword(body);
|
||||
const username = await this.userService.forgotPassword(body);
|
||||
username && this.loginService.clearCacheOnSuccess(username)
|
||||
return this.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,15 @@ export class CodeService {
|
|||
}
|
||||
/**
|
||||
*/
|
||||
async sendSmsCode(phoneCode = '86', mobile: string, randomStr: string) {
|
||||
async sendSmsCode(
|
||||
phoneCode = '86',
|
||||
mobile: string,
|
||||
randomStr: string,
|
||||
opts?: {
|
||||
duration?: number,
|
||||
verificationType?: string
|
||||
},
|
||||
) {
|
||||
if (!mobile) {
|
||||
throw new Error('手机号不能为空');
|
||||
}
|
||||
|
@ -65,6 +73,8 @@ export class CodeService {
|
|||
throw new Error('randomStr不能为空');
|
||||
}
|
||||
|
||||
const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1);
|
||||
|
||||
const sysSettings = await this.sysSettingsService.getPrivateSettings();
|
||||
if (!sysSettings.sms?.config?.accessId) {
|
||||
throw new Error('当前站点还未配置短信');
|
||||
|
@ -84,16 +94,29 @@ export class CodeService {
|
|||
phoneCode,
|
||||
});
|
||||
|
||||
const key = this.buildSmsCodeKey(phoneCode, mobile, randomStr);
|
||||
const key = this.buildSmsCodeKey(phoneCode, mobile, randomStr, opts?.verificationType);
|
||||
cache.set(key, smsCode, {
|
||||
ttl: 5 * 60 * 1000, //5分钟
|
||||
ttl: duration * 60 * 1000, //5分钟
|
||||
});
|
||||
return smsCode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param email 收件邮箱
|
||||
* @param randomStr
|
||||
* @param opts title标题 content内容模版 duration有效时间单位分钟 verificationType验证类型
|
||||
*/
|
||||
async sendEmailCode(email: string, randomStr: string) {
|
||||
async sendEmailCode(
|
||||
email: string,
|
||||
randomStr: string,
|
||||
opts?: {
|
||||
title?: string,
|
||||
content?: string,
|
||||
duration?: number,
|
||||
verificationType?: string
|
||||
},
|
||||
) {
|
||||
if (!email) {
|
||||
throw new Error('Email不能为空');
|
||||
}
|
||||
|
@ -110,15 +133,20 @@ export class CodeService {
|
|||
}
|
||||
|
||||
const code = randomNumber(4);
|
||||
const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1);
|
||||
|
||||
const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`;
|
||||
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
|
||||
|
||||
await this.emailService.send({
|
||||
subject: `【${siteTitle}】验证码`,
|
||||
content: `您的验证码是${code},请勿泄露`,
|
||||
subject: title,
|
||||
content: content,
|
||||
receivers: [email],
|
||||
});
|
||||
|
||||
const key = this.buildEmailCodeKey(email, randomStr);
|
||||
const key = this.buildEmailCodeKey(email, randomStr, opts?.verificationType);
|
||||
cache.set(key, code, {
|
||||
ttl: 5 * 60 * 1000, //5分钟
|
||||
ttl: duration * 60 * 1000, //5分钟
|
||||
});
|
||||
return code;
|
||||
}
|
||||
|
@ -126,20 +154,20 @@ export class CodeService {
|
|||
/**
|
||||
* checkSms
|
||||
*/
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; throwError: boolean }) {
|
||||
const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr);
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean }) {
|
||||
const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType);
|
||||
if (isDev()) {
|
||||
return true;
|
||||
}
|
||||
return this.checkValidateCode(key, opts.smsCode, opts.throwError);
|
||||
}
|
||||
|
||||
buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string) {
|
||||
return `sms:${phoneCode}${mobile}:${randomStr}`;
|
||||
buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) {
|
||||
return ['sms', verificationType, phoneCode, mobile, randomStr].filter(item => !!item).join(':');
|
||||
}
|
||||
|
||||
buildEmailCodeKey(email: string, randomStr: string) {
|
||||
return `email:${email}:${randomStr}`;
|
||||
buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) {
|
||||
return ['email', verificationType, email, randomStr].filter(item => !!item).join(':');
|
||||
}
|
||||
checkValidateCode(key: string, userCode: string, throwError = true) {
|
||||
//验证图片验证码
|
||||
|
@ -154,8 +182,18 @@ export class CodeService {
|
|||
return true;
|
||||
}
|
||||
|
||||
checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; throwError: boolean }) {
|
||||
const key = this.buildEmailCodeKey(opts.email, opts.randomStr);
|
||||
checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean }) {
|
||||
const key = this.buildEmailCodeKey(opts.email, opts.randomStr, opts.verificationType);
|
||||
return this.checkValidateCode(key, opts.validateCode, opts.throwError);
|
||||
}
|
||||
|
||||
compile(templateString: string) {
|
||||
return new Function(
|
||||
"data",
|
||||
` with(data || {}) {
|
||||
return \`${templateString}\`;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||
// return;
|
||||
}
|
||||
await this.resetPassword(user.id, data.password)
|
||||
return user.username;
|
||||
}
|
||||
|
||||
async changePassword(userId: any, form: any) {
|
||||
|
|
Loading…
Reference in New Issue