feat(auth): integrate Captcha validation in PasswordAuthenticator

- Added CaptchaGuard dependency to PasswordAuthenticator.
- Implemented Captcha validation in the authenticate method to enhance security.
- Updated the login modal in home.html.twig to redirect to the admin panel instead of opening a modal.
- Enhanced the Altcha widget with localized strings for better user experience.
- Removed the login modal implementation from home.html.twig to streamline the login process.
- Updated manifest.json and AST cache files to reflect changes in the codebase.
This commit is contained in:
hamed
2026-07-10 11:05:59 +03:30
parent aded526718
commit 8b6d431f61
12 changed files with 733 additions and 736 deletions
+12 -1
View File
@@ -10,12 +10,23 @@ interface AltchaProps {
challengeUrl?: string;
}
// برچسب‌های فارسی widget (ترجمه‌ی رسمی locale fa).
const FA_STRINGS = JSON.stringify({
label: 'من ربات نیستم',
verifying: 'در حال بررسی...',
verified: 'تأیید شد',
waitAlert: 'در حال بررسی... لطفاً منتظر بمانید.',
error: 'احراز هویت ناموفق بود. کمی بعد دوباره تلاش کنید.',
expired: 'احراز هویت منقضی شد. دوباره تلاش کنید.',
});
// altcha-widget یک custom element است؛ به JSX معرفی می‌شود (React 19: namespace زیر React.JSX).
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'altcha-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
challengeurl?: string;
strings?: string;
};
}
}
@@ -39,5 +50,5 @@ export default function Altcha({ onVerified, challengeUrl = '/api/v1/altcha/chal
return () => el.removeEventListener('statechange', onStateChange);
}, [onVerified]);
return <altcha-widget ref={ref as React.Ref<HTMLElement>} challengeurl={challengeUrl} />;
return <altcha-widget ref={ref as React.Ref<HTMLElement>} challengeurl={challengeUrl} strings={FA_STRINGS} />;
}
+14 -4
View File
@@ -40,6 +40,8 @@ export default function LoginPage() {
const [cooldown, setCooldown] = useState(0);
// آخرین payload حل‌شده‌ی ALTCHA برای مرحله‌ی جاری (یک‌بارمصرف؛ بین مراحل ریست می‌شود).
const [altcha, setAltcha] = useState('');
// با تغییر key، widget رمزِ فرمِ ورود پس از هر تلاش ناموفق دوباره challenge تازه می‌گیرد.
const [pwCaptchaKey, setPwCaptchaKey] = useState(0);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => () => { if (timerRef.current) clearInterval(timerRef.current); }, []);
@@ -72,13 +74,20 @@ export default function LoginPage() {
const res = await fetch('/api/v1/user/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mobile_number: pwMobile, password: pwPass }),
body: JSON.stringify({ mobile_number: pwMobile, password: pwPass, altcha }),
});
const json = await res.json();
if (!res.ok) { toast.error(json?.errors?.[0]?.message ?? 'خطا در ورود'); return; }
if (!res.ok) {
toast.error(json?.errors?.[0]?.message ?? 'خطا در ورود');
setAltcha(''); setPwCaptchaKey((k) => k + 1); // challenge یک‌بارمصرف؛ تازه بگیر
return;
}
login(json.access_token, json.refresh_token);
toast.success('خوش آمدید');
} catch { toast.error('خطا در اتصال به سرور'); }
} catch {
toast.error('خطا در اتصال به سرور');
setAltcha(''); setPwCaptchaKey((k) => k + 1);
}
finally { setPwLoading(false); }
};
@@ -248,8 +257,9 @@ export default function LoginPage() {
</button>
</div>
</div>
<div style={{ marginTop: 12 }}><Altcha key={`pw-login-${pwCaptchaKey}`} onVerified={setAltcha} /></div>
<button type="submit" className="btn primary block" disabled={pwLoading}
style={{ marginTop: 8, height: 46, fontSize: 15 }}>
style={{ marginTop: 12, height: 46, fontSize: 15 }}>
{pwLoading ? 'در حال ورود...' : 'ورود به سیستم'}
</button>
<div style={{ textAlign: 'center', marginTop: 16 }}>