add dist
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
turnstile?: {
|
||||
render: (el: HTMLElement, opts: Record<string, unknown>) => string;
|
||||
reset: (id?: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
export type TurnstileProps = {
|
||||
/** Called with the token once solved, or null on expiry/error. */
|
||||
onToken: (token: null | string) => void;
|
||||
/**
|
||||
* Public Turnstile site key. The client project reads it server-side from
|
||||
* SiteIntegrations (turnstileSiteKey) and passes it in — the widget is a pure
|
||||
* client component and can't read Payload itself.
|
||||
*/
|
||||
siteKey: string;
|
||||
theme?: 'auto' | 'dark' | 'light';
|
||||
};
|
||||
/**
|
||||
* Cloudflare Turnstile widget — reusable across any form. Renders the challenge
|
||||
* and reports the token via onToken. The token must then be verified
|
||||
* server-side (see verifyTurnstile) before a submission is trusted.
|
||||
*
|
||||
* siteKey is a prop rather than an env var so all Turnstile config lives in one
|
||||
* place (SiteIntegrations), consistent with the plugin's panel-managed model.
|
||||
* The script is injected directly (no next/script dependency) so the widget
|
||||
* stays framework-agnostic.
|
||||
*/
|
||||
export declare function Turnstile({ onToken, siteKey, theme }: TurnstileProps): import("react/jsx-runtime").JSX.Element | null;
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { useEffect, useRef } from 'react';
|
||||
const SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
|
||||
/** Loads the Turnstile script once, shared across all widget instances. */ function ensureScript() {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (document.querySelector(`script[src="${SCRIPT_SRC}"]`)) {
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = SCRIPT_SRC;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
/**
|
||||
* Cloudflare Turnstile widget — reusable across any form. Renders the challenge
|
||||
* and reports the token via onToken. The token must then be verified
|
||||
* server-side (see verifyTurnstile) before a submission is trusted.
|
||||
*
|
||||
* siteKey is a prop rather than an env var so all Turnstile config lives in one
|
||||
* place (SiteIntegrations), consistent with the plugin's panel-managed model.
|
||||
* The script is injected directly (no next/script dependency) so the widget
|
||||
* stays framework-agnostic.
|
||||
*/ export function Turnstile({ onToken, siteKey, theme = 'auto' }) {
|
||||
const ref = useRef(null);
|
||||
const widgetId = useRef(null);
|
||||
useEffect(()=>{
|
||||
if (!siteKey) {
|
||||
return;
|
||||
}
|
||||
ensureScript();
|
||||
function render() {
|
||||
if (!ref.current || !window.turnstile || widgetId.current) {
|
||||
return;
|
||||
}
|
||||
widgetId.current = window.turnstile.render(ref.current, {
|
||||
callback: (token)=>onToken(token),
|
||||
'error-callback': ()=>onToken(null),
|
||||
'expired-callback': ()=>onToken(null),
|
||||
sitekey: siteKey,
|
||||
theme
|
||||
});
|
||||
}
|
||||
render();
|
||||
// Script may load after mount — retry briefly until ready.
|
||||
const interval = setInterval(render, 300);
|
||||
return ()=>clearInterval(interval);
|
||||
}, [
|
||||
siteKey,
|
||||
theme,
|
||||
onToken
|
||||
]);
|
||||
if (!siteKey) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/ _jsx("div", {
|
||||
ref: ref
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=Turnstile.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/Turnstile.tsx"],"sourcesContent":["'use client'\nimport { useEffect, useRef } from 'react'\n\ndeclare global {\n interface Window {\n turnstile?: {\n render: (el: HTMLElement, opts: Record<string, unknown>) => string\n reset: (id?: string) => void\n }\n }\n}\n\nconst SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'\n\n/** Loads the Turnstile script once, shared across all widget instances. */\nfunction ensureScript(): void {\n if (typeof document === 'undefined') {return}\n if (document.querySelector(`script[src=\"${SCRIPT_SRC}\"]`)) {return}\n const script = document.createElement('script')\n script.src = SCRIPT_SRC\n script.async = true\n script.defer = true\n document.head.appendChild(script)\n}\n\nexport type TurnstileProps = {\n /** Called with the token once solved, or null on expiry/error. */\n onToken: (token: null | string) => void\n /**\n * Public Turnstile site key. The client project reads it server-side from\n * SiteIntegrations (turnstileSiteKey) and passes it in — the widget is a pure\n * client component and can't read Payload itself.\n */\n siteKey: string\n theme?: 'auto' | 'dark' | 'light'\n}\n\n/**\n * Cloudflare Turnstile widget — reusable across any form. Renders the challenge\n * and reports the token via onToken. The token must then be verified\n * server-side (see verifyTurnstile) before a submission is trusted.\n *\n * siteKey is a prop rather than an env var so all Turnstile config lives in one\n * place (SiteIntegrations), consistent with the plugin's panel-managed model.\n * The script is injected directly (no next/script dependency) so the widget\n * stays framework-agnostic.\n */\nexport function Turnstile({ onToken, siteKey, theme = 'auto' }: TurnstileProps) {\n const ref = useRef<HTMLDivElement>(null)\n const widgetId = useRef<null | string>(null)\n\n useEffect(() => {\n if (!siteKey) {return}\n\n ensureScript()\n\n function render() {\n if (!ref.current || !window.turnstile || widgetId.current) {return}\n widgetId.current = window.turnstile.render(ref.current, {\n callback: (token: string) => onToken(token),\n 'error-callback': () => onToken(null),\n 'expired-callback': () => onToken(null),\n sitekey: siteKey,\n theme,\n })\n }\n\n render()\n // Script may load after mount — retry briefly until ready.\n const interval = setInterval(render, 300)\n return () => clearInterval(interval)\n }, [siteKey, theme, onToken])\n\n if (!siteKey) {return null}\n\n return <div ref={ref} />\n}\n"],"names":["useEffect","useRef","SCRIPT_SRC","ensureScript","document","querySelector","script","createElement","src","async","defer","head","appendChild","Turnstile","onToken","siteKey","theme","ref","widgetId","render","current","window","turnstile","callback","token","sitekey","interval","setInterval","clearInterval","div"],"mappings":"AAAA;;AACA,SAASA,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAWzC,MAAMC,aAAa;AAEnB,yEAAyE,GACzE,SAASC;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC;IAAM;IAC5C,IAAIA,SAASC,aAAa,CAAC,CAAC,YAAY,EAAEH,WAAW,EAAE,CAAC,GAAG;QAAC;IAAM;IAClE,MAAMI,SAASF,SAASG,aAAa,CAAC;IACtCD,OAAOE,GAAG,GAAGN;IACbI,OAAOG,KAAK,GAAG;IACfH,OAAOI,KAAK,GAAG;IACfN,SAASO,IAAI,CAACC,WAAW,CAACN;AAC5B;AAcA;;;;;;;;;CASC,GACD,OAAO,SAASO,UAAU,EAAEC,OAAO,EAAEC,OAAO,EAAEC,QAAQ,MAAM,EAAkB;IAC5E,MAAMC,MAAMhB,OAAuB;IACnC,MAAMiB,WAAWjB,OAAsB;IAEvCD,UAAU;QACR,IAAI,CAACe,SAAS;YAAC;QAAM;QAErBZ;QAEA,SAASgB;YACP,IAAI,CAACF,IAAIG,OAAO,IAAI,CAACC,OAAOC,SAAS,IAAIJ,SAASE,OAAO,EAAE;gBAAC;YAAM;YAClEF,SAASE,OAAO,GAAGC,OAAOC,SAAS,CAACH,MAAM,CAACF,IAAIG,OAAO,EAAE;gBACtDG,UAAU,CAACC,QAAkBV,QAAQU;gBACrC,kBAAkB,IAAMV,QAAQ;gBAChC,oBAAoB,IAAMA,QAAQ;gBAClCW,SAASV;gBACTC;YACF;QACF;QAEAG;QACA,2DAA2D;QAC3D,MAAMO,WAAWC,YAAYR,QAAQ;QACrC,OAAO,IAAMS,cAAcF;IAC7B,GAAG;QAACX;QAASC;QAAOF;KAAQ;IAE5B,IAAI,CAACC,SAAS;QAAC,OAAO;IAAI;IAE1B,qBAAO,KAACc;QAAIZ,KAAKA;;AACnB"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export { Turnstile } from './Turnstile.js';
|
||||
export type { TurnstileProps } from './Turnstile.js';
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
'use client';
|
||||
// Client-only exports — the Turnstile widget. Kept separate from index.ts so
|
||||
// the server-only verify never leaks into a browser bundle.
|
||||
export { Turnstile } from './Turnstile.js';
|
||||
|
||||
//# sourceMappingURL=client.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/client.ts"],"sourcesContent":["'use client'\n// Client-only exports — the Turnstile widget. Kept separate from index.ts so\n// the server-only verify never leaks into a browser bundle.\nexport { Turnstile } from './Turnstile.js'\nexport type { TurnstileProps } from './Turnstile.js'\n"],"names":["Turnstile"],"mappings":"AAAA;AACA,6EAA6E;AAC7E,4DAA4D;AAC5D,SAASA,SAAS,QAAQ,iBAAgB"}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export { verifyTurnstile } from './verify.js';
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
// Server-only exports. verify.ts imports 'server-only', so this must never be
|
||||
// imported from a client component — use ./client for the widget instead.
|
||||
export { verifyTurnstile } from './verify.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/index.ts"],"sourcesContent":["// Server-only exports. verify.ts imports 'server-only', so this must never be\n// imported from a client component — use ./client for the widget instead.\nexport { verifyTurnstile } from './verify.js'\n"],"names":["verifyTurnstile"],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,SAASA,eAAe,QAAQ,cAAa"}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import 'server-only';
|
||||
import type { BasePayload } from 'payload';
|
||||
type VerifyTurnstileArgs = {
|
||||
/** Optional client IP for stricter verification. */
|
||||
ip?: string;
|
||||
/** Payload instance — used to read the secret from SiteIntegrations. */
|
||||
payload: BasePayload;
|
||||
/** Token produced by the client-side widget. */
|
||||
token: string;
|
||||
};
|
||||
/**
|
||||
* Verifies a Turnstile token with Cloudflare, server-side only.
|
||||
*
|
||||
* The secret comes from the SiteIntegrations global (editor-managed, per the
|
||||
* plugin's "secrets in the panel" model), read via the Local API which bypasses
|
||||
* access control. `server-only` guarantees this never reaches the browser
|
||||
* bundle, keeping the secret off the client.
|
||||
*
|
||||
* Returns false on any failure (missing secret/token, network error, rejected
|
||||
* challenge) — callers treat false as "do not trust this submission".
|
||||
*/
|
||||
export declare function verifyTurnstile({ ip, payload, token, }: VerifyTurnstileArgs): Promise<boolean>;
|
||||
export {};
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
import 'server-only';
|
||||
import { getSiteIntegrations } from '../payload/index.js';
|
||||
/**
|
||||
* Verifies a Turnstile token with Cloudflare, server-side only.
|
||||
*
|
||||
* The secret comes from the SiteIntegrations global (editor-managed, per the
|
||||
* plugin's "secrets in the panel" model), read via the Local API which bypasses
|
||||
* access control. `server-only` guarantees this never reaches the browser
|
||||
* bundle, keeping the secret off the client.
|
||||
*
|
||||
* Returns false on any failure (missing secret/token, network error, rejected
|
||||
* challenge) — callers treat false as "do not trust this submission".
|
||||
*/ export async function verifyTurnstile({ ip, payload, token }) {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
const integrations = await getSiteIntegrations(payload);
|
||||
const secret = integrations.turnstileSecretKey;
|
||||
if (!secret) {
|
||||
return false;
|
||||
}
|
||||
const body = new URLSearchParams({
|
||||
response: token,
|
||||
secret
|
||||
});
|
||||
if (ip) {
|
||||
body.append('remoteip', ip);
|
||||
}
|
||||
try {
|
||||
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
body,
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.success;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=verify.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/verify.ts"],"sourcesContent":["import 'server-only'\n\nimport type { BasePayload } from 'payload'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\ntype SiteverifyResponse = {\n challenge_ts?: string\n 'error-codes'?: string[]\n hostname?: string\n success: boolean\n}\n\ntype IntegrationsWithTurnstile = {\n turnstileSecretKey?: null | string\n}\n\ntype VerifyTurnstileArgs = {\n /** Optional client IP for stricter verification. */\n ip?: string\n /** Payload instance — used to read the secret from SiteIntegrations. */\n payload: BasePayload\n /** Token produced by the client-side widget. */\n token: string\n}\n\n/**\n * Verifies a Turnstile token with Cloudflare, server-side only.\n *\n * The secret comes from the SiteIntegrations global (editor-managed, per the\n * plugin's \"secrets in the panel\" model), read via the Local API which bypasses\n * access control. `server-only` guarantees this never reaches the browser\n * bundle, keeping the secret off the client.\n *\n * Returns false on any failure (missing secret/token, network error, rejected\n * challenge) — callers treat false as \"do not trust this submission\".\n */\nexport async function verifyTurnstile({\n ip,\n payload,\n token,\n}: VerifyTurnstileArgs): Promise<boolean> {\n if (!token) {return false}\n\n const integrations = await getSiteIntegrations<IntegrationsWithTurnstile>(payload)\n const secret = integrations.turnstileSecretKey\n if (!secret) {return false}\n\n const body = new URLSearchParams({ response: token, secret })\n if (ip) {body.append('remoteip', ip)}\n\n try {\n const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {\n body,\n method: 'POST',\n })\n const data = (await res.json()) as SiteverifyResponse\n return data.success\n } catch {\n return false\n }\n}\n"],"names":["getSiteIntegrations","verifyTurnstile","ip","payload","token","integrations","secret","turnstileSecretKey","body","URLSearchParams","response","append","res","fetch","method","data","json","success"],"mappings":"AAAA,OAAO,cAAa;AAIpB,SAASA,mBAAmB,QAAQ,sBAAqB;AAsBzD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,gBAAgB,EACpCC,EAAE,EACFC,OAAO,EACPC,KAAK,EACe;IACpB,IAAI,CAACA,OAAO;QAAC,OAAO;IAAK;IAEzB,MAAMC,eAAe,MAAML,oBAA+CG;IAC1E,MAAMG,SAASD,aAAaE,kBAAkB;IAC9C,IAAI,CAACD,QAAQ;QAAC,OAAO;IAAK;IAE1B,MAAME,OAAO,IAAIC,gBAAgB;QAAEC,UAAUN;QAAOE;IAAO;IAC3D,IAAIJ,IAAI;QAACM,KAAKG,MAAM,CAAC,YAAYT;IAAG;IAEpC,IAAI;QACF,MAAMU,MAAM,MAAMC,MAAM,6DAA6D;YACnFL;YACAM,QAAQ;QACV;QACA,MAAMC,OAAQ,MAAMH,IAAII,IAAI;QAC5B,OAAOD,KAAKE,OAAO;IACrB,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
||||
Reference in New Issue
Block a user