diff --git a/django_hoptcha/decorators.py b/django_hoptcha/decorators.py index 2c48928..091f5da 100755 --- a/django_hoptcha/decorators.py +++ b/django_hoptcha/decorators.py @@ -23,6 +23,8 @@ SOFTWARE. """ import time +import json + from urllib.parse import urlencode from functools import wraps @@ -75,10 +77,13 @@ def hoptcha_protected( if isinstance(key, str): key_func = BUILTIN_KEYS.get(key) + if not key_func: raise ValueError(f"Unknown key: {key}") + elif callable(key): key_func = key + else: raise TypeError("key must be a string or callable") @@ -101,7 +106,19 @@ def hoptcha_protected( attempts = cache.get(cache_key, 0) if attempts >= threshold: - token = request.POST.get("captcha_token") or request.GET.get("captcha_token") + token = ( + request.POST.get("captcha_token") or + request.GET.get("captcha_token") + ) + + # Try extracting from JSON body if not found yet + if not token and request.content_type == "application/json": + try: + body = json.loads(request.body) + token = body.get("captcha_token") + except (json.JSONDecodeError, TypeError): + pass # Malformed or empty JSON + if not token or not verify_token(token): return response(request) if response else JsonResponse({ "error": "CAPTCHA", diff --git a/django_hoptcha/static/django_hoptcha/hoptcha.js b/django_hoptcha/static/django_hoptcha/hoptcha.js index fd5f2b5..a0f50ee 100755 --- a/django_hoptcha/static/django_hoptcha/hoptcha.js +++ b/django_hoptcha/static/django_hoptcha/hoptcha.js @@ -1,11 +1,6 @@ -// ==================== HOPTCHA CLIENT ==================== - -(function(window, $) { - if (!$) { - console.error("Hoptcha requires jQuery. Please include it before this script."); - return; - } +// ==================== HOPTCHA CLIENT (Vanilla JS) ==================== +(function (window) { /** * Handle messages from the iframe. */ @@ -23,14 +18,17 @@ * Renders the Hoptcha iframe inside #hoptcha-container. * @param {string} url - The Hoptcha URL endpoint. */ - window.renderCaptchaStep = function(url) { - $('#hoptcha-container').html(` - - `); + window.renderCaptchaStep = function (url) { + const container = document.getElementById("hoptcha-container"); + if (container) { + container.innerHTML = ` + + `; + } }; /** @@ -39,13 +37,30 @@ * @param {object} payload - Payload to send, must include `captcha_token`. * @param {function} onSuccess - Success handler. * @param {function} [onError] - Error fallback. + * @param {function} [onCaptcha] - Optional custom render callback. */ - window.hoptchaPost = function(url, payload, onSuccess, onError, onCaptcha) { - $.post(url, payload, function(data) { + window.hoptchaPost = function (url, payload, onSuccess, onError, onCaptcha) { + fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload) + }) + .then(response => { + if (!response.ok) { + return response.json().then(data => { + throw { status: response.status, data }; + }); + } + return response.json(); + }) + .then(data => { if (onSuccess) onSuccess(data); - }).fail(xhr => { - const error = xhr.responseJSON ? xhr.responseJSON.error : 'Something went wrong.'; - const captcha_url = xhr.responseJSON?.url; + }) + .catch(err => { + const error = err.data?.error || 'Something went wrong.'; + const captcha_url = err.data?.url; if (!captcha_url) { if (onError) onError(error); @@ -54,7 +69,7 @@ } // Register callback - window._captchaSuccessCallback = function(token) { + window._captchaSuccessCallback = function (token) { payload.captcha_token = token; window.hoptchaPost(url, payload, onSuccess, onError, onCaptcha); }; @@ -63,5 +78,4 @@ render(captcha_url); }); }; - -})(window, window.jQuery); +})(window); diff --git a/example/demoapp/demoapp/templates/form.html b/example/demoapp/demoapp/templates/form.html index d1d0a71..93f48f1 100755 --- a/example/demoapp/demoapp/templates/form.html +++ b/example/demoapp/demoapp/templates/form.html @@ -4,7 +4,6 @@