Skip to content

Commit 0969f70

Browse files
committed
feat: add authentication settings API endpoints and enhance login logic with access token validation
1 parent ceb601d commit 0969f70

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-14
lines changed

apps/chat/views/chat.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ class CaptchaView(APIView):
190190
tags=[_("Chat")], # type: ignore
191191
responses=CaptchaAPI.get_response())
192192
def get(self, request: Request):
193-
return result.success(CaptchaSerializer().generate())
193+
username = request.query_params.get('username', None)
194+
return result.success(CaptchaSerializer().generate(username, 'chat'))
194195

195196

196197
class SpeechToText(APIView):

apps/users/serializers/login.py

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99
import base64
1010
import datetime
11+
import json
1112

1213
from captcha.image import ImageCaptcha
1314
from django.core import signing
@@ -18,6 +19,7 @@
1819

1920
from common.constants.authentication_type import AuthenticationType
2021
from common.constants.cache_version import Cache_Version
22+
from common.database_model_manage.database_model_manage import DatabaseModelManage
2123
from common.exception.app_exception import AppApiException
2224
from common.utils.common import password_encrypt, get_random_chars
2325
from maxkb.const import CONFIG
@@ -27,7 +29,11 @@
2729
class LoginRequest(serializers.Serializer):
2830
username = serializers.CharField(required=True, max_length=64, help_text=_("Username"), label=_("Username"))
2931
password = serializers.CharField(required=True, max_length=128, label=_("Password"))
30-
captcha = serializers.CharField(required=True, max_length=64, label=_('captcha'))
32+
captcha = serializers.CharField(required=False, max_length=64, label=_('captcha'), allow_null=True,
33+
allow_blank=True)
34+
35+
36+
system_version, system_get_key = Cache_Version.SYSTEM.value
3137

3238

3339
class LoginResponse(serializers.Serializer):
@@ -37,23 +43,70 @@ class LoginResponse(serializers.Serializer):
3743
token = serializers.CharField(required=True, label=_("token"))
3844

3945

46+
def record_login_fail(username: str, expire: int = 3600):
47+
"""记录登录失败次数"""
48+
if not username:
49+
return
50+
fail_key = system_get_key(f'system_{username}')
51+
fail_count = cache.get(fail_key, version=system_version)
52+
if fail_count is None:
53+
cache.set(fail_key, 1, timeout=expire, version=system_version)
54+
else:
55+
cache.incr(fail_key, 1, version=system_version)
56+
57+
4058
class LoginSerializer(serializers.Serializer):
4159

4260
@staticmethod
4361
def login(instance):
44-
LoginRequest(data=instance).is_valid(raise_exception=True)
45-
username = instance.get('username')
46-
password = instance.get('password')
47-
captcha = instance.get('captcha')
48-
captcha_cache = cache.get(Cache_Version.CAPTCHA.get_key(captcha=captcha.lower()),
49-
version=Cache_Version.CAPTCHA.get_version())
50-
if captcha_cache is None:
51-
raise AppApiException(1005, _("Captcha code error or expiration"))
62+
username = instance.get("username", "")
63+
try:
64+
LoginRequest(data=instance).is_valid(raise_exception=True)
65+
except Exception as e:
66+
record_login_fail(username)
67+
raise e
68+
auth_setting_model = DatabaseModelManage.get_model('auth_setting')
69+
# 默认配置
70+
auth_setting = {}
71+
if auth_setting_model:
72+
setting_obj = auth_setting_model.objects.filter(param_key='auth_setting').first()
73+
if setting_obj:
74+
try:
75+
auth_setting = json.loads(setting_obj.param_value) or {}
76+
except Exception:
77+
auth_setting = {}
78+
79+
max_attempts = auth_setting.get("max_attempts", 0)
80+
password = instance.get("password")
81+
captcha = instance.get("captcha", "")
82+
83+
# 判断是否需要验证码
84+
need_captcha = True
85+
if max_attempts == -1:
86+
need_captcha = False
87+
elif max_attempts > 0:
88+
fail_count = cache.get(system_get_key(f'system_{username}'), version=system_version) or 0
89+
need_captcha = fail_count >= max_attempts
90+
91+
if need_captcha:
92+
if not captcha:
93+
raise AppApiException(1005, _("Captcha is required"))
94+
95+
captcha_cache = cache.get(
96+
Cache_Version.CAPTCHA.get_key(captcha=f"system_{username}"),
97+
version=Cache_Version.CAPTCHA.get_version()
98+
)
99+
if captcha_cache is None or captcha.lower() != captcha_cache:
100+
raise AppApiException(1005, _("Captcha code error or expiration"))
101+
52102
user = QuerySet(User).filter(username=username, password=password_encrypt(password)).first()
53103
if user is None:
104+
record_login_fail(username)
54105
raise AppApiException(500, _('The username or password is incorrect'))
55106
if not user.is_active:
107+
record_login_fail(username)
56108
raise AppApiException(1005, _("The user has been disabled, please contact the administrator!"))
109+
cache.delete(system_get_key(f'system_{username}'), version=system_version)
57110
token = signing.dumps({'username': user.username,
58111
'id': str(user.id),
59112
'email': user.email,
@@ -73,11 +126,11 @@ class CaptchaResponse(serializers.Serializer):
73126

74127
class CaptchaSerializer(serializers.Serializer):
75128
@staticmethod
76-
def generate():
129+
def generate(username: str, type: str = 'system'):
77130
chars = get_random_chars()
78131
image = ImageCaptcha()
79132
data = image.generate(chars)
80133
captcha = base64.b64encode(data.getbuffer())
81-
cache.set(Cache_Version.CAPTCHA.get_key(captcha=chars.lower()), chars,
82-
timeout=60, version=Cache_Version.CAPTCHA.get_version())
134+
cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(),
135+
timeout=300, version=Cache_Version.CAPTCHA.get_version())
83136
return {'captcha': 'data:image/png;base64,' + captcha.decode()}

apps/users/views/login.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@ class CaptchaView(APIView):
7373
tags=[_("User Management")], # type: ignore
7474
responses=CaptchaAPI.get_response())
7575
def get(self, request: Request):
76-
return result.success(CaptchaSerializer().generate())
76+
username = request.query_params.get('username', None)
77+
return result.success(CaptchaSerializer().generate(username))

0 commit comments

Comments
 (0)