Skip to content

Commit 8b574c1

Browse files
committed
fix: implement verification code attempt limit and error handling in user verification process
1 parent 22cec3d commit 8b574c1

4 files changed

Lines changed: 61 additions & 12 deletions

File tree

‎apps/locales/en_US/LC_MESSAGES/django.po‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,11 @@ msgstr ""
17241724
msgid "The verification code is incorrect or the verification code has expired"
17251725
msgstr ""
17261726

1727+
#: apps/users/serializers/user.py:79
1728+
#: apps/users/serializers/user.py:90
1729+
msgid "Too many verification code attempts, please request a new code"
1730+
msgstr ""
1731+
17271732
#: apps/common/constants/exception_code_constants.py:39
17281733
msgid "The username has been registered, please log in directly"
17291734
msgstr ""
@@ -9387,4 +9392,4 @@ msgid "Token Index"
93879392
msgstr "Token Index"
93889393

93899394
msgid "Authorize to Workspace"
9390-
msgstr ""
9395+
msgstr ""

‎apps/locales/zh_CN/LC_MESSAGES/django.po‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,11 @@ msgstr "该邮箱未注册,请先注册"
17311731
msgid "The verification code is incorrect or the verification code has expired"
17321732
msgstr "验证码不正确或已过期"
17331733

1734+
#: apps/users/serializers/user.py:79
1735+
#: apps/users/serializers/user.py:90
1736+
msgid "Too many verification code attempts, please request a new code"
1737+
msgstr "验证码错误次数过多,请重新获取验证码"
1738+
17341739
#: apps/common/constants/exception_code_constants.py:39
17351740
msgid "The username has been registered, please log in directly"
17361741
msgstr "用户名已注册,请直接登录"
@@ -9510,4 +9515,4 @@ msgid "Token Index"
95109515
msgstr "分词索引"
95119516

95129517
msgid "Authorize to Workspace"
9513-
msgstr "授权工作空间"
9518+
msgstr "授权工作空间"

‎apps/locales/zh_Hant/LC_MESSAGES/django.po‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,11 @@ msgstr "該郵箱未註冊,請先註冊"
17311731
msgid "The verification code is incorrect or the verification code has expired"
17321732
msgstr "驗證碼不正確或已過期"
17331733

1734+
#: apps/users/serializers/user.py:79
1735+
#: apps/users/serializers/user.py:90
1736+
msgid "Too many verification code attempts, please request a new code"
1737+
msgstr "驗證碼錯誤次數過多,請重新取得驗證碼"
1738+
17341739
#: apps/common/constants/exception_code_constants.py:39
17351740
msgid "The username has been registered, please log in directly"
17361741
msgstr "用戶名已註冊,請直接登錄"
@@ -9510,4 +9515,4 @@ msgid "Token Index"
95109515
msgstr "分詞索引"
95119516

95129517
msgid "Authorize to Workspace"
9513-
msgstr "授權工作空間"
9518+
msgstr "授權工作空間"

‎apps/users/serializers/user.py‎

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,45 @@
5656
version, get_key = Cache_Version.SYSTEM.value
5757

5858

59+
# 验证码校验相关的安全限制
60+
MAX_VERIFY_CODE_ATTEMPTS = 5
61+
VERIFY_CODE_EXPIRE_SECONDS = 60 * 30
62+
VERIFY_CODE_FAILED_ATTEMPTS = VERIFY_CODE_EXPIRE_SECONDS
63+
64+
65+
def check_verify_code_attempts(email: str, type_code: str, submitted_code: str) -> bool:
66+
"""
67+
校验验证码并限制错误尝试次数,防止验证码被暴力破解(CWE-307)。
68+
连续错误达到上限后,使当前验证码立即失效,必须重新发送验证码。
69+
校验通过时返回 True,否则抛出校验异常。
70+
"""
71+
code_cache_key = email + ":" + type_code
72+
failed_cache_key = code_cache_key + "_failed_attempts"
73+
cached_code = cache.get(get_key(code_cache_key), version=version)
74+
failed_attempts = int(cache.get(get_key(failed_cache_key), version=version) or 0)
75+
76+
# 已锁定:验证码已被置为失效,要求重新发送
77+
if failed_attempts >= MAX_VERIFY_CODE_ATTEMPTS:
78+
cache.delete(get_key(code_cache_key), version=version)
79+
raise AppApiException(500, _("Too many verification code attempts, please request a new code"))
80+
81+
if cached_code is None:
82+
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()
83+
84+
if cached_code != submitted_code:
85+
failed_attempts += 1
86+
cache.set(get_key(failed_cache_key), failed_attempts, timeout=VERIFY_CODE_FAILED_ATTEMPTS, version=version)
87+
if failed_attempts >= MAX_VERIFY_CODE_ATTEMPTS:
88+
# 达到最大尝试次数,立即使验证码失效并进入锁定状态
89+
cache.delete(get_key(code_cache_key), version=version)
90+
raise AppApiException(500, _("Too many verification code attempts, please request a new code"))
91+
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()
92+
93+
# 校验通过,清除错误尝试计数
94+
cache.delete(get_key(failed_cache_key), version=version)
95+
return True
96+
97+
5998
class UserProfileResponse(serializers.ModelSerializer):
6099
is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))
61100
permissions = serializers.ListField(required=True, label=_('permissions'))
@@ -1016,13 +1055,10 @@ class Meta:
10161055
def is_valid(self, *, raise_exception=False):
10171056
super().is_valid(raise_exception=True)
10181057
email = self.data.get("email")
1019-
cache_code = cache.get(get_key(email + ':reset_password'), version=version)
10201058
if self.data.get('password') != self.data.get('re_password'):
10211059
raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code,
10221060
ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message)
1023-
if cache_code != self.data.get('code'):
1024-
raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code,
1025-
ExceptionCodeConstants.CODE_ERROR.value.message)
1061+
check_verify_code_attempts(email, "reset_password", self.data.get('code'))
10261062
return True
10271063

10281064
def reset_password(self):
@@ -1161,7 +1197,8 @@ def send(self):
11611197
except Exception as e:
11621198
cache.delete(get_key(code_cache_key_lock))
11631199
return True
1164-
cache.set(get_key(code_cache_key), code, timeout=60 * 30, version=version)
1200+
cache.set(get_key(code_cache_key), code, timeout=VERIFY_CODE_EXPIRE_SECONDS, version=version)
1201+
cache.delete(get_key(code_cache_key + "_failed_attempts"), version=version)
11651202
return True
11661203

11671204

@@ -1187,10 +1224,7 @@ class CheckCodeSerializer(serializers.Serializer):
11871224

11881225
def is_valid(self, *, raise_exception=False):
11891226
super().is_valid()
1190-
value = cache.get(get_key(self.data.get("email") + ":" + self.data.get("type")), version=version)
1191-
if value is None or value != self.data.get("code"):
1192-
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()
1193-
return True
1227+
return check_verify_code_attempts(self.data.get("email"), self.data.get("type"), self.data.get("code"))
11941228

11951229

11961230
class SwitchLanguageSerializer(serializers.Serializer):

0 commit comments

Comments
 (0)