Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/captcha/Captcha.tsx
T

55 lines
2.1 KiB
TypeScript

import { useEffect, useRef } from "react";
interface Props {
/** Render the mCaptcha widget. When false (or unconfigured) nothing is rendered. */
enabled: boolean;
/** Public base URL of the self-hosted mCaptcha instance (web config / MCAPTCHA_INSTANCE_URL). */
instanceUrl?: string;
/** Public sitekey for this surface (e.g. MCAPTCHA_SITEKEY_LOGIN). */
sitekey?: string;
}
// mCaptcha's vanilla glue mounts the widget into an element with this id and
// injects a hidden <input name="mcaptcha__token"> into the enclosing <form>
// on solve. Keep in sync with @mcaptcha/vanilla-glue (exported `ID`).
const MOUNT_ID = "mcaptcha__widget-container";
/**
* mCaptcha (proof-of-work) widget — replaces the former no-op RecaptchaWrapper.
*
* Render this **inside** the <form> it should protect: the glue injects the
* hidden `mcaptcha__token` field next to this mount, so it is submitted with
* the form and verified server-side by `@toolctrl/captcha` (see AuthService /
* api.leads.capture / infra/mcaptcha/README.md + AD-7).
*
* Client load failures are non-fatal — the server verifier fails open
* (CAPTCHA_FAIL_OPEN, default true) when no token is present. The glue is
* imported dynamically so it never runs during SSR.
*/
export default function Captcha({ enabled, instanceUrl, sitekey }: Props) {
const active = enabled && Boolean(instanceUrl && sitekey);
const initialized = useRef(false);
useEffect(() => {
if (!active || initialized.current) return;
initialized.current = true;
let cancelled = false;
void (async () => {
try {
const { default: Widget } = await import("@mcaptcha/vanilla-glue");
if (cancelled) return;
// Widget self-mounts into #MOUNT_ID and injects the hidden mcaptcha__token field.
void new Widget({ siteKey: { instanceUrl: new URL(instanceUrl!), key: sitekey! } });
} catch (err) {
console.error("mCaptcha widget failed to initialise", err);
}
})();
return () => {
cancelled = true;
};
}, [active, instanceUrl, sitekey]);
if (!active) return null;
return <div id={MOUNT_ID} className="m-captcha" data-ui-id="ui.captcha.mount" />;
}