-
- {{ t("authentication.forgotAdminPassword") }}
-
+
+
+ {{ t("authentication.forgotPassword") }}
+
diff --git a/packages/ui/certd-client/src/views/framework/login/sms-code.vue b/packages/ui/certd-client/src/views/framework/login/sms-code.vue
index b9f6e1ac..efbf6ab3 100644
--- a/packages/ui/certd-client/src/views/framework/login/sms-code.vue
+++ b/packages/ui/certd-client/src/views/framework/login/sms-code.vue
@@ -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;
diff --git a/packages/ui/certd-client/src/views/framework/register/email-code.vue b/packages/ui/certd-client/src/views/framework/register/email-code.vue
index 51e6cd15..6ee92e1f 100644
--- a/packages/ui/certd-client/src/views/framework/register/email-code.vue
+++ b/packages/ui/certd-client/src/views/framework/register/email-code.vue
@@ -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;
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 a3024ae4..8d2138b6 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,6 +47,10 @@
+
+
+
+
{{ t("certd.saveButton") }}
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 09a90009..fb0eedc2 100644
--- a/packages/ui/certd-server/src/controller/basic/code-controller.ts
+++ b/packages/ui/certd-server/src/controller/basic/code-controller.ts
@@ -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);
}
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
new file mode 100644
index 00000000..a497504b
--- /dev/null
+++ b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts
@@ -0,0 +1,56 @@
+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()
+ codeService: CodeService;
+
+ @Inject()
+ sysSettingsService: SysSettingsService;
+
+ @Post('/forgotPassword', { summary: Constants.per.guest })
+ public async forgotPassword(
+ @Body(ALL)
+ body: any,
+ ) {
+ const sysSettings = await this.sysSettingsService.getPublicSettings();
+ if(!sysSettings.selfServicePasswordRetrievalEnabled) {
+ throw new CommonException('暂未开启自助找回');
+ }
+
+ if(body.type === 'email') {
+ this.codeService.checkEmailCode({
+ verificationType: 'forgotPassword',
+ email: body.input,
+ randomStr: body.randomStr,
+ validateCode: body.validateCode,
+ throwError: true,
+ });
+ } else if(body.type === 'mobile') {
+ await this.codeService.checkSmsCode({
+ verificationType: 'forgotPassword',
+ mobile: body.input,
+ randomStr: body.randomStr,
+ phoneCode: body.phoneCode,
+ smsCode: body.validateCode,
+ throwError: true,
+ });
+ } else {
+ throw new CommonException('暂不支持的找回类型,请联系管理员找回');
+ }
+ const username = await this.userService.forgotPassword(body);
+ username && this.loginService.clearCacheOnSuccess(username)
+ return this.ok();
+ }
+}
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 a7f5122a..62d108ca 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
@@ -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}\`;
+ }
+ `
+ );
+ }
}
diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
index fec66ac4..3298d477 100644
--- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
+++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
@@ -15,6 +15,7 @@ import { DbAdapter } from '../../../db/index.js';
import { simpleNanoId, utils } from '@certd/basic';
export type RegisterType = 'username' | 'mobile' | 'email';
+export type ForgotPasswordType = 'mobile' | 'email';
export const AdminRoleId = 1
/**
@@ -23,7 +24,7 @@ export const AdminRoleId = 1
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UserService extends BaseService
{
-
+
@InjectEntityModel(UserEntity)
repository: Repository;
@Inject()
@@ -229,6 +230,29 @@ export class UserService extends BaseService {
return newUser;
}
+ async forgotPassword(
+ data: {
+ type: ForgotPasswordType; input?: string, phoneCode?: string,
+ randomStr: string, imgCode:string, validateCode: string,
+ password: string, confirmPassword: string,
+ }
+ ) {
+ if(!data.type) {
+ throw new CommonException('找回类型不能为空');
+ }
+ if(data.password !== data.confirmPassword) {
+ throw new CommonException('两次输入的密码不一致');
+ }
+ const user = await this.findOne([{ [data.type]: data.input }]);
+ console.log('user', user)
+ if(!user) {
+ throw new CommonException('用户不存在');
+ // return;
+ }
+ await this.resetPassword(user.id, data.password)
+ return user.username;
+ }
+
async changePassword(userId: any, form: any) {
const user = await this.info(userId);
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);