diff --git a/verification/curator-service/api/src/controllers/auth.ts b/verification/curator-service/api/src/controllers/auth.ts index 4a5f184ab..363e7134c 100644 --- a/verification/curator-service/api/src/controllers/auth.ts +++ b/verification/curator-service/api/src/controllers/auth.ts @@ -216,12 +216,13 @@ export class AuthController { req.body.token, ); - if (!captchaResult) + if (!captchaResult) { res.status(403).json({ message: "Unfortunately, you didn't pass the captcha. Please, try again later.", }); - + return; + } passport.authenticate( 'register', (error: Error, user: IUser, info: any) => { @@ -253,12 +254,13 @@ export class AuthController { req.body.token, ); - if (!captchaResult) + if (!captchaResult) { res.status(403).json({ message: "Unfortunately, you didn't pass the captcha. Please, try again later.", }); - + return; + } passport.authenticate( 'login', ( diff --git a/verification/curator-service/api/src/util/single-window-rate-limiters.ts b/verification/curator-service/api/src/util/single-window-rate-limiters.ts index f125a9744..6c78a463f 100644 --- a/verification/curator-service/api/src/util/single-window-rate-limiters.ts +++ b/verification/curator-service/api/src/util/single-window-rate-limiters.ts @@ -10,6 +10,7 @@ export const loginLimiter = rateLimit({ message: 'Too many failed login attempts, please try again later', }); }, + skipSuccessfulRequests: true, }); export const registerLimiter = rateLimit({ @@ -23,6 +24,7 @@ export const registerLimiter = rateLimit({ 'You sent too many requests. Please wait a while then try again', }); }, + skipSuccessfulRequests: true, }); export const resetPasswordLimiter = rateLimit({ @@ -36,6 +38,7 @@ export const resetPasswordLimiter = rateLimit({ 'You sent too many requests. Please wait a while then try again', }); }, + skipSuccessfulRequests: true, }); export const forgotPasswordLimiter = rateLimit({ @@ -49,6 +52,7 @@ export const forgotPasswordLimiter = rateLimit({ 'You sent too many requests. Please wait a while then try again', }); }, + skipSuccessfulRequests: true, }); export const resetPasswordWithTokenLimiter = rateLimit({ @@ -62,4 +66,5 @@ export const resetPasswordWithTokenLimiter = rateLimit({ 'You sent too many requests. Please wait a while then try again', }); }, + skipSuccessfulRequests: true, }); diff --git a/verification/curator-service/ui/package-lock.json b/verification/curator-service/ui/package-lock.json index 89668d7b3..c7adc02e7 100644 --- a/verification/curator-service/ui/package-lock.json +++ b/verification/curator-service/ui/package-lock.json @@ -33,7 +33,7 @@ "react-dom": "^16.13.1", "react-draggable": "^4.4.4", "react-google-button": "^0.7.2", - "react-google-recaptcha": "^2.1.0", + "react-google-recaptcha": "^3.0.0-alpha.1", "react-gtm-module": "^2.0.11", "react-helmet": "^6.1.0", "react-highlight-words": "^0.17.0", @@ -23223,12 +23223,12 @@ } }, "node_modules/react-google-recaptcha": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz", - "integrity": "sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==", + "version": "3.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.0.0-alpha.1.tgz", + "integrity": "sha512-eLMcS69D+RoJgTJ1Lymww/81iVprBUY7jHTxnvrMT5tqDlM+fTu/e8nDgBNdALRDXOcF5Io0YmPtz3QPPby2aQ==", "dependencies": { "prop-types": "^15.5.0", - "react-async-script": "^1.1.1" + "react-async-script": "^1.2.0" }, "peerDependencies": { "react": ">=16.4.1" @@ -47441,12 +47441,12 @@ } }, "react-google-recaptcha": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz", - "integrity": "sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==", + "version": "3.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.0.0-alpha.1.tgz", + "integrity": "sha512-eLMcS69D+RoJgTJ1Lymww/81iVprBUY7jHTxnvrMT5tqDlM+fTu/e8nDgBNdALRDXOcF5Io0YmPtz3QPPby2aQ==", "requires": { "prop-types": "^15.5.0", - "react-async-script": "^1.1.1" + "react-async-script": "^1.2.0" } }, "react-gtm-module": { diff --git a/verification/curator-service/ui/package.json b/verification/curator-service/ui/package.json index ce69cca09..91b4bdadd 100644 --- a/verification/curator-service/ui/package.json +++ b/verification/curator-service/ui/package.json @@ -28,7 +28,7 @@ "react-dom": "^16.13.1", "react-draggable": "^4.4.4", "react-google-button": "^0.7.2", - "react-google-recaptcha": "^2.1.0", + "react-google-recaptcha": "^3.0.0-alpha.1", "react-gtm-module": "^2.0.11", "react-helmet": "^6.1.0", "react-highlight-words": "^0.17.0", diff --git a/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx b/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx index 1a55bce05..ef84d3148 100644 --- a/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx +++ b/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Paper, Typography } from '@material-ui/core'; import { Theme, makeStyles } from '@material-ui/core/styles'; import { useLastLocation } from 'react-router-last-location'; @@ -33,6 +33,7 @@ import { import { MapLink } from '../../constants/types'; import { getReleaseNotesUrl } from '../util/helperFunctions'; import { getDiseaseName } from '../../redux/app/thunk'; +import ReCAPTCHA from 'react-google-recaptcha'; interface StylesProps { smallHeight: boolean; @@ -202,6 +203,11 @@ const MoreInformationLinks = ({ ); }; +const RECAPTCHA_SITE_KEY = window.Cypress + ? '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' + : ((process.env.RECAPTCHA_SITE_KEY || + process.env.REACT_APP_RECAPTCHA_SITE_KEY) as string); + const LandingPage = (): JSX.Element => { const dispatch = useAppDispatch(); @@ -210,6 +216,7 @@ const LandingPage = (): JSX.Element => { const lastLocation = useLastLocation(); const [registrationScreenOn, setRegistrationScreenOn] = useState(true); const [changePasswordScreenOn, setChangePasswordScreenOn] = useState(false); + const recaptchaRef = useRef(null); const isLoading = useAppSelector(selectIsLoading); const error = useAppSelector(selectError); @@ -277,15 +284,18 @@ const LandingPage = (): JSX.Element => { ) : ( !changePasswordScreenOn && ( ) )} + {changePasswordScreenOn && ( { + ); }; diff --git a/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx b/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx index ab597d153..9fd8484cb 100644 --- a/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx +++ b/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { useAppDispatch } from '../../hooks/redux'; @@ -69,22 +69,18 @@ interface FormValues { interface SignInFormProps { disabled?: boolean; setRegistrationScreenOn: (active: boolean) => void; + recaptchaRef?: React.RefObject; } -const RECAPTCHA_SITE_KEY = window.Cypress - ? '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' - : ((process.env.RECAPTCHA_SITE_KEY || - process.env.REACT_APP_RECAPTCHA_SITE_KEY) as string); - export default function SignInForm({ disabled, setRegistrationScreenOn, + recaptchaRef, }: SignInFormProps): JSX.Element { const dispatch = useAppDispatch(); const classes = useStyles(); const [passwordVisible, setPasswordVisible] = useState(false); - const recaptchaRef = useRef(null); const validationSchema = Yup.object().shape({ email: Yup.string() @@ -100,7 +96,7 @@ export default function SignInForm({ }, validationSchema, onSubmit: async (values) => { - if (!recaptchaRef.current) return; + if (!recaptchaRef || !recaptchaRef.current) return; // eslint-disable-next-line no-useless-catch try { @@ -238,7 +234,6 @@ export default function SignInForm({ > Sign in - Don't have an account?{' '} - diff --git a/verification/curator-service/ui/src/components/landing-page/SignUpForm.tsx b/verification/curator-service/ui/src/components/landing-page/SignUpForm.tsx index 304be3926..3a24a3d84 100644 --- a/verification/curator-service/ui/src/components/landing-page/SignUpForm.tsx +++ b/verification/curator-service/ui/src/components/landing-page/SignUpForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { useAppDispatch } from '../../hooks/redux'; @@ -90,21 +90,17 @@ interface FormValues { interface SignUpFormProps { disabled: boolean; setRegistrationScreenOn: (active: boolean) => void; + recaptchaRef?: React.RefObject; } -const RECAPTCHA_SITE_KEY = window.Cypress - ? '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' - : ((process.env.RECAPTCHA_SITE_KEY || - process.env.REACT_APP_RECAPTCHA_SITE_KEY) as string); - export default function SignUpForm({ disabled, setRegistrationScreenOn, + recaptchaRef, }: SignUpFormProps): React.ReactElement { const classes = useStyles(); const dispatch = useAppDispatch(); - const recaptchaRef = useRef(null); const [passwordVisible, setPasswordVisible] = useState(false); const [passwordStrength, setPasswordStrength] = useState(0); const [passwordConfirmationVisible, setPasswordConfirmationVisible] = @@ -161,7 +157,7 @@ export default function SignUpForm({ }, validationSchema, onSubmit: async (values) => { - if (!recaptchaRef.current) return; + if (!recaptchaRef || !recaptchaRef.current) return; const { email, password, isNewsletterChecked } = values; // eslint-disable-next-line no-useless-catch try { @@ -452,12 +448,6 @@ export default function SignUpForm({ > Sign up - - Already have an account?{' '}