Skip to content

Commit 0406fbc

Browse files
committed
feat: enhance captcha generation with access token validation and configurable max attempts
1 parent db0ed26 commit 0406fbc

File tree

2 files changed

+69
-20
lines changed

2 files changed

+69
-20
lines changed

apps/chat/views/chat.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ class CaptchaView(APIView):
191191
responses=CaptchaAPI.get_response())
192192
def get(self, request: Request):
193193
username = request.query_params.get('username', None)
194-
return result.success(CaptchaSerializer().generate(username, 'chat'))
194+
accessToken = request.query_params.get('accessToken', None)
195+
return result.success(CaptchaSerializer().chat_generate(username, 'chat', accessToken))
195196

196197

197198
class SpeechToText(APIView):

apps/users/serializers/login.py

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from django.utils.translation import gettext_lazy as _
1818
from rest_framework import serializers
1919

20+
from application.models import ApplicationAccessToken
2021
from common.constants.authentication_type import AuthenticationType
2122
from common.constants.cache_version import Cache_Version
2223
from common.database_model_manage.database_model_manage import DatabaseModelManage
@@ -33,8 +34,7 @@ class LoginRequest(serializers.Serializer):
3334
captcha = serializers.CharField(required=False, max_length=64, label=_('captcha'), allow_null=True,
3435
allow_blank=True)
3536
encryptedData = serializers.CharField(required=False, label=_('encryptedData'), allow_null=True,
36-
allow_blank=True)
37-
37+
allow_blank=True)
3838

3939

4040
system_version, system_get_key = Cache_Version.SYSTEM.value
@@ -61,6 +61,20 @@ def record_login_fail(username: str, expire: int = 600):
6161

6262
class LoginSerializer(serializers.Serializer):
6363

64+
@staticmethod
65+
def get_auth_setting():
66+
"""获取认证设置"""
67+
auth_setting_model = DatabaseModelManage.get_model('auth_setting')
68+
auth_setting = {}
69+
if auth_setting_model:
70+
setting_obj = auth_setting_model.objects.filter(param_key='auth_setting').first()
71+
if setting_obj:
72+
try:
73+
auth_setting = json.loads(setting_obj.param_value) or {}
74+
except Exception:
75+
auth_setting = {}
76+
return auth_setting
77+
6478
@staticmethod
6579
def login(instance):
6680
username = instance.get("username", "")
@@ -73,16 +87,7 @@ def login(instance):
7387
except Exception as e:
7488
record_login_fail(username)
7589
raise e
76-
auth_setting_model = DatabaseModelManage.get_model('auth_setting')
77-
# 默认配置
78-
auth_setting = {}
79-
if auth_setting_model:
80-
setting_obj = auth_setting_model.objects.filter(param_key='auth_setting').first()
81-
if setting_obj:
82-
try:
83-
auth_setting = json.loads(setting_obj.param_value) or {}
84-
except Exception:
85-
auth_setting = {}
90+
auth_setting = LoginSerializer.get_auth_setting()
8691

8792
max_attempts = auth_setting.get("max_attempts", 1)
8893
password = instance.get("password")
@@ -135,10 +140,53 @@ class CaptchaResponse(serializers.Serializer):
135140
class CaptchaSerializer(serializers.Serializer):
136141
@staticmethod
137142
def generate(username: str, type: str = 'system'):
138-
chars = get_random_chars()
139-
image = ImageCaptcha()
140-
data = image.generate(chars)
141-
captcha = base64.b64encode(data.getbuffer())
142-
cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(),
143-
timeout=300, version=Cache_Version.CAPTCHA.get_version())
144-
return {'captcha': 'data:image/png;base64,' + captcha.decode()}
143+
auth_setting = LoginSerializer.get_auth_setting()
144+
max_attempts = auth_setting.get("max_attempts", 1)
145+
need_captcha = False
146+
if max_attempts == -1:
147+
need_captcha = False
148+
elif max_attempts > 0:
149+
fail_count = cache.get(system_get_key(f'{type}_{username}'), version=system_version) or 0
150+
need_captcha = fail_count >= max_attempts
151+
152+
if need_captcha:
153+
chars = get_random_chars()
154+
image = ImageCaptcha()
155+
data = image.generate(chars)
156+
captcha = base64.b64encode(data.getbuffer())
157+
cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(),
158+
timeout=300, version=Cache_Version.CAPTCHA.get_version())
159+
return {'captcha': 'data:image/png;base64,' + captcha.decode()}
160+
return {'captcha': ''}
161+
162+
@staticmethod
163+
def chat_generate(username: str, type: str = 'chat', access_token: str = ''):
164+
auth_setting = {}
165+
application_access_token = ApplicationAccessToken.objects.filter(
166+
access_token=access_token
167+
).first()
168+
169+
if not application_access_token:
170+
raise AppApiException(1005, _('Invalid access token'))
171+
if application_access_token:
172+
auth_setting = application_access_token.authentication_value
173+
max_attempts = auth_setting.get("max_attempts", 1)
174+
need_captcha = False
175+
if max_attempts == -1:
176+
need_captcha = False
177+
elif max_attempts > 0:
178+
fail_count = cache.get(system_get_key(f'{type}_{username}'), version=system_version) or 0
179+
need_captcha = fail_count >= max_attempts
180+
181+
if need_captcha:
182+
chars = get_random_chars()
183+
image = ImageCaptcha()
184+
data = image.generate(chars)
185+
captcha = base64.b64encode(data.getbuffer())
186+
cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(),
187+
timeout=300, version=Cache_Version.CAPTCHA.get_version())
188+
return {'captcha': 'data:image/png;base64,' + captcha.decode()}
189+
return {'captcha': ''}
190+
191+
192+

0 commit comments

Comments
 (0)