From d7a3b7fd3ff25a05065a48b651ad09c6b8c2e026 Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Fri, 11 Jul 2025 18:47:03 +0800 Subject: [PATCH] feat: enhance password reset functionality with email verification and validation --- apps/users/serializers/user.py | 103 ++++++++++++++++++++++++++++----- apps/users/views/user.py | 8 +-- 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 08fefaff1..ce68a5dc2 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -607,20 +607,97 @@ def update_user_role(instance, user): class RePasswordSerializer(serializers.Serializer): - password = serializers.CharField(required=True, label=_("Password"), - validators=[validators.RegexValidator(regex=re.compile( - "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)" - "(?![0-9_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9_!@#$%^&*`~.()-+=]{6,20}$") - , message=_( - "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."))]) + email = serializers.EmailField( + required=True, + label=_("Email"), + validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, + code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) - re_password = serializers.CharField(required=True, label=_("Confirm Password"), - validators=[validators.RegexValidator(regex=re.compile( - "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)" - "(?![0-9_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9_!@#$%^&*`~.()-+=]{6,20}$") - , message=_( - "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."))] - ) + code = serializers.CharField(required=True, label=_("Code")) + password = serializers.CharField( + required=True, + label=_("Password"), + max_length=20, + min_length=6, + validators=[ + validators.RegexValidator( + regex=PASSWORD_REGEX, + message=_( + "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." + ) + ) + ] + ) + re_password = serializers.CharField( + required=True, + label=_("Re Password"), + validators=[ + validators.RegexValidator( + regex=PASSWORD_REGEX, + message=_( + "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." + ) + ) + ] + ) + + class Meta: + model = User + fields = '__all__' + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + email = self.data.get("email") + cache_code = cache.get(get_key(email + ':reset_password'), version=version) + if self.data.get('password') != self.data.get('re_password'): + raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code, + ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message) + if cache_code != self.data.get('code'): + raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code, + ExceptionCodeConstants.CODE_ERROR.value.message) + return True + + def reset_password(self): + """ + 修改密码 + :return: 是否成功 + """ + if self.is_valid(): + email = self.data.get("email") + QuerySet(User).filter(email=email).update( + password=password_encrypt(self.data.get('password'))) + code_cache_key = email + ":reset_password" + cache.delete(get_key(code_cache_key), version=version) + return True + + +class ResetCurrentUserPassword(serializers.Serializer): + password = serializers.CharField( + required=True, + label=_("Password"), + max_length=20, + min_length=6, + validators=[ + validators.RegexValidator( + regex=PASSWORD_REGEX, + message=_( + "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." + ) + ) + ] + ) + re_password = serializers.CharField( + required=True, + label=_("Re Password"), + validators=[ + validators.RegexValidator( + regex=PASSWORD_REGEX, + message=_( + "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." + ) + ) + ] + ) class Meta: model = User diff --git a/apps/users/views/user.py b/apps/users/views/user.py index c75420443..316de0173 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -28,7 +28,7 @@ from users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi, Delet SendEmailAPI, CheckCodeAPI, SwitchUserLanguageAPI from users.models import User from users.serializers.user import UserProfileSerializer, UserManageSerializer, CheckCodeSerializer, \ - SendEmailSerializer, RePasswordSerializer, SwitchLanguageSerializer + SendEmailSerializer, RePasswordSerializer, SwitchLanguageSerializer, ResetCurrentUserPassword default_password = CONFIG.get('DEFAULT_PASSWORD', 'MaxKB@123..') @@ -260,6 +260,7 @@ class UserManage(APIView): @log(menu='User management', operate='Change password', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')), get_details=get_re_password_details) + @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) def put(self, request: Request, user_id): return result.success( UserManageSerializer.Operate(data={'id': user_id}).re_password(request.data, with_valid=True)) @@ -293,10 +294,9 @@ class RePasswordView(APIView): @log(menu='User management', operate='Change password', get_operation_object=lambda r, k: {'name': r.user.username}, get_details=get_re_password_details) - @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) def post(self, request: Request): serializer_obj = RePasswordSerializer(data=request.data) - return result.success(serializer_obj.reset_password(request.user.id)) + return result.success(serializer_obj.reset_password()) class SendEmail(APIView): @@ -367,7 +367,7 @@ class ResetCurrentUserPasswordView(APIView): @has_permissions(PermissionConstants.CHANGE_PASSWORD, RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.WORKSPACE_MANAGE) def post(self, request: Request): - serializer_obj = RePasswordSerializer(data=request.data) + serializer_obj = ResetCurrentUserPassword(data=request.data) if serializer_obj.reset_password(request.user.id): version, get_key = Cache_Version.TOKEN.value cache.delete(get_key(token=request.auth), version=version)