Rewrite to vanilla JS
This commit is contained in:
parent
a53fada7e1
commit
0759b43efc
@ -23,6 +23,8 @@ SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@ -75,10 +77,13 @@ def hoptcha_protected(
|
|||||||
|
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
key_func = BUILTIN_KEYS.get(key)
|
key_func = BUILTIN_KEYS.get(key)
|
||||||
|
|
||||||
if not key_func:
|
if not key_func:
|
||||||
raise ValueError(f"Unknown key: {key}")
|
raise ValueError(f"Unknown key: {key}")
|
||||||
|
|
||||||
elif callable(key):
|
elif callable(key):
|
||||||
key_func = key
|
key_func = key
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError("key must be a string or callable")
|
raise TypeError("key must be a string or callable")
|
||||||
|
|
||||||
@ -101,7 +106,19 @@ def hoptcha_protected(
|
|||||||
attempts = cache.get(cache_key, 0)
|
attempts = cache.get(cache_key, 0)
|
||||||
|
|
||||||
if attempts >= threshold:
|
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):
|
if not token or not verify_token(token):
|
||||||
return response(request) if response else JsonResponse({
|
return response(request) if response else JsonResponse({
|
||||||
"error": "CAPTCHA",
|
"error": "CAPTCHA",
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
// ==================== HOPTCHA CLIENT ====================
|
// ==================== HOPTCHA CLIENT (Vanilla JS) ====================
|
||||||
|
|
||||||
(function(window, $) {
|
|
||||||
if (!$) {
|
|
||||||
console.error("Hoptcha requires jQuery. Please include it before this script.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
(function (window) {
|
||||||
/**
|
/**
|
||||||
* Handle messages from the iframe.
|
* Handle messages from the iframe.
|
||||||
*/
|
*/
|
||||||
@ -23,14 +18,17 @@
|
|||||||
* Renders the Hoptcha iframe inside #hoptcha-container.
|
* Renders the Hoptcha iframe inside #hoptcha-container.
|
||||||
* @param {string} url - The Hoptcha URL endpoint.
|
* @param {string} url - The Hoptcha URL endpoint.
|
||||||
*/
|
*/
|
||||||
window.renderCaptchaStep = function(url) {
|
window.renderCaptchaStep = function (url) {
|
||||||
$('#hoptcha-container').html(`
|
const container = document.getElementById("hoptcha-container");
|
||||||
<iframe
|
if (container) {
|
||||||
id="captcha-iframe"
|
container.innerHTML = `
|
||||||
src=${url}
|
<iframe
|
||||||
style="width: 100%; height: 250px; border: none; border-radius: 12px;"
|
id="captcha-iframe"
|
||||||
></iframe>
|
src="${url}"
|
||||||
`);
|
style="width: 100%; height: 250px; border: none; border-radius: 12px;"
|
||||||
|
></iframe>
|
||||||
|
`;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,13 +37,30 @@
|
|||||||
* @param {object} payload - Payload to send, must include `captcha_token`.
|
* @param {object} payload - Payload to send, must include `captcha_token`.
|
||||||
* @param {function} onSuccess - Success handler.
|
* @param {function} onSuccess - Success handler.
|
||||||
* @param {function} [onError] - Error fallback.
|
* @param {function} [onError] - Error fallback.
|
||||||
|
* @param {function} [onCaptcha] - Optional custom render callback.
|
||||||
*/
|
*/
|
||||||
window.hoptchaPost = function(url, payload, onSuccess, onError, onCaptcha) {
|
window.hoptchaPost = function (url, payload, onSuccess, onError, onCaptcha) {
|
||||||
$.post(url, payload, function(data) {
|
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);
|
if (onSuccess) onSuccess(data);
|
||||||
}).fail(xhr => {
|
})
|
||||||
const error = xhr.responseJSON ? xhr.responseJSON.error : 'Something went wrong.';
|
.catch(err => {
|
||||||
const captcha_url = xhr.responseJSON?.url;
|
const error = err.data?.error || 'Something went wrong.';
|
||||||
|
const captcha_url = err.data?.url;
|
||||||
|
|
||||||
if (!captcha_url) {
|
if (!captcha_url) {
|
||||||
if (onError) onError(error);
|
if (onError) onError(error);
|
||||||
@ -54,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register callback
|
// Register callback
|
||||||
window._captchaSuccessCallback = function(token) {
|
window._captchaSuccessCallback = function (token) {
|
||||||
payload.captcha_token = token;
|
payload.captcha_token = token;
|
||||||
window.hoptchaPost(url, payload, onSuccess, onError, onCaptcha);
|
window.hoptchaPost(url, payload, onSuccess, onError, onCaptcha);
|
||||||
};
|
};
|
||||||
@ -63,5 +78,4 @@
|
|||||||
render(captcha_url);
|
render(captcha_url);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
})(window);
|
||||||
})(window, window.jQuery);
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Hoptcha Demo</title>
|
<title>Hoptcha Demo</title>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
||||||
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
|
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@ -73,26 +72,39 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function closeCaptcha() {
|
function closeCaptcha() {
|
||||||
$('#custom-captcha-modal').hide();
|
const modal = document.getElementById("custom-captcha-modal");
|
||||||
|
if (modal) modal.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#demo-form").submit(function(e) {
|
const demoForm = document.getElementById("demo-form");
|
||||||
e.preventDefault();
|
|
||||||
const formData = $(this).serializeArray();
|
|
||||||
let payload = {};
|
|
||||||
formData.forEach(item => payload[item.name] = item.value);
|
|
||||||
payload["captcha_token"] = "init";
|
|
||||||
|
|
||||||
hoptchaPost('/submit/', payload, function(data) {
|
if (demoForm) {
|
||||||
closeCaptcha();
|
demoForm.addEventListener("submit", function (e) {
|
||||||
alert(data.success);
|
e.preventDefault();
|
||||||
}, function(error) {
|
|
||||||
alert(error);
|
const formData = new FormData(demoForm);
|
||||||
}, function renderCustomCaptcha(url) {
|
const payload = {};
|
||||||
$('#custom-captcha-frame').attr('src', url);
|
|
||||||
$('#custom-captcha-modal').show();
|
for (const [key, value] of formData.entries()) {
|
||||||
|
payload[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload["captcha_token"] = "init";
|
||||||
|
|
||||||
|
hoptchaPost('/submit/', payload, function (data) {
|
||||||
|
closeCaptcha();
|
||||||
|
alert(data.success);
|
||||||
|
}, function (error) {
|
||||||
|
alert(error);
|
||||||
|
}, function renderCustomCaptcha(url) {
|
||||||
|
const frame = document.getElementById("custom-captcha-frame");
|
||||||
|
const modal = document.getElementById("custom-captcha-modal");
|
||||||
|
|
||||||
|
if (frame) frame.src = url;
|
||||||
|
if (modal) modal.style.display = "block";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -22,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
@ -30,10 +32,16 @@ from django_hoptcha.decorators import hoptcha_protected
|
|||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@hoptcha_protected(threshold=3, timeout=300)
|
@hoptcha_protected(threshold=3, timeout=300)
|
||||||
def protected_form_submit(request):
|
def protected_form_submit(request):
|
||||||
name = request.POST.get("name")
|
try:
|
||||||
if not name:
|
data = json.loads(request.body)
|
||||||
return JsonResponse({"error": "Name is required."}, status=400)
|
name = data.get('name', '')
|
||||||
return JsonResponse({"success": f"Hello, {name}!"})
|
|
||||||
|
if not name:
|
||||||
|
return JsonResponse({"error": "Name is required."}, status=400)
|
||||||
|
return JsonResponse({"success": f"Hello, {name}!"})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({"error": str(e)}, status=500)
|
||||||
|
|
||||||
def form_index(request):
|
def form_index(request):
|
||||||
return render(request, "templates/form.html")
|
return render(request, "templates/form.html")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user