Merge pull request #19141 from overleaf/ii-add-email-account-settings

[web] Reset recaptcha every time

GitOrigin-RevId: 6db3571ad6dad3611c748a2d92dce47002a16a77
This commit is contained in:
ilkin-overleaf 2024-06-28 12:54:32 +03:00 committed by Copybot
parent 268291b52d
commit 687b56f18e
5 changed files with 62 additions and 14 deletions

View file

@ -151,7 +151,7 @@ function AddEmail() {
return (
<form>
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
<OLCol lg={8}>
<Cell>
{InputComponent}
@ -176,7 +176,7 @@ function AddEmail() {
return (
<form>
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
<OLCol lg={8}>
<Cell>
{InputComponent}

View file

@ -80,7 +80,7 @@ export function AddSecondaryEmailPrompt() {
<>
<Interstitial showLogo title={t('add_a_recovery_email_address')}>
<form className="add-secondary-email" onSubmit={handleSubmit}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
<p>{t('keep_your_account_safe_add_another_email')}</p>
<EmailInput

View file

@ -1,14 +1,18 @@
import { forwardRef } from 'react'
import ReCAPTCHA from 'react-google-recaptcha'
import getMeta from '@/utils/meta'
import { ExposedSettings } from '../../../../types/exposed-settings'
type Page = keyof ExposedSettings['recaptchaDisabled']
interface ReCaptcha2Props
extends Pick<React.ComponentProps<typeof ReCAPTCHA>, 'onChange'> {
page: keyof ExposedSettings['recaptchaDisabled']
recaptchaRef: React.LegacyRef<ReCAPTCHA>
}
export const ReCaptcha2 = forwardRef<
ReCAPTCHA,
{ page: Page; onChange?: (token: string | null) => void }
>(function ReCaptcha2({ page: site, onChange }, ref) {
export function ReCaptcha2({
page: site,
onChange,
recaptchaRef,
}: ReCaptcha2Props) {
const { recaptchaSiteKey, recaptchaDisabled } = getMeta('ol-ExposedSettings')
if (!recaptchaSiteKey) {
@ -19,11 +23,11 @@ export const ReCaptcha2 = forwardRef<
}
return (
<ReCAPTCHA
ref={ref}
ref={recaptchaRef}
size="invisible"
sitekey={recaptchaSiteKey}
onChange={onChange}
badge="inline"
/>
)
})
}

View file

@ -1,13 +1,20 @@
import { LegacyRef, createRef } from 'react'
import { useRef } from 'react'
import ReCAPTCHA from 'react-google-recaptcha'
export const useRecaptcha = () => {
const ref: LegacyRef<ReCAPTCHA> = createRef<ReCAPTCHA>()
const getReCaptchaToken = async (): Promise<string | null> => {
const ref = useRef<ReCAPTCHA | null>(null)
const getReCaptchaToken = async (): Promise<
ReturnType<ReCAPTCHA['executeAsync']>
> => {
if (!ref.current) {
return null
}
// Reset the reCAPTCHA before each submission.
// The reCAPTCHA token is meant to be used once per validation
ref.current.reset()
return await ref.current.executeAsync()
}
return { ref, getReCaptchaToken }
}

View file

@ -0,0 +1,37 @@
import { useRecaptcha } from '@/shared/hooks/use-recaptcha'
import * as ReactGoogleRecaptcha from 'react-google-recaptcha'
const ReCaptcha = () => {
const { ref: recaptchaRef, getReCaptchaToken } = useRecaptcha()
const handleClick = async () => {
await getReCaptchaToken()
}
return (
<>
<ReactGoogleRecaptcha.ReCAPTCHA
ref={recaptchaRef}
size="invisible"
sitekey="123456"
badge="inline"
/>
<button onClick={handleClick}>Click</button>
</>
)
}
describe('useRecaptcha', function () {
it('should reset the captcha', function () {
cy.spy(ReactGoogleRecaptcha.ReCAPTCHA.prototype, 'reset').as('resetSpy')
cy.spy(ReactGoogleRecaptcha.ReCAPTCHA.prototype, 'executeAsync').as(
'executeAsyncSpy'
)
cy.mount(<ReCaptcha />)
cy.findByRole('button', { name: /click/i }).click()
cy.get('@resetSpy').should('have.been.calledOnce')
cy.get('@executeAsyncSpy').should('have.been.calledOnce')
})
})