diff --git a/ruoyi-fastapi-backend/exceptions/exception.py b/ruoyi-fastapi-backend/exceptions/exception.py index ef2fda0..28c39a6 100644 --- a/ruoyi-fastapi-backend/exceptions/exception.py +++ b/ruoyi-fastapi-backend/exceptions/exception.py @@ -26,3 +26,13 @@ class PermissionException(Exception): def __init__(self, data: str = None, message: str = None): self.data = data self.message = message + + +class ModelValidatorException(Exception): + """ + 自定义模型校验异常ModelValidatorException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message diff --git a/ruoyi-fastapi-backend/exceptions/handle.py b/ruoyi-fastapi-backend/exceptions/handle.py index 61b7f90..040a11f 100644 --- a/ruoyi-fastapi-backend/exceptions/handle.py +++ b/ruoyi-fastapi-backend/exceptions/handle.py @@ -1,6 +1,6 @@ from fastapi import FastAPI, Request from fastapi.exceptions import HTTPException -from exceptions.exception import AuthException, PermissionException +from exceptions.exception import AuthException, PermissionException, ModelValidatorException from utils.response_util import ResponseUtil, JSONResponse, jsonable_encoder @@ -18,6 +18,11 @@ def handle_exception(app: FastAPI): async def permission_exception_handler(request: Request, exc: PermissionException): return ResponseUtil.forbidden(data=exc.data, msg=exc.message) + # 自定义模型检验异常 + @app.exception_handler(ModelValidatorException) + async def model_validator_exception_handler(request: Request, exc: ModelValidatorException): + return ResponseUtil.failure(data=exc.data, msg=exc.message) + # 处理其他http请求异常 @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 46ea0b8..a991556 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -217,12 +217,12 @@ async def change_system_user_profile_info(request: Request, user_info: UserInfoM @userController.put("/profile/updatePwd") @log_decorator(title='个人信息', business_type=2) -async def reset_system_user_password(request: Request, old_password: str = Query(alias='oldPassword'), new_password: str = Query(alias='newPassword'), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): +async def reset_system_user_password(request: Request, reset_password: ResetPasswordModel = Depends(ResetPasswordModel.as_query), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): try: reset_user = ResetUserModel( userId=current_user.user.user_id, - oldPassword=old_password, - password=PwdUtil.get_password_hash(new_password), + oldPassword=reset_password.old_password, + password=PwdUtil.get_password_hash(reset_password.new_password), updateBy=current_user.user.user_name, updateTime=datetime.now() ) diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py index 09a595f..13ad73a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -1,6 +1,8 @@ -from pydantic import BaseModel, ConfigDict +import re +from pydantic import BaseModel, ConfigDict, model_validator from pydantic.alias_generators import to_camel from typing import Optional +from exceptions.exception import ModelValidatorException class UserLogin(BaseModel): @@ -23,6 +25,14 @@ class UserRegister(BaseModel): code: Optional[str] = None uuid: Optional[str] = None + @model_validator(mode='after') + def check_password(self) -> 'UserRegister': + pattern = r'''^[^<>"'|\\]+$''' + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |") + class Token(BaseModel): access_token: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py index fbc3983..fa761ef 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -1,3 +1,4 @@ +import re from pydantic import BaseModel, ConfigDict, model_validator from pydantic.alias_generators import to_camel from typing import Union, Optional, List @@ -6,6 +7,7 @@ from module_admin.entity.vo.role_vo import RoleModel from module_admin.entity.vo.dept_vo import DeptModel from module_admin.entity.vo.post_vo import PostModel from module_admin.annotation.pydantic_annotation import as_query, as_form +from exceptions.exception import ModelValidatorException class TokenData(BaseModel): @@ -42,6 +44,14 @@ class UserModel(BaseModel): remark: Optional[str] = None admin: Optional[bool] = False + @model_validator(mode='after') + def check_password(self) -> 'UserModel': + pattern = r'''^[^<>"'|\\]+$''' + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |") + @model_validator(mode='after') def check_admin(self) -> 'UserModel': if self.user_id == 1: @@ -144,6 +154,25 @@ class EditUserModel(AddUserModel): role: Optional[List] = [] +@as_query +class ResetPasswordModel(BaseModel): + """ + 重置密码模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + old_password: Optional[str] = None + new_password: Optional[str] = None + + @model_validator(mode='after') + def check_new_password(self) -> 'ResetPasswordModel': + pattern = r'''^[^<>"'|\\]+$''' + if self.new_password is None or re.match(pattern, self.new_password): + return self + else: + raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |") + + class ResetUserModel(UserModel): """ 重置用户密码模型 diff --git a/ruoyi-fastapi-frontend/src/views/register.vue b/ruoyi-fastapi-frontend/src/views/register.vue index 43e97e4..810dea8 100644 --- a/ruoyi-fastapi-frontend/src/views/register.vue +++ b/ruoyi-fastapi-frontend/src/views/register.vue @@ -105,7 +105,8 @@ const registerRules = { ], password: [ { required: true, trigger: "blur", message: "请输入您的密码" }, - { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" } + { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, + { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" } ], confirmPassword: [ { required: true, trigger: "blur", message: "请再次输入您的密码" }, diff --git a/ruoyi-fastapi-frontend/src/views/system/user/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/index.vue index d6cfdd6..5cd9936 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/index.vue @@ -391,7 +391,7 @@ const data = reactive({ rules: { userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }], nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }], - password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }], + password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }], email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }], phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }] } @@ -494,6 +494,11 @@ function handleResetPwd(row) { closeOnClickModal: false, inputPattern: /^.{5,20}$/, inputErrorMessage: "用户密码长度必须介于 5 和 20 之间", + inputValidator: (value) => { + if (/<|>|"|'|\||\\/.test(value)) { + return "不能包含非法字符:< > \" ' \\\ |" + } + }, }).then(({ value }) => { resetUserPwd(row.userId, value).then(response => { proxy.$modal.msgSuccess("修改成功,新密码是:" + value); diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue index dec2d79..96daef3 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue @@ -36,7 +36,7 @@ const equalToPassword = (rule, value, callback) => { }; const rules = ref({ oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }], - newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }], + newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }], confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }] });