Skip to content

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

Merged
merged 2 commits into from
Apr 17, 2025
Merged

feat: Captcha #2913

merged 2 commits into from
Apr 17, 2025

Conversation

shaohuzhang1
Copy link
Contributor

feat: Captcha

Copy link

f2c-ci-robot bot commented Apr 17, 2025

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.

Copy link

f2c-ci-robot bot commented Apr 17, 2025

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@shaohuzhang1 shaohuzhang1 merged commit dcb77bb into v2 Apr 17, 2025
3 of 4 checks passed
@shaohuzhang1 shaohuzhang1 deleted the pr@v2@feat_captcha branch April 17, 2025 11:16
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()}
Copy link
Contributor Author

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:

  1. Imports: The ImageCaptcha class is used to generate captchas. This function was deprecated in favor of using Pillow (PIL fork) or similar libraries like Pilbox.
  2. String Concatenation: Direct string concatenation using + can lead to SQL injection vulnerabilities when dealing with untrusted input.

Potential Issues:

  1. Database Security: Passwords should not be stored in plain text. Implementing hashing like bcrypt would enhance security.
  2. Code Readability: Repeated logic could be optimized into a reusable function.
  3. 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())
Copy link
Contributor Author

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
Copy link
Contributor Author

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:

  1. Namespace Correction:

    from .login import CaptchaResponse

    Adding . before login should correctly reference the module if it's in the same package.

  2. Import Style:
    You can use from users.serializers.login import LoginResponse, LoginRequest directly without aliasing them again within the same module.

  3. Method Overload:
    The methods ApiLoginResponse.get_request() and ApiCaptchaAPI.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
  4. Consistency:
    Naming conventions might be slightly inconsistent (e.g., CaptchaResponse vs. CaptchaAPI). Ensure consistency across your project.

Minor Issues:

  1. Result Serializer Imports:
    Make sure all necessary imports for ResultSerializer are included at the beginning of the file.

  2. 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.

  3. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant