Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/locales/en_US/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,11 @@ msgstr ""
msgid "The verification code is incorrect or the verification code has expired"
msgstr ""

#: apps/users/serializers/user.py:79
#: apps/users/serializers/user.py:90
msgid "Too many verification code attempts, please request a new code"
msgstr ""

#: apps/common/constants/exception_code_constants.py:39
msgid "The username has been registered, please log in directly"
msgstr ""
Expand Down Expand Up @@ -9387,4 +9392,4 @@ msgid "Token Index"
msgstr "Token Index"

msgid "Authorize to Workspace"
msgstr ""
msgstr ""
7 changes: 6 additions & 1 deletion apps/locales/zh_CN/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,11 @@ msgstr "该邮箱未注册,请先注册"
msgid "The verification code is incorrect or the verification code has expired"
msgstr "验证码不正确或已过期"

#: apps/users/serializers/user.py:79
#: apps/users/serializers/user.py:90
msgid "Too many verification code attempts, please request a new code"
msgstr "验证码错误次数过多,请重新获取验证码"

#: apps/common/constants/exception_code_constants.py:39
msgid "The username has been registered, please log in directly"
msgstr "用户名已注册,请直接登录"
Expand Down Expand Up @@ -9510,4 +9515,4 @@ msgid "Token Index"
msgstr "分词索引"

msgid "Authorize to Workspace"
msgstr "授权工作空间"
msgstr "授权工作空间"
7 changes: 6 additions & 1 deletion apps/locales/zh_Hant/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,11 @@ msgstr "該郵箱未註冊,請先註冊"
msgid "The verification code is incorrect or the verification code has expired"
msgstr "驗證碼不正確或已過期"

#: apps/users/serializers/user.py:79
#: apps/users/serializers/user.py:90
msgid "Too many verification code attempts, please request a new code"
msgstr "驗證碼錯誤次數過多,請重新取得驗證碼"

#: apps/common/constants/exception_code_constants.py:39
msgid "The username has been registered, please log in directly"
msgstr "用戶名已註冊,請直接登錄"
Expand Down Expand Up @@ -9510,4 +9515,4 @@ msgid "Token Index"
msgstr "分詞索引"

msgid "Authorize to Workspace"
msgstr "授權工作空間"
msgstr "授權工作空間"
52 changes: 43 additions & 9 deletions apps/users/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,45 @@
version, get_key = Cache_Version.SYSTEM.value


# 验证码校验相关的安全限制
MAX_VERIFY_CODE_ATTEMPTS = 5
VERIFY_CODE_EXPIRE_SECONDS = 60 * 30
VERIFY_CODE_FAILED_ATTEMPTS = VERIFY_CODE_EXPIRE_SECONDS


def check_verify_code_attempts(email: str, type_code: str, submitted_code: str) -> bool:
"""
校验验证码并限制错误尝试次数,防止验证码被暴力破解(CWE-307)。
连续错误达到上限后,使当前验证码立即失效,必须重新发送验证码。
校验通过时返回 True,否则抛出校验异常。
"""
code_cache_key = email + ":" + type_code
failed_cache_key = code_cache_key + "_failed_attempts"
cached_code = cache.get(get_key(code_cache_key), version=version)
failed_attempts = int(cache.get(get_key(failed_cache_key), version=version) or 0)

# 已锁定:验证码已被置为失效,要求重新发送
if failed_attempts >= MAX_VERIFY_CODE_ATTEMPTS:
cache.delete(get_key(code_cache_key), version=version)
raise AppApiException(500, _("Too many verification code attempts, please request a new code"))

if cached_code is None:
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()

if cached_code != submitted_code:
failed_attempts += 1
cache.set(get_key(failed_cache_key), failed_attempts, timeout=VERIFY_CODE_FAILED_ATTEMPTS, version=version)
if failed_attempts >= MAX_VERIFY_CODE_ATTEMPTS:
# 达到最大尝试次数,立即使验证码失效并进入锁定状态
cache.delete(get_key(code_cache_key), version=version)
raise AppApiException(500, _("Too many verification code attempts, please request a new code"))
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()

# 校验通过,清除错误尝试计数
cache.delete(get_key(failed_cache_key), version=version)
return True


class UserProfileResponse(serializers.ModelSerializer):
is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))
permissions = serializers.ListField(required=True, label=_('permissions'))
Expand Down Expand Up @@ -1016,13 +1055,10 @@ class Meta:
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)
check_verify_code_attempts(email, "reset_password", self.data.get('code'))
return True

def reset_password(self):
Expand Down Expand Up @@ -1161,7 +1197,8 @@ def send(self):
except Exception as e:
cache.delete(get_key(code_cache_key_lock))
return True
cache.set(get_key(code_cache_key), code, timeout=60 * 30, version=version)
cache.set(get_key(code_cache_key), code, timeout=VERIFY_CODE_EXPIRE_SECONDS, version=version)
cache.delete(get_key(code_cache_key + "_failed_attempts"), version=version)
return True


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

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


class SwitchLanguageSerializer(serializers.Serializer):
Expand Down
Loading