Skip to content

Commit

Permalink
Ensure turnstile is reset on form submission error
Browse files Browse the repository at this point in the history
Work towards #1076
  • Loading branch information
paustint committed Nov 17, 2024
1 parent d86dd58 commit af8a592
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 6 deletions.
15 changes: 11 additions & 4 deletions apps/landing/components/auth/Captcha.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Turnstile } from '@marsidev/react-turnstile';
import { useId } from 'react';
import { Maybe } from '@jetstream/types';
import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile';
import { forwardRef, useId, useImperativeHandle, useRef } from 'react';
import { ENVIRONMENT } from '../../utils/environment';

interface CaptchaProps {
Expand All @@ -17,9 +18,13 @@ interface CaptchaProps {
onFinished: () => void;
}

export function Captcha({ action, formError, onLoad, onChange, onFinished }: CaptchaProps) {
// eslint-disable-next-line react/display-name
export const Captcha = forwardRef<Maybe<TurnstileInstance>, CaptchaProps>(({ action, formError, onLoad, onChange, onFinished }, ref) => {
const turnstileRef = useRef<TurnstileInstance>(null);
const id = useId();

useImperativeHandle<unknown, Maybe<TurnstileInstance>>(ref, () => turnstileRef.current, [turnstileRef]);

// Skip rendering the captcha if we're running in Playwright or if the key is not set
// In real environments the server will still validate and prevent access if there isn't a valid token
if (!ENVIRONMENT.CAPTCHA_KEY || (window as any)?.playwright) {
Expand All @@ -31,13 +36,15 @@ export function Captcha({ action, formError, onLoad, onChange, onFinished }: Cap
<>
<Turnstile
id={id}
ref={turnstileRef}
siteKey={ENVIRONMENT.CAPTCHA_KEY}
options={{
action,
theme: 'light',
appearance: 'always',
size: 'flexible',
refreshExpired: 'auto',
feedbackEnabled: true,
}}
onWidgetLoad={onLoad}
onSuccess={(token) => {
Expand All @@ -52,4 +59,4 @@ export function Captcha({ action, formError, onLoad, onChange, onFinished }: Cap
)}
</>
);
}
});
10 changes: 9 additions & 1 deletion apps/landing/components/auth/LoginOrSignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable @next/next/no-img-element */
import { zodResolver } from '@hookform/resolvers/zod';
import { Providers } from '@jetstream/auth/types';
import { TurnstileInstance } from '@marsidev/react-turnstile';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Fragment, useState } from 'react';
import { Fragment, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { AUTH_PATHS, ENVIRONMENT } from '../../utils/environment';
Expand Down Expand Up @@ -53,6 +54,7 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro
const router = useRouter();
const [showPasswordActive, setShowPasswordActive] = useState(false);
const [finishedCaptcha, setFinishedCaptcha] = useState(false);
const captchaRef = useRef<TurnstileInstance>(null);

const {
register,
Expand Down Expand Up @@ -100,6 +102,11 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro

if (!response.ok || error) {
router.push(`${router.pathname}?${new URLSearchParams({ error: errorType || 'UNKNOWN_ERROR' })}`);
try {
captchaRef?.current?.reset();
} catch (ex) {
console.error('Error resetting captcha', ex);
}
return;
}

Expand Down Expand Up @@ -258,6 +265,7 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro
</div>

<Captcha
ref={captchaRef}
formError={errors?.captchaToken?.message}
action={action}
onChange={(token) => setValue('captchaToken', token)}
Expand Down
10 changes: 9 additions & 1 deletion apps/landing/components/auth/PasswordResetInit.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable @next/next/no-img-element */
import { zodResolver } from '@hookform/resolvers/zod';
import { TurnstileInstance } from '@marsidev/react-turnstile';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Fragment, useState } from 'react';
import { Fragment, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { AUTH_PATHS } from '../../utils/environment';
Expand All @@ -29,6 +30,7 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) {
const [isSubmitted, setIsSubmitted] = useState(false);
const [finishedCaptcha, setFinishedCaptcha] = useState(false);
const [error, setError] = useState<string>();
const captchaRef = useRef<TurnstileInstance>(null);

const {
register,
Expand Down Expand Up @@ -58,6 +60,11 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) {
});

if (!response.ok) {
try {
captchaRef?.current?.reset();
} catch (ex) {
console.error('Error resetting captcha', ex);
}
throw new Error('Unable to initialize the reset process');
}

Expand Down Expand Up @@ -127,6 +134,7 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) {
/>

<Captcha
ref={captchaRef}
formError={errors?.captchaToken?.message}
action="password-reset-init"
onChange={(token) => setValue('captchaToken', token)}
Expand Down

0 comments on commit af8a592

Please sign in to comment.