feat: Replace aCAPTCHA with official ALTCHA (altcha.org) Proof-of-Work web component widget

This commit is contained in:
Richard
2026-07-31 11:39:29 +02:00
parent 6ef74c7cf4
commit 83c782c469
417 changed files with 100543 additions and 317 deletions
+24 -38
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { X, ShieldCheck, UserCheck, AlertCircle, CheckCircle2 } from 'lucide-react';
import 'altcha';
export default function GuestSignupModal({ shift, onClose, onSubmit }) {
const [guestName, setGuestName] = useState('');
@@ -7,7 +8,7 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
return sessionStorage.getItem('guest_captcha_verified') === 'true';
});
const [captchaToken, setCaptchaToken] = useState(() => {
return sessionStorage.getItem('guest_captcha_verified') === 'true' ? 'acaptcha-session-verified' : '';
return sessionStorage.getItem('guest_captcha_verified') === 'true' ? 'altcha-session-verified' : '';
});
const [isCaptchaSolved, setIsCaptchaSolved] = useState(() => {
return sessionStorage.getItem('guest_captcha_verified') === 'true';
@@ -30,35 +31,21 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
const el = captchaRef.current;
if (!el) return;
const handleVerified = (e) => {
const isVerified = e?.detail?.verified || (el.isValid && el.isValid());
if (isVerified) {
const handleStateChange = (e) => {
const state = e?.detail?.state;
const payload = e?.detail?.payload;
if (state === 'verified') {
sessionStorage.setItem('guest_captcha_verified', 'true');
setSessionVerified(true);
setIsCaptchaSolved(true);
const token = 'acaptcha-verified-' + Math.random().toString(36).substring(2, 10);
setCaptchaToken(token);
setCaptchaToken(payload || ('altcha-verified-' + Math.random().toString(36).substring(2, 10)));
}
};
const handleReset = () => {
setIsCaptchaSolved(false);
setCaptchaToken('');
};
el.addEventListener('verified', handleVerified);
el.addEventListener('statuschange', handleVerified);
el.addEventListener('reset', handleReset);
// Initial check if already valid
if (el.isValid && el.isValid()) {
handleVerified({ detail: { verified: true } });
}
el.addEventListener('statechange', handleStateChange);
return () => {
el.removeEventListener('verified', handleVerified);
el.removeEventListener('statuschange', handleVerified);
el.removeEventListener('reset', handleReset);
el.removeEventListener('statechange', handleStateChange);
};
}, [sessionVerified]);
@@ -74,11 +61,11 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
const isVerified = sessionVerified || isCaptchaSolved;
if (!isVerified) {
setError('Bitte löse zuerst das Captcha.');
setError('Bitte schließe zuerst den ALTCHA Security Check ab.');
return;
}
const token = captchaToken || 'acaptcha-session-verified';
const token = captchaToken || 'altcha-session-verified';
// Save in localStorage for guest convenience
localStorage.setItem('guest_display_name', guestName.trim());
@@ -140,11 +127,11 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
</p>
</div>
{/* Security Check Section */}
{/* ALTCHA Security Check Section */}
{sessionVerified ? (
<div className="p-3.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs flex items-center justify-between font-mono">
<span className="flex items-center gap-2 font-semibold">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0" /> Security Check verifiziert
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0" /> ALTCHA Spamschutz verifiziert
</span>
<span className="text-[10px] text-emerald-400/80">[ In dieser Sitzung aktiv ]</span>
</div>
@@ -152,31 +139,30 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
<div className="p-4 rounded-xl bg-slate-900/90 border border-slate-800 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-slate-300 flex items-center gap-1.5 font-mono">
<ShieldCheck className="w-4 h-4 text-emerald-400" /> Security Check (aCAPTCHA)
<ShieldCheck className="w-4 h-4 text-emerald-400" /> Security Check (ALTCHA)
</span>
<a
href="https://acaptcha.vercel.app/"
href="https://altcha.org/"
target="_blank"
rel="noreferrer"
className="text-[10px] text-blue-400 hover:underline font-mono"
>
aCAPTCHA v1.1
ALTCHA.org
</a>
</div>
<div className="border border-slate-700 rounded-lg p-3 bg-slate-950/80 flex flex-col items-center justify-center min-h-[100px]">
<captcha-element
<div className="border border-slate-700 rounded-lg p-3 bg-slate-950/80 flex flex-col items-center justify-center min-h-[90px]">
<altcha-widget
ref={captchaRef}
status="null"
sound-enabled="true"
dark-mode="dark"
type="both"
difficulty="adaptive"
></captcha-element>
test="true"
auto="onload"
hidefooter="true"
style={{ width: '100%' }}
></altcha-widget>
{isCaptchaSolved && (
<div className="mt-2 text-xs font-medium text-emerald-400 flex items-center gap-1 font-mono">
<CheckCircle2 className="w-4 h-4" /> Captcha verifiziert!
<CheckCircle2 className="w-4 h-4" /> ALTCHA erfolgreich verifiziert!
</div>
)}
</div>