Skip to content

Commit

Permalink
fix(mis): 在管理系统修改用户密码时配置的正则不生效 (#1253)
Browse files Browse the repository at this point in the history
**背景**

使用`typebox`时会沿用之前的特殊注释以优化为可进行后端校验
这使原本在`@pattern`下注释的正则规则被执行进代码
导致`common.yml`中即使更改了配置,在页面也可以正常显示,但是无法通过接口参数校验
` passwordPattern`是可配置的,原本在mis-web下的`@pattern`注释应理解为默认值而不是真正的校验值


此PR进一步检查了typebox相关属性定义是否与原有代码含义一致,优化了平台管理/租户管理下修改密码时对密码`@pattern`注释的描述,删除了`typebox`中关于`pattern`的属性定义
其他关于正则的typebox转换因为后端也没有做特殊的`@pattern`的注释没有上述问题

同时追加了管理系统下 平台管理和租户管理对用户密码修改的后端校验
                   ai 系统下 个人信息的密码修改后端校验


**修复前**

```
common.yml

passwordPattern:
  # 正则表达式。下面为默认值
  regex: ^(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:"<>?]).{8,}$

  # 出错时的消息。下面为默认值
  errorMessage: 必须包含数字和符号,长度大于等于8位
```

页面显示正常

![image](https://github.com/PKUHPC/SCOW/assets/43978285/a0eb580d-3d88-4ea9-8c98-e31c80ec6ab4)
但是接口请求参数校验失败,提示正则不满足包含数字

![image](https://github.com/PKUHPC/SCOW/assets/43978285/7ff9f87e-d75c-4810-a7ac-8475b8550eb8)


**修复后**
可正常在管理系统下的平台管理/租户管理/ai的个人信息下按配置的passwordPattern的正则修改用户密码
后端校验失败是提示错误信息

//TODO auth服务中校验?
  • Loading branch information
piccaSun committed May 18, 2024
1 parent 0957f1a commit d080a8b
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 17 deletions.
7 changes: 7 additions & 0 deletions .changeset/dull-panthers-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@scow/mis-web": patch
"@scow/config": patch
---

修复在 common.yml 中自定义更改用户密码正则后在管理系统不生效的问题,
增加平台管理和租户管理下修改用户密码的后端校验
5 changes: 5 additions & 0 deletions .changeset/nine-baboons-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/ai": patch
---

增加 ai 系统下个人信息中修改密码的后端校验
8 changes: 5 additions & 3 deletions apps/ai/src/app/(auth)/profile/ChangePasswordModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const ChangePasswordModal: React.FC<Props> = ({
},
onError(e) {
if (e.data?.code === "BAD_REQUEST") {
message.error(`修改密码失败:${e.message}`);
message.error(`修改密码失败: ${e.message}`);
}
else if (e.data?.code === "CONFLICT") {
message.error("原密码错误");
Expand Down Expand Up @@ -85,8 +85,10 @@ export const ChangePasswordModal: React.FC<Props> = ({
<Input.Password />
</Form.Item>
<Form.Item
rules={[{ required: true },
{ pattern: publicConfig.PASSWORD_PATTERN ? new RegExp(publicConfig.PASSWORD_PATTERN) : undefined }]}
rules={[
{ required: true },
{ pattern: publicConfig.PASSWORD_PATTERN ? new RegExp(publicConfig.PASSWORD_PATTERN) : undefined },
]}
label="新密码"
name="newPassword"
>
Expand Down
9 changes: 9 additions & 0 deletions apps/ai/src/server/trpc/route/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { join } from "path";
import { deleteUserToken, getUserToken, setUserTokenCookie } from "src/server/auth/cookie";
import { getUserInfo } from "src/server/auth/server";
import { validateToken } from "src/server/auth/token";
import { commonConfig } from "src/server/config/common";
import { config } from "src/server/config/env";
import { router } from "src/server/trpc/def";
import { authProcedure, baseProcedure } from "src/server/trpc/procedure/base";
Expand Down Expand Up @@ -157,6 +158,14 @@ export const auth = router({
});
}

const passwordPattern = commonConfig.passwordPattern?.regex && new RegExp(commonConfig.passwordPattern?.regex);
if (passwordPattern && !passwordPattern.test(newPassword)) {
throw new TRPCError({
message: "password is not valid",
code: "BAD_REQUEST",
});
}

const changeRes = await changePassword(config.AUTH_INTERNAL_URL, {
identityId,
newPassword,
Expand Down
9 changes: 8 additions & 1 deletion apps/mis-web/src/pageComponents/admin/AllUsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import { api } from "src/apis";
import { ChangePasswordModalLink } from "src/components/ChangePasswordModal";
import { FilterFormContainer, FilterFormTabs } from "src/components/FilterFormContainer";
import { PlatformRoleSelector } from "src/components/PlatformRoleSelector";
import { prefix, useI18nTranslateToString } from "src/i18n";
import { prefix, useI18n, useI18nTranslateToString } from "src/i18n";
import { PlatformRole, SortDirectionType, UsersSortFieldType } from "src/models/User";
import { ExportFileModaLButton } from "src/pageComponents/common/exportFileModal";
import { MAX_EXPORT_COUNT, urlToExport } from "src/pageComponents/file/apis";
import { GetAllUsersSchema } from "src/pages/api/admin/getAllUsers";
import { User } from "src/stores/UserStore";
import { getRuntimeI18nConfigText } from "src/utils/config";

import { ChangeTenantModalLink } from "./ChangeTenantModal";

Expand Down Expand Up @@ -234,6 +235,7 @@ const UserInfoTable: React.FC<UserInfoTableProps> = ({
}) => {

const t = useI18nTranslateToString();
const languageId = useI18n().currentLanguage.id;

const { message } = App.useApp();

Expand Down Expand Up @@ -319,6 +321,11 @@ const UserInfoTable: React.FC<UserInfoTableProps> = ({
} })
.httpError(404, () => { message.error(t(p("notExist"))); })
.httpError(501, () => { message.error(t(p("notAvailable"))); })
.httpError(400, (e) => {
if (e.code === "PASSWORD_NOT_VALID") {
message.error(getRuntimeI18nConfigText(languageId, "passwordPatternMessage"));
};
})
.then(() => { message.success(t(p("success"))); })
.catch(() => { message.error(t(p("fail"))); });
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const ChangePasswordModal: React.FC<Props> = ({
.httpError(400, (e) => {
if (e.code === "PASSWORD_NOT_VALID") {
message.error(getRuntimeI18nConfigText(languageId, "passwordPatternMessage"));
};
}
})
.then(() => {
form.resetFields();
Expand Down
9 changes: 8 additions & 1 deletion apps/mis-web/src/pageComponents/tenant/AdminUserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import { api } from "src/apis";
import { ChangePasswordModalLink } from "src/components/ChangePasswordModal";
import { FilterFormContainer, FilterFormTabs } from "src/components/FilterFormContainer";
import { TenantRoleSelector } from "src/components/TenantRoleSelector";
import { prefix, useI18nTranslateToString } from "src/i18n";
import { prefix, useI18n, useI18nTranslateToString } from "src/i18n";
import { FullUserInfo, TenantRole } from "src/models/User";
import { ExportFileModaLButton } from "src/pageComponents/common/exportFileModal";
import { MAX_EXPORT_COUNT, urlToExport } from "src/pageComponents/file/apis";
import { GetTenantUsersSchema } from "src/pages/api/admin/getTenantUsers";
import { User } from "src/stores/UserStore";
import { getRuntimeI18nConfigText } from "src/utils/config";

interface Props {
data: Static<typeof GetTenantUsersSchema["responses"]["200"]> | undefined;
Expand Down Expand Up @@ -55,6 +56,7 @@ export const AdminUserTable: React.FC<Props> = ({
}) => {

const t = useI18nTranslateToString();
const languageId = useI18n().currentLanguage.id;

const { message } = App.useApp();
const [form] = Form.useForm<FilterForm>();
Expand Down Expand Up @@ -255,6 +257,11 @@ export const AdminUserTable: React.FC<Props> = ({
} })
.httpError(404, () => { message.error(t(p("notExist"))); })
.httpError(501, () => { message.error(t(p("notAvailable"))); })
.httpError(400, (e) => {
if (e.code === "PASSWORD_NOT_VALID") {
message.error(getRuntimeI18nConfigText(languageId, "passwordPatternMessage"));
};
})
.then(() => { message.success(t(p("changeSuccess"))); })
.catch(() => { message.error(t(p("changeFail"))); });
}}
Expand Down
21 changes: 17 additions & 4 deletions apps/mis-web/src/pages/api/admin/changePassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ export const ChangePasswordAsPlatformAdminSchema = typeboxRouteSchema({
body: Type.Object({
identityId: Type.String(),
/**
* @pattern ^(?=.*\d)(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:"<>?]).{8,}$
* CommonConfig.PASSWORD_PATTERN
* OR
* when CommonConfig.PASSWORD_PATTERN={} =>
* use default value: ^(?=.*\d)(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:"<>?]).{8,}$
*/
newPassword: Type.String({
pattern: "^(?=.*\\d)(?=.*[a-zA-Z])(?=.*[`~!@#\\$%^&*()_+\\-[\\];',./{}|:\"<>?]).{8,}$",
}),
newPassword: Type.String(),
}),

responses: {
Expand All @@ -43,6 +44,11 @@ export const ChangePasswordAsPlatformAdminSchema = typeboxRouteSchema({
/** 用户未找到 */
404: Type.Null(),

/** 密码正则校验失败 */
400: Type.Object({
code: Type.Literal("PASSWORD_NOT_VALID"),
}),

/** 本功能在当前配置下不可用。 */
501: Type.Null(),
},
Expand All @@ -69,6 +75,13 @@ export default /* #__PURE__*/typeboxRoute(

const { identityId, newPassword } = req.body;

const passwordPattern = publicConfig.PASSWORD_PATTERN && new RegExp(publicConfig.PASSWORD_PATTERN);
if (passwordPattern && !passwordPattern.test(newPassword)) {
return { 400: {
code: "PASSWORD_NOT_VALID" as const,
} };
}

const logInfo = {
operatorUserId: info.identityId,
operatorIp: parseIp(req) ?? "",
Expand Down
21 changes: 17 additions & 4 deletions apps/mis-web/src/pages/api/tenant/changePassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ export const ChangePasswordAsTenantAdminSchema = typeboxRouteSchema({
body: Type.Object({
identityId: Type.String(),
/**
* @pattern ^(?=.*\d)(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:"<>?]).{8,}$
* CommonConfig.PASSWORD_PATTERN
* OR
* when CommonConfig.PASSWORD_PATTERN={} =>
* use default value: ^(?=.*\d)(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:"<>?]).{8,}$
*/
newPassword: Type.String({
pattern: "^(?=.*\\d)(?=.*[a-zA-Z])(?=.*[`~!@#\\$%^&*()_+\\-[\\];',./{}|:\"<>?]).{8,}$",
}),
newPassword: Type.String(),
}),

responses: {
Expand All @@ -46,6 +47,11 @@ export const ChangePasswordAsTenantAdminSchema = typeboxRouteSchema({
/** 用户未找到 */
404: Type.Null(),

/** 密码正则校验失败 */
400: Type.Object({
code: Type.Literal("PASSWORD_NOT_VALID"),
}),

/** 本功能在当前配置下不可用。 */
501: Type.Null(),
},
Expand Down Expand Up @@ -82,6 +88,13 @@ export default /* #__PURE__*/typeboxRoute(
return;
}

const passwordPattern = publicConfig.PASSWORD_PATTERN && new RegExp(publicConfig.PASSWORD_PATTERN);
if (passwordPattern && !passwordPattern.test(newPassword)) {
return { 400: {
code: "PASSWORD_NOT_VALID" as const,
} };
}

const logInfo = {
operatorUserId: info.identityId,
operatorIp: parseIp(req) ?? "",
Expand Down
5 changes: 3 additions & 2 deletions docs/docs/deploy/config/mis/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ db:

# 获取作业相关配置
fetchJobs:
# 从哪个时间点开始获取作业(日期格式ISO 8601)
# startDate: ""
# 从哪个时间点开始获取作业
# (日期格式ISO 8601,且需在末尾添加时区,推荐使用协调世界时(UTC))
# startDate: "2000-01-01T00:00:00Z"

# 限制一次获取的作业数量
# batchSize: 1000
Expand Down
2 changes: 1 addition & 1 deletion libs/config/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const CommonConfigSchema = Type.Object({
passwordPattern: Type.Object({
regex: Type.String({
description: "用户密码的正则规则",
default: "^(?=.*\d)(?=.*[a-zA-Z])(?=.*[`~!@#\$%^&*()_+\-[\];',./{}|:\"<>?]).{8,}$",
default: "^(?=.*\\d)(?=.*[a-zA-Z])(?=.*[`~!@#\\$%^&*()_+\\-[\\];',./{}|:\"<>?]).{8,}$",
}),
errorMessage: createI18nStringSchema({
description: "如果密码不符合规则显示什么",
Expand Down

0 comments on commit d080a8b

Please sign in to comment.