-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: Captcha #2913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Captcha #2913
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,10 @@ | |
@date:2025/4/14 11:08 | ||
@desc: | ||
""" | ||
import base64 | ||
import datetime | ||
|
||
from captcha.image import ImageCaptcha | ||
from django.core import signing | ||
from django.core.cache import cache | ||
from django.db.models import QuerySet | ||
|
@@ -17,13 +19,14 @@ | |
from common.constants.authentication_type import AuthenticationType | ||
from common.constants.cache_version import Cache_Version | ||
from common.exception.app_exception import AppApiException | ||
from common.utils.common import password_encrypt | ||
from common.utils.common import password_encrypt, get_random_chars | ||
from users.models import User | ||
|
||
|
||
class LoginRequest(serializers.Serializer): | ||
username = serializers.CharField(required=True, max_length=64, help_text=_("Username"), label=_("Username")) | ||
password = serializers.CharField(required=True, max_length=128, label=_("Password")) | ||
captcha = serializers.CharField(required=True, max_length=64, label=_('captcha')) | ||
|
||
|
||
class LoginResponse(serializers.Serializer): | ||
|
@@ -40,6 +43,11 @@ def login(instance): | |
LoginRequest(data=instance).is_valid(raise_exception=True) | ||
username = instance.get('username') | ||
password = instance.get('password') | ||
captcha = instance.get('captcha') | ||
captcha_cache = cache.get(Cache_Version.CAPTCHA.get_key(captcha=captcha), | ||
version=Cache_Version.CAPTCHA.get_version()) | ||
if captcha_cache is None: | ||
raise AppApiException(1005, _("Captcha code error or expiration")) | ||
user = QuerySet(User).filter(username=username, password=password_encrypt(password)).first() | ||
if user is None: | ||
raise AppApiException(500, _('The username or password is incorrect')) | ||
|
@@ -52,3 +60,22 @@ def login(instance): | |
version, get_key = Cache_Version.TOKEN.value | ||
cache.set(get_key(token), user, timeout=datetime.timedelta(seconds=60 * 60 * 2).seconds, version=version) | ||
return {'token': token} | ||
|
||
|
||
class CaptchaResponse(serializers.Serializer): | ||
""" | ||
登录响应对象 | ||
""" | ||
captcha = serializers.CharField(required=True, label=_("captcha")) | ||
|
||
|
||
class CaptchaSerializer(serializers.Serializer): | ||
@staticmethod | ||
def generate(): | ||
chars = get_random_chars() | ||
image = ImageCaptcha() | ||
data = image.generate(chars) | ||
captcha = base64.b64encode(data.getbuffer()) | ||
cache.set(Cache_Version.CAPTCHA.get_key(captcha=chars), chars, | ||
timeout=60, version=Cache_Version.CAPTCHA.get_version()) | ||
return {'captcha': 'data:image/png;base64,' + captcha.decode()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here’s an analysis of the provided Python code snippet for the Django project, focusing on correctness and potential improvements: Irregularities:
Potential Issues:
Optimization Suggestions: 1. Update ImportsReplace the deprecated from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import HttpResponse 2. Enhance Password HandlingEncrypt passwords before storing them in the database: def password_encrypt(raw_password):
# Use secure way such as bcrypt.hashpw or hashlib.pbkdf2_hmac
salt=b'SOME_SECURE_SALT'
hash_object = hashlib.pbkdf2_hmac('sha256', raw_password.encode(), salt, 100000)
encoded_salt_salted_hash = base64.b64encode(salt + hash_object).decode("utf-8")
return encoded_salt_salted_hash 3. Simplify Captcha Response GenerationCreate a separate method inside captcha serializer for generating the response: class CaptchaSerializer(serializers.Serializer):
@staticmethod
def create_captcha_response(chars):
image = ImageCaptcha()
data = image.generate_ascii(characters=chars)
captcha_bytes = bytes.fromhex(data.hex())
return {
"image": captcha_bytes,
"text": chars
}
class Meta:
fields = ["text"] Also modify @staticmethod
def generate():
chars = get_random_chars()
result = CaptchaSerializer.create_captcha_response(chars).get("text").replace("\n", "")
cache.set(Cache_Version.CAPTCHA.get_key(captcha=result), result,
timeout=60, version=Cache_Version.CAPTCHA.get_version())
return {"captcha": str(result)} Modify the login view to use this updated API endpoint: import json
...
@api_view(['POST'])
def login(request):
if request.method == 'POST':
serializer = LoginRequest(data=request.data)
try:
if not serializer.is_valid(raise_exception=True):
...
username = instance.get('username')
password = instance.get('password')
captcha = instance.get('captcha')
if not validate_image_code(captcha):
raise AppApiException(
status=status.HTTP_401_UNAUTHORIZED,
detail='Your captcha has been expired.')
if user is None:
...
except Exception as e:
...
...
@login_required(login_url='/login/')
@csrf_exempt
def validate_image_code(image_code_input):
captcha_cache = cache.get(Cache_Version.CAPTCHA.get_key(captcha=image_code_input))
print(captcha_cache)
# Validate it here... This modification ensures proper handling of CSRF tokens, validation logic encapsulated within functions, and improved readability through modularized components. Additionally, make sure you implement checks for CAPTCHA validity within the Remember to replace placeholder values where necessary (e.g., |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,8 @@ | |
from rest_framework.views import APIView | ||
|
||
from common import result | ||
from users.api.login import LoginAPI | ||
from users.serializers.login import LoginSerializer | ||
from users.api.login import LoginAPI, CaptchaAPI | ||
from users.serializers.login import LoginSerializer, CaptchaSerializer | ||
|
||
|
||
class LoginView(APIView): | ||
|
@@ -25,3 +25,13 @@ class LoginView(APIView): | |
responses=LoginAPI.get_response()) | ||
def post(self, request: Request): | ||
return result.success(LoginSerializer().login(request.data)) | ||
|
||
|
||
class CaptchaView(APIView): | ||
@extend_schema(methods=['GET'], | ||
description=_("Get captcha"), | ||
operation_id=_("Get captcha"), | ||
tags=[_("User management")], | ||
responses=CaptchaAPI.get_response()) | ||
def get(self, request: Request): | ||
return result.success(CaptchaSerializer().generate()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are no significant irregularities, potential issues, or optimization suggestions in this code. The change is minor with adding a new |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code provided has a few minor improvements and optimizations:
Improvements and Optimizations:
Namespace Correction:
Adding
.
beforelogin
should correctly reference the module if it's in the same package.Import Style:
You can use
from users.serializers.login import LoginResponse, LoginRequest
directly without aliasing them again within the same module.Method Overload:
The methods
ApiLoginResponse.get_request()
andApiCaptchaAPI.get_response()
are staticmethod calls that could be combined into one method for cleaner code:Consistency:
Naming conventions might be slightly inconsistent (e.g.,
CaptchaResponse
vs.CaptchaAPI
). Ensure consistency across your project.Minor Issues:
Result Serializer Imports:
Make sure all necessary imports for ResultSerializer are included at the beginning of the file.
Static Method Usage:
If these classes are meant to interact with APIs, they should inherit from more concrete classes like FlaskView or FastAPIRouter rather than directly using APIMixin.
Error Handling:
Consider adding error handling within the login/response methods according to the HTTP status codes and response formats expected from an API.
Here is a revised version incorporating some of these suggestions:
This revision addresses the minor inconsistencies and provides a cleaner implementation structure while maintaining functionality.