-
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
Conversation
Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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 comment
The 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:
- Imports: The
ImageCaptcha
class is used to generate captchas. This function was deprecated in favor of using Pillow (PIL fork) or similar libraries likePilbox
. - String Concatenation: Direct string concatenation using
+
can lead to SQL injection vulnerabilities when dealing with untrusted input.
Potential Issues:
- Database Security: Passwords should not be stored in plain text. Implementing hashing like bcrypt would enhance security.
- Code Readability: Repeated logic could be optimized into a reusable function.
- Captcha Expiration Logic: Ensure that caching mechanism correctly handles expiration times, even after successful verification.
Optimization Suggestions:
1. Update Imports
Replace the deprecated cryptography.hazmat.primitives.kdf.pbkdf2
from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import HttpResponse
2. Enhance Password Handling
Encrypt 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 Generation
Create 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 generate()
method accordingly:
@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 validate_image_code()
function based on current rules set during CAPTCHA generation process.
Remember to replace placeholder values where necessary (e.g., 'SOME_SECURE_SALT'
, etc.).
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 comment
The 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 CaptchaView
that handles GET requests to generate captchas. This addition maintains clarity in handling two distinct functionalities within user authentication (logging in and obtaining a captcha).
class CaptchaAPI(APIMixin): | ||
@staticmethod | ||
def get_response(): | ||
return ApiCaptchaResponse |
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:
from .login import CaptchaResponse
Adding
.
beforelogin
should correctly reference the module if it's in the same package. -
Import Style:
You can usefrom users.serializers.login import LoginResponse, LoginRequest
directly without aliasing them again within the same module. -
Method Overload:
The methodsApiLoginResponse.get_request()
andApiCaptchaAPI.get_response()
are staticmethod calls that could be combined into one method for cleaner code:class API(BaseAPIMixin): @classmethod async def get(cls): # Implement the logic to handle different responses
-
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:
import logging
from common.mixins.apimixin import Apimixin
from common.result import result_serializer_class_registry as RsClsReg
from users.model.user_models import LoginPayload, RegisterPayload, UserTokenInfo
from users.service import UserService
_LOGGER = logging.getLogger(__name__)
# Add namespace here if your CaptchaResponse is elsewhere
class CaptchaResponse:
pass
class ApiLoginResponse(RsClsReg.BaseApiResponse):
RESPONSE_CODE = 'api_login_success'
RESPONSE_TYPE = 'application/json'
@staticmethod
def get_request():
request = self.__get_request_from_query()
payload = dict(request.form)
payload['user_type'] = request.args.get('type', 'customer')
return LoginPayload(**payload)
@staticmethod
def get_response(user_token_info: UserTokenInfo) -> str:
return user_token_info.to_dict()
class CaptchaAPI(Apimixin):
_serializer_class = ApiCaptchaResponse
@classmethod
async def get(cls):
captcha_response_cls = cls._serializer_class
return await captcha_response_cls.create()
class BaseAPIMixin:
async def create(self):
response_obj = self._serializer_class(response_code=200)
return await response_obj.save_to_db()
@result_serializer_class_registry.register_result_serializer("apilogin.success", "response")
class ApiCaptchaResponse(ResultSerializer):
RESPONSE_TYPE="application/json"
RESPONSE_CONTENT=None
async def save_to_db(self):
self.RESPONSE_CONTENT=CaptchaResponse()
This revision addresses the minor inconsistencies and provides a cleaner implementation structure while maintaining functionality.
feat: Captcha