fix: reset Cloudflare Turnstile token on signup errors (#24927)
* fix: cloudflare turnstile token reset * fix: silently reset turnstile on invalid token error * refactor: remove unused forwardRef logic from Turnstile component - Remove forwardRef, useImperativeHandle, and useRef imports - Remove unused TurnstileInstance type export - Simplify to a plain function component - The ref-based reset was replaced by key-based remount in signup-view * refactor: remove redundant cfToken validation check The submit button is already disabled when cfToken is missing, making this defensive check unreachable during normal form flow. * revert prettier formatiing * chore: revert yarn.lock changes * refactor(auth): use shared constant for cloudflare token error message Replace hardcoded "Invalid cloudflare token" string with an exported constant to prevent silent breakage if the error message changes.
This commit is contained in:
@@ -35,6 +35,7 @@ import { pushGTMEvent } from "@calcom/lib/gtm";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { INVALID_CLOUDFLARE_TOKEN_ERROR } from "@calcom/lib/server/checkCfTurnstileToken";
|
||||
import { IS_EUROPE } from "@calcom/lib/timezoneConstants";
|
||||
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
@@ -191,6 +192,7 @@ export default function Signup({
|
||||
const [usernameTaken, setUsernameTaken] = useState(false);
|
||||
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
|
||||
const [displayEmailForm, setDisplayEmailForm] = useState(token);
|
||||
const [turnstileKey, setTurnstileKey] = useState(0);
|
||||
const searchParams = useCompatSearchParams();
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
@@ -207,7 +209,6 @@ export default function Signup({
|
||||
|
||||
useEffect(() => {
|
||||
if (redirectUrl) {
|
||||
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||
localStorage.setItem("onBoardingRedirect", redirectUrl);
|
||||
}
|
||||
}, [redirectUrl]);
|
||||
@@ -306,6 +307,13 @@ export default function Signup({
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setTurnstileKey((k) => k + 1);
|
||||
formMethods.setValue("cfToken", undefined);
|
||||
|
||||
if (err.message === INVALID_CLOUDFLARE_TOKEN_ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture("signup_form_submit_error", {
|
||||
has_token: !!token,
|
||||
is_org_invite: isOrgInviteByLink,
|
||||
@@ -512,10 +520,17 @@ export default function Signup({
|
||||
{/* Cloudflare Turnstile Captcha */}
|
||||
{CLOUDFLARE_SITE_ID ? (
|
||||
<TurnstileCaptcha
|
||||
key={turnstileKey}
|
||||
appearance="interaction-only"
|
||||
onVerify={(token) => {
|
||||
formMethods.setValue("cfToken", token);
|
||||
}}
|
||||
onExpire={() => {
|
||||
formMethods.setValue("cfToken", undefined);
|
||||
}}
|
||||
onError={() => {
|
||||
formMethods.setValue("cfToken", undefined);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -558,7 +573,6 @@ export default function Signup({
|
||||
org_slug: orgSlug,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||
localStorage.setItem("username", username);
|
||||
const sp = new URLSearchParams();
|
||||
// @NOTE: don't remove username query param as it's required right now for stripe payment page
|
||||
@@ -588,9 +602,7 @@ export default function Signup({
|
||||
!!formMethods.formState.errors.email ||
|
||||
!formMethods.getValues("email") ||
|
||||
!formMethods.getValues("password") ||
|
||||
(CLOUDFLARE_SITE_ID &&
|
||||
!process.env.NEXT_PUBLIC_IS_E2E &&
|
||||
!formMethods.getValues("cfToken")) ||
|
||||
(CLOUDFLARE_SITE_ID && !process.env.NEXT_PUBLIC_IS_E2E && !watch("cfToken")) ||
|
||||
isSubmitting ||
|
||||
usernameTaken
|
||||
}>
|
||||
@@ -640,7 +652,6 @@ export default function Signup({
|
||||
if (prepopulateFormValues?.username) {
|
||||
// If username is present we save it in query params to check for premium
|
||||
searchQueryParams.set("username", prepopulateFormValues.username);
|
||||
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||
localStorage.setItem("username", prepopulateFormValues.username);
|
||||
}
|
||||
if (token) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { HttpError } from "../http-error";
|
||||
|
||||
const TURNSTILE_SECRET_ID = process.env.CLOUDFLARE_TURNSTILE_SECRET;
|
||||
|
||||
export const INVALID_CLOUDFLARE_TOKEN_ERROR = "Invalid cloudflare token";
|
||||
|
||||
export async function checkCfTurnstileToken({ token, remoteIp }: { token?: string; remoteIp: string }) {
|
||||
// This means the instance doesn't have turnstile enabled - we skip the check and just return success.
|
||||
// OR the instance is running in CI so we skip these checks also
|
||||
@@ -28,7 +30,7 @@ export async function checkCfTurnstileToken({ token, remoteIp }: { token?: strin
|
||||
const data = await result.json();
|
||||
|
||||
if (!data["success"]) {
|
||||
throw new HttpError({ statusCode: 401, message: "Invalid cloudflare token" });
|
||||
throw new HttpError({ statusCode: 401, message: INVALID_CLOUDFLARE_TOKEN_ERROR });
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
Reference in New Issue
Block a user