diff --git a/AGENTS.md b/AGENTS.md index e878d1064..3a008f4ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,7 +105,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。 - 前端 `pnpm dev`:启动 Vite 开发服务 - 前端 `pnpm build`:生产构建 -- 前端 `pnpm tsc`:类型检查 +- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。 - 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本 ## 流水线与插件模型 @@ -216,5 +216,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd - 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。 - 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。 - 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。 -- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write `,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix `;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。 -- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。 +- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write `,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix `;不要运行 `vue-tsc` / `pnpm tsc`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。 +- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。 diff --git a/README.md b/README.md index 79987d561..f4d96c2a0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永 * **开放接口支持**: 提供RESTful API接口,方便集成到其他系统 * **站点证书监控**: 定时监控网站证书的过期时间 * **多用户管理**: 用户可以管理自己的证书流水线 +* **项目管理**: 企业级项目管理模式 * **多语言支持**: 中英双语切换 * **无忧升级**: 版本向下兼容 diff --git a/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts b/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts index aba15f531..77a08fb85 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts @@ -78,8 +78,13 @@ export default { passkeyRegisterFailed: "Passkey registration failed", title: "Change Password", weakPasswordWarning: "For your account security, please change your password immediately", + initPasswordWarning: "This account does not have a login password yet. Please set one first", + initPasswordTitle: "Set Password", changeNow: "Change Now", + setNow: "Set Now", + notNow: "Not Now", successMessage: "Changed successfully", + initPasswordSuccessMessage: "Set successfully", oldPassword: "Old Password", oldPasswordRequired: "Please enter the old password", newPassword: "New Password", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts index 4ec2f109f..f4e23f5ef 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts @@ -79,8 +79,13 @@ export default { title: "修改密码", weakPasswordWarning: "为了您的账户安全,请立即修改密码", + initPasswordWarning: "当前账号还未设置登录密码,请先设置密码", + initPasswordTitle: "设置密码", changeNow: "立即修改", + setNow: "立即设置", + notNow: "暂不设置", successMessage: "修改成功", + initPasswordSuccessMessage: "设置成功", oldPassword: "旧密码", oldPasswordRequired: "请输入旧密码", newPassword: "新密码", diff --git a/packages/ui/certd-client/src/store/user/api.user.ts b/packages/ui/certd-client/src/store/user/api.user.ts index e358c760c..498c08abe 100644 --- a/packages/ui/certd-client/src/store/user/api.user.ts +++ b/packages/ui/certd-client/src/store/user/api.user.ts @@ -38,6 +38,7 @@ export interface UserInfoRes { avatar?: string; roleIds: number[]; isWeak?: boolean; + needInitPassword?: boolean; validTime?: number; status?: number; } diff --git a/packages/ui/certd-client/src/views/certd/mine/api.ts b/packages/ui/certd-client/src/views/certd/mine/api.ts index 5cabbd16c..04bcd69fd 100644 --- a/packages/ui/certd-client/src/views/certd/mine/api.ts +++ b/packages/ui/certd-client/src/views/certd/mine/api.ts @@ -15,6 +15,14 @@ export async function changePassword(form: any) { }); } +export async function initPassword(form: any) { + return await request({ + url: "/mine/initPassword", + method: "POST", + data: form, + }); +} + export async function UpdateProfile(form: any) { return await request({ url: "/mine/updateProfile", diff --git a/packages/ui/certd-client/src/views/certd/mine/change-password-button.vue b/packages/ui/certd-client/src/views/certd/mine/change-password-button.vue index a1b530de6..ed2f2ec3c 100644 --- a/packages/ui/certd-client/src/views/certd/mine/change-password-button.vue +++ b/packages/ui/certd-client/src/views/certd/mine/change-password-button.vue @@ -9,7 +9,7 @@ import { ref } from "vue"; import { useI18n } from "/src/locales"; const { t } = useI18n(); -import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud"; +import { compute, CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud"; import * as api from "/@/views/certd/mine/api"; import { notification } from "ant-design-vue"; import { useUserStore } from "/@/store/user"; @@ -20,6 +20,11 @@ defineProps<{ let passwordFormRef = ref(); +type OpenOptions = { + password?: string; + init?: boolean; +}; + const validatePass1 = async (rule: any, value: any) => { if (value === "") { throw new Error(t("authentication.enterPassword")); @@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = { width: "500px", }, async doSubmit({ form }) { - await api.changePassword(form); + if (form.init) { + await api.initPassword(form); + } else { + await api.changePassword(form); + } //重新加载用户信息 await userStore.loadUserInfo(); }, async afterSubmit() { - notification.success({ message: t("authentication.successMessage") }); + const formData = passwordFormRef.value?.getFormData?.(); + const message = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage"); + notification.success({ message }); }, }, columns: { + init: { + title: "init", + type: "text", + form: { + show: false, + }, + }, password: { title: t("authentication.oldPassword"), type: "password", form: { + show: compute(({ form }) => form.init !== true), rules: [{ required: true, message: t("authentication.oldPasswordRequired") }], }, }, @@ -97,12 +116,16 @@ const passwordFormOptions: CrudOptions = { }, }; -async function open(opts: { password: "" }) { +async function open(opts: OpenOptions = {}) { const formOptions = buildFormOptions(passwordFormOptions); formOptions.newInstance = true; //新实例打开 + if (opts.init) { + formOptions.wrapper.title = t("authentication.initPasswordTitle"); + } passwordFormRef.value = await openDialog(formOptions); passwordFormRef.value.setFormData({ - password: opts.password, + init: opts.init === true, + password: opts.password || "", }); console.log(passwordFormRef.value); } diff --git a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue index 8fb3e8145..11009f97e 100644 --- a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue +++ b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue @@ -48,7 +48,7 @@
- + {{ t("authentication.securitySettingTip") }} @@ -387,6 +387,7 @@ const checkPasskeySupport = () => { } }; const userStore = useUserStore(); +const changePasswordButtonRef = ref(); const userAvatar = computed(() => { if (isEmpty(userInfo.value.avatar)) { return ""; @@ -400,6 +401,21 @@ const userAvatar = computed(() => { onMounted(async () => { await getUserInfo(); + userStore.setUserInfo(userInfo.value); + if (userInfo.value.needInitPassword === true) { + Modal.confirm({ + title: t("authentication.initPasswordTitle"), + content: t("authentication.initPasswordWarning"), + okText: t("authentication.setNow"), + cancelText: t("authentication.notNow"), + closable: true, + onOk: () => { + changePasswordButtonRef.value.open({ + init: true, + }); + }, + }); + } await loadContactCapability(); await loadOauthBounds(); await loadOauthProviders(); diff --git a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts index 95f7c96da..fde30a218 100644 --- a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts +++ b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts @@ -1,16 +1,16 @@ -import { BaseController, Constants, SysSettingsService } from '@certd/lib-server'; -import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; -import { PasskeyService } from '../../../modules/login/service/passkey-service.js'; -import { RoleService } from '../../../modules/sys/authority/service/role-service.js'; -import { UserService } from '../../../modules/sys/authority/service/user-service.js'; -import { ApiTags } from '@midwayjs/swagger'; -import { CodeService } from '../../../modules/basic/service/code-service.js'; +import { BaseController, Constants, SysSettingsService } from "@certd/lib-server"; +import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core"; +import { PasskeyService } from "../../../modules/login/service/passkey-service.js"; +import { RoleService } from "../../../modules/sys/authority/service/role-service.js"; +import { UserService } from "../../../modules/sys/authority/service/user-service.js"; +import { ApiTags } from "@midwayjs/swagger"; +import { CodeService } from "../../../modules/basic/service/code-service.js"; /** */ @Provide() -@Controller('/api/mine') -@ApiTags(['mine']) +@Controller("/api/mine") +@ApiTags(["mine"]) export class MineController extends BaseController { @Inject() userService: UserService; @@ -27,28 +27,38 @@ export class MineController extends BaseController { @Inject() sysSettingsService: SysSettingsService; - @Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' }) + @Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" }) public async info() { const userId = this.getUserId(); const user = await this.userService.info(userId); - const isWeak = await this.userService.checkPassword('123456', user.password, user.passwordVersion); + const isWeak = await this.userService.checkPassword("123456", user.password, user.passwordVersion); if (isWeak) { //@ts-ignore user.isWeak = true; } + const needInitPassword = user.password === "changeme"; user.roleIds = await this.roleService.getRoleIdsByUserId(userId); delete user.password; + //@ts-ignore + user.needInitPassword = needInitPassword; return this.ok(user); } - @Post('/changePassword', { description: Constants.per.authOnly, summary: '修改密码' }) + @Post("/changePassword", { description: Constants.per.authOnly, summary: "修改密码" }) public async changePassword(@Body(ALL) body: any) { const userId = this.getUserId(); await this.userService.changePassword(userId, body); return this.ok({}); } - @Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' }) + @Post("/initPassword", { description: Constants.per.authOnly, summary: "初始化密码" }) + public async initPassword(@Body(ALL) body: any) { + const userId = this.getUserId(); + await this.userService.initPassword(userId, body); + return this.ok({}); + } + + @Post("/updateProfile", { description: Constants.per.authOnly, summary: "更新用户资料" }) public async updateProfile(@Body(ALL) body: any) { const userId = this.getUserId(); @@ -59,7 +69,7 @@ export class MineController extends BaseController { return this.ok({}); } - @Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' }) + @Post("/contact/capability", { description: Constants.per.authOnly, summary: "查询联系方式绑定能力" }) public async contactCapability() { const settings = await this.sysSettingsService.getPrivateSettings(); return this.ok({ @@ -67,27 +77,27 @@ export class MineController extends BaseController { }); } - @Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' }) - public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) { + @Post("/contact/verifyIdentity", { description: Constants.per.authOnly, summary: "验证本人操作" }) + public async verifyContactIdentity(@Body(ALL) body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }) { const userId = this.getUserId(); await this.userService.verifyIdentity(userId, body, this.codeService); const validationCode = this.codeService.setValidationValue({ - type: 'contactIdentity', + type: "contactIdentity", userId, identityType: body.identityType, }); return this.ok({ validationCode }); } - @Post('/contact/mobile', { description: Constants.per.authOnly, summary: '绑定或修改手机号' }) + @Post("/contact/mobile", { description: Constants.per.authOnly, summary: "绑定或修改手机号" }) public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) { const userId = this.getUserId(); this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService); await this.codeService.checkSmsCode({ mobile: body.mobile, - phoneCode: body.phoneCode || '86', + phoneCode: body.phoneCode || "86", smsCode: body.validateCode, - verificationType: 'bindMobile', + verificationType: "bindMobile", throwError: true, }); await this.userService.updateMobile(userId, { @@ -97,14 +107,14 @@ export class MineController extends BaseController { return this.ok({}); } - @Post('/contact/email', { description: Constants.per.authOnly, summary: '绑定或修改邮箱' }) + @Post("/contact/email", { description: Constants.per.authOnly, summary: "绑定或修改邮箱" }) public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) { const userId = this.getUserId(); this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService); this.codeService.checkEmailCode({ email: body.email, validateCode: body.validateCode, - verificationType: 'bindEmail', + verificationType: "bindEmail", throwError: true, }); await this.userService.updateEmail(userId, { diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-password-init.test.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-password-init.test.ts new file mode 100644 index 000000000..96fa3f77e --- /dev/null +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-password-init.test.ts @@ -0,0 +1,51 @@ +/// + +import assert from "node:assert/strict"; +import { UserService } from "./user-service.js"; + +describe("UserService.initPassword", () => { + function createService(user: any) { + const service = new UserService(); + service.info = async () => user; + let updatedParam: any; + service.update = async (param: any) => { + updatedParam = param; + }; + return { service, getUpdatedParam: () => updatedParam }; + } + + it("sets a new password when current password is changeme", async () => { + const { service, getUpdatedParam } = createService({ + id: 12, + password: "changeme", + passwordVersion: 2, + }); + + await service.initPassword(12, { + newPassword: "new-password", + confirmNewPassword: "new-password", + }); + + assert.deepEqual(getUpdatedParam(), { + id: 12, + password: "new-password", + }); + }); + + it("rejects initPassword after password has already been set", async () => { + const { service } = createService({ + id: 12, + password: "$2a$10$already-hashed", + passwordVersion: 2, + }); + + await assert.rejects( + () => + service.initPassword(12, { + newPassword: "new-password", + confirmNewPassword: "new-password", + }), + /当前账号已设置密码/ + ); + }); +}); 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 311c4948c..ac931a48f 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 @@ -1,22 +1,22 @@ -import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { InjectEntityModel } from '@midwayjs/typeorm'; -import { EntityManager, In, MoreThan, Not, Repository } from 'typeorm'; -import { UserEntity } from '../entity/user.js'; -import * as _ from 'lodash-es'; -import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server'; -import { RoleService } from './role-service.js'; -import { PermissionService } from './permission-service.js'; -import { UserRoleService } from './user-role-service.js'; -import { UserRoleEntity } from '../entity/user-role.js'; -import bcrypt from 'bcryptjs'; -import { RandomUtil } from '../../../../utils/random.js'; -import dayjs from 'dayjs'; -import { DbAdapter } from '../../../db/index.js'; -import { simpleNanoId, utils } from '@certd/basic'; -import { OauthBoundService } from '../../../login/service/oauth-bound-service.js'; +import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { InjectEntityModel } from "@midwayjs/typeorm"; +import { EntityManager, In, MoreThan, Not, Repository } from "typeorm"; +import { UserEntity } from "../entity/user.js"; +import * as _ from "lodash-es"; +import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from "@certd/lib-server"; +import { RoleService } from "./role-service.js"; +import { PermissionService } from "./permission-service.js"; +import { UserRoleService } from "./user-role-service.js"; +import { UserRoleEntity } from "../entity/user-role.js"; +import bcrypt from "bcryptjs"; +import { RandomUtil } from "../../../../utils/random.js"; +import dayjs from "dayjs"; +import { DbAdapter } from "../../../db/index.js"; +import { simpleNanoId, utils } from "@certd/basic"; +import { OauthBoundService } from "../../../login/service/oauth-bound-service.js"; -export type RegisterType = 'username' | 'mobile' | 'email'; -export type ForgotPasswordType = 'mobile' | 'email'; +export type RegisterType = "username" | "mobile" | "email"; +export type ForgotPasswordType = "mobile" | "email"; export const AdminRoleId = 1; @@ -83,14 +83,14 @@ export class UserService extends BaseService { }, }); if (!_.isEmpty(exists)) { - throw new CommonException('用户名已经存在'); + throw new CommonException("用户名已经存在"); } const plainPassword = param.password ?? RandomUtil.randomStr(6); param.passwordVersion = 2; param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆 if (param.avatar) { - param.avatar = await this.fileService.saveFile(0, param.avatar, 'public'); + param.avatar = await this.fileService.saveFile(0, param.avatar, "public"); } await super.add(param); @@ -107,13 +107,13 @@ export class UserService extends BaseService { */ async update(param) { if (param.id == null) { - throw new CommonException('id不能为空'); + throw new CommonException("id不能为空"); } const userInfo = await this.repository.findOne({ where: { id: param.id }, }); if (!userInfo) { - throw new CommonException('用户不存在'); + throw new CommonException("用户不存在"); } if (param.username) { @@ -125,7 +125,7 @@ export class UserService extends BaseService { { email: username, id: Not(id) }, ]); if (old != null) { - throw new CommonException('用户名已被占用'); + throw new CommonException("用户名已被占用"); } } if (!_.isEmpty(param.password)) { @@ -136,7 +136,7 @@ export class UserService extends BaseService { } if (param.avatar) { - param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, 'public'); + param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, "public"); } await super.update(param); await this.roleService.updateRoles(param.id, param.roles); @@ -168,7 +168,7 @@ export class UserService extends BaseService { async buildPlainPassword(rawPassword: string) { const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo); if (!setting.siteId) { - throw new CommonException('站点ID还未初始化'); + throw new CommonException("站点ID还未初始化"); } const prefixSiteId = setting.siteId.substring(1, 5); return rawPassword + prefixSiteId; @@ -192,48 +192,47 @@ export class UserService extends BaseService { const username = user.username; const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]); if (old != null) { - throw new CommonException('用户名已被注册'); + throw new CommonException("用户名已被注册"); } } if (user.mobile) { const mobile = user.mobile; - user.nickName = user.username || mobile.substring(0, 3) + '****' + mobile.substring(7); + user.nickName = user.username || mobile.substring(0, 3) + "****" + mobile.substring(7); const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]); if (old != null) { - throw new CommonException('手机号已被注册'); + throw new CommonException("手机号已被注册"); } } if (user.email) { const email = user.email; const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]); if (old != null) { - throw new CommonException('邮箱已被注册'); + throw new CommonException("邮箱已被注册"); } } if (!user.username) { - user.username = 'user_' + simpleNanoId(); + user.username = "user_" + simpleNanoId(); } let newUser: UserEntity = UserEntity.of({ username: user.username, password: user.password, - email: user.email || '', - mobile: user.mobile || '', + email: user.email || "", + mobile: user.mobile || "", nickName: user.nickName || user.username, - avatar: user.avatar || '', - phoneCode: user.phoneCode || '86', + avatar: user.avatar || "", + phoneCode: user.phoneCode || "86", status: 1, passwordVersion: 2, }); if (!newUser.password) { newUser.password = "changeme"; - }else{ + } else { newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion); } - await this.transaction(async txManager => { newUser = await txManager.save(newUser); @@ -248,28 +247,28 @@ export class UserService extends BaseService { delete newUser.password; - utils.mitter.emit('register', { userId: newUser.id }); + utils.mitter.emit("register", { userId: newUser.id }); return newUser; } async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) { if (!data.type) { - throw new CommonException('找回类型不能为空'); + throw new CommonException("找回类型不能为空"); } if (data.password !== data.confirmPassword) { - throw new CommonException('两次输入的密码不一致'); + throw new CommonException("两次输入的密码不一致"); } const where: any = { [data.type]: data.input, }; - if (data.type === 'mobile') { - where.phoneCode = data.phoneCode ?? '86'; + if (data.type === "mobile") { + where.phoneCode = data.phoneCode ?? "86"; } const user = await this.findOne({ [data.type]: data.input }); - console.log('user', user); + console.log("user", user); if (!user) { - throw new CommonException('用户不存在'); + throw new CommonException("用户不存在"); // return; } await this.resetPassword(user.id, data.password); @@ -284,7 +283,7 @@ export class UserService extends BaseService { const user = await this.info(userId); const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion); if (!passwordChecked) { - throw new CommonException('原密码错误'); + throw new CommonException("原密码错误"); } const param = { id: userId, @@ -294,9 +293,26 @@ export class UserService extends BaseService { await this.update(param); } + async initPassword(userId: any, form: any) { + const user = await this.info(userId); + if (user.password !== "changeme") { + throw new CommonException("当前账号已设置密码"); + } + if (!form.newPassword) { + throw new CommonException("新密码不能为空"); + } + if (form.newPassword !== form.confirmNewPassword) { + throw new CommonException("两次输入的密码不一致"); + } + await this.update({ + id: userId, + password: form.newPassword, + }); + } + async resetPassword(userId: any, newPasswd: string) { if (!userId) { - throw new CommonException('userId不能为空'); + throw new CommonException("userId不能为空"); } const param = { id: userId, @@ -307,15 +323,15 @@ export class UserService extends BaseService { //@ts-ignore async delete(ids: any) { - if (typeof ids === 'string') { - ids = ids.split(','); + if (typeof ids === "string") { + ids = ids.split(","); ids = ids.map(id => parseInt(id)); } if (ids.length === 0) { return; } if (ids.includes(1)) { - throw new CommonException('不能删除管理员'); + throw new CommonException("不能删除管理员"); } await super.delete(ids); await this.oauthBoundService.deleteWhere({ @@ -325,7 +341,7 @@ export class UserService extends BaseService { async isAdmin(userId: any) { if (!userId) { - throw new CommonException('userId不能为空'); + throw new CommonException("userId不能为空"); } const userRoles = await this.userRoleService.find({ where: { @@ -340,7 +356,7 @@ export class UserService extends BaseService { async updateStatus(id: number, status: number) { if (!id) { - throw new CommonException('userId不能为空'); + throw new CommonException("userId不能为空"); } await this.repository.update(id, { status, @@ -357,16 +373,16 @@ export class UserService extends BaseService { } async registerCountPerDay(param: { days: number } = { days: 7 }) { - const todayEnd = dayjs().endOf('day'); + const todayEnd = dayjs().endOf("day"); const result = await this.getRepository() - .createQueryBuilder('main') - .select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期 - .addSelect('COUNT(1) AS count') + .createQueryBuilder("main") + .select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期 + .addSelect("COUNT(1) AS count") .where({ // 0点 - createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()), + createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()), }) - .groupBy('date') + .groupBy("date") .getRawMany(); return result; @@ -386,7 +402,7 @@ export class UserService extends BaseService { status: 1, }, order: { - updateTime: 'DESC', + updateTime: "DESC", }, }); } @@ -398,61 +414,61 @@ export class UserService extends BaseService { }); } - async verifyIdentity(userId: number, body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }, codeService: any) { + async verifyIdentity(userId: number, body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }, codeService: any) { const user = await this.info(userId); - if (body.identityType === 'password') { + if (body.identityType === "password") { const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion); if (!passwordChecked) { - throw new CommonException('密码错误'); + throw new CommonException("密码错误"); } return; } - if (body.identityType === 'email') { + if (body.identityType === "email") { if (!user.email) { - throw new CommonException('当前账号未绑定邮箱'); + throw new CommonException("当前账号未绑定邮箱"); } codeService.checkEmailCode({ email: user.email, validateCode: body.identityValidateCode, - verificationType: 'contactIdentity', + verificationType: "contactIdentity", throwError: true, }); return; } - if (body.identityType === 'mobile') { + if (body.identityType === "mobile") { if (!user.mobile) { - throw new CommonException('当前账号未绑定手机号'); + throw new CommonException("当前账号未绑定手机号"); } await codeService.checkSmsCode({ mobile: user.mobile, - phoneCode: user.phoneCode || '86', + phoneCode: user.phoneCode || "86", smsCode: body.identityValidateCode, - verificationType: 'contactIdentity', + verificationType: "contactIdentity", throwError: true, }); return; } - throw new CommonException('不支持的验证方式'); + throw new CommonException("不支持的验证方式"); } checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) { const validationValue = codeService.getValidationValue(validationCode); - if (!validationValue || validationValue.type !== 'contactIdentity' || validationValue.userId !== userId) { - throw new CommonException('请先验证本人操作'); + if (!validationValue || validationValue.type !== "contactIdentity" || validationValue.userId !== userId) { + throw new CommonException("请先验证本人操作"); } } async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) { const mobile = body.mobile?.trim(); if (!mobile) { - throw new CommonException('手机号不能为空'); + throw new CommonException("手机号不能为空"); } const old = await this.findOne(buildUserContactConflictWhere(mobile, userId)); if (old != null) { - throw new CommonException('手机号已被占用'); + throw new CommonException("手机号已被占用"); } await this.repository.update(userId, { - phoneCode: body.phoneCode || '86', + phoneCode: body.phoneCode || "86", mobile, }); } @@ -460,11 +476,11 @@ export class UserService extends BaseService { async updateEmail(userId: number, body: { email: string }) { const email = body.email?.trim(); if (!email) { - throw new CommonException('邮箱不能为空'); + throw new CommonException("邮箱不能为空"); } const old = await this.findOne(buildUserContactConflictWhere(email, userId)); if (old != null) { - throw new CommonException('邮箱已被占用'); + throw new CommonException("邮箱已被占用"); } await this.repository.update(userId, { email, @@ -473,7 +489,7 @@ export class UserService extends BaseService { async getAllUserIds() { const users = await this.repository.find({ - select: ['id'], + select: ["id"], where: { status: 1, },