Files
clinicpro/assets/admin/components/ui/Altcha.tsx
T
hamed 10b0743d9a Implement ALTCHA captcha service with challenge generation and solution verification
- Added AltchaService class for managing ALTCHA captcha challenges and solutions.
- Created CaptchaController to handle API requests for generating challenges.
- Introduced CaptchaGuard for validating captcha solutions on public endpoints.
- Developed unit tests for AltchaService to ensure challenge creation and solution verification functionality.
- Implemented integration tests for the Captcha API endpoint and captcha bypass behavior when disabled.
- Added documentation for the Captcha API in the corresponding markdown file.
2026-07-10 10:31:59 +03:30

44 lines
1.5 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import 'altcha';
// ALTCHA خودمیزبان: widget با گرفتن challenge از بک‌اند، proof-of-work را در
// پس‌زمینه‌ی مرورگر حل می‌کند و مقدار base64 حل‌شده را در event `verified` می‌دهد.
// این مقدار باید در بدنه‌ی درخواستِ endpoint عمومی با کلید `altcha` ارسال شود.
interface AltchaProps {
onVerified: (payload: string) => void;
challengeUrl?: string;
}
// 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;
};
}
}
}
export default function Altcha({ onVerified, challengeUrl = '/api/v1/altcha/challenge' }: AltchaProps) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const onStateChange = (e: Event) => {
const detail = (e as CustomEvent).detail as { state?: string; payload?: string };
if (detail?.state === 'verified' && detail.payload) {
onVerified(detail.payload);
}
};
el.addEventListener('statechange', onStateChange);
return () => el.removeEventListener('statechange', onStateChange);
}, [onVerified]);
return <altcha-widget ref={ref as React.Ref<HTMLElement>} challengeurl={challengeUrl} />;
}