import logging from datetime import timedelta from altcha import ChallengeOptions, create_challenge from django.conf import settings from django.http import HttpRequest, HttpResponseBase from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from pydantic import BaseModel from zerver.lib.exceptions import JsonableError from zerver.lib.response import json_success from zerver.lib.typed_endpoint import typed_endpoint_without_parameters class AltchaPayload(BaseModel): algorithm: str challenge: str number: int salt: str signature: str @typed_endpoint_without_parameters def get_challenge( request: HttpRequest, ) -> HttpResponseBase: if not settings.USING_CAPTCHA or not settings.ALTCHA_HMAC_KEY: # nocoverage raise JsonableError(_("Challenges are not enabled.")) now = timezone_now() expires = now + timedelta(minutes=1) try: challenge = create_challenge( ChallengeOptions( hmac_key=settings.ALTCHA_HMAC_KEY, max_number=500000, expires=expires, ) ) session_challenges = request.session.get("altcha_challenges", []) # We prune out expired challenges not for correctness (the # expiration is validated separately) but to prevent this from # growing without bound session_challenges = [(c, e) for (c, e) in session_challenges if e > now.timestamp()] request.session["altcha_challenges"] = [ *session_challenges, (challenge.challenge, expires.timestamp()), ] return json_success(request, data=challenge.__dict__) except Exception as e: # nocoverage logging.exception(e) raise JsonableError(_("Failed to generate challenge"))