From 1cf8808563656352a19b73af0004084e023dbcaf Mon Sep 17 00:00:00 2001 From: Oskar Kocjan Date: Wed, 7 Sep 2022 10:15:30 +0200 Subject: [PATCH 1/3] blocking actions by recatpcha fix, limiters skip attempt on success, recaptcha hidden, ui recaptcha fix --- .../components/landing-page/LandingPage.tsx | 18 ++++++++++- .../components/landing-page/SignInForm.tsx | 32 +++++++++++-------- .../components/landing-page/SignUpForm.tsx | 31 ++++++++++-------- .../curator-service/ui/src/types/index.d.ts | 1 + 4 files changed, 54 insertions(+), 28 deletions(-) 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..3fb073bfa 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..227ef4a11 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,20 @@ export default function SignInForm({ > Sign in - + + This site is protected by reCAPTCHA and the Google{' '} + + Privacy Policy + {' '} + and{' '} + + Terms of Service + {' '} + apply. + 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..4e25096e6 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,11 +448,20 @@ export default function SignUpForm({ > Sign up - + + This site is protected by reCAPTCHA and the Google{' '} + + Privacy Policy + {' '} + and{' '} + + Terms of Service + {' '} + apply. + Already have an account?{' '} diff --git a/verification/curator-service/ui/src/types/index.d.ts b/verification/curator-service/ui/src/types/index.d.ts index 17e4e948e..90c24a1bb 100644 --- a/verification/curator-service/ui/src/types/index.d.ts +++ b/verification/curator-service/ui/src/types/index.d.ts @@ -3,5 +3,6 @@ export {}; declare global { interface Window { dataLayer: any; + Cypress: any; } } From fb2e1be8553925bd76914fc867eb1705cb8efdef Mon Sep 17 00:00:00 2001 From: Oskar Kocjan Date: Wed, 7 Sep 2022 10:17:12 +0200 Subject: [PATCH 2/3] backend fix --- .../curator-service/api/src/controllers/auth.ts | 10 ++++++---- .../api/src/util/single-window-rate-limiters.ts | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) 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, }); From 129b4eff15e1475e1cd871dc88eefd55f5308ceb Mon Sep 17 00:00:00 2001 From: Oskar Kocjan Date: Thu, 8 Sep 2022 10:17:48 +0200 Subject: [PATCH 3/3] new version of react-google-recaptcha --- .../curator-service/ui/package-lock.json | 18 +++++++++--------- verification/curator-service/ui/package.json | 2 +- .../components/landing-page/LandingPage.tsx | 1 - .../src/components/landing-page/SignInForm.tsx | 14 -------------- .../src/components/landing-page/SignUpForm.tsx | 15 --------------- 5 files changed, 10 insertions(+), 40 deletions(-) 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 3fb073bfa..ef84d3148 100644 --- a/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx +++ b/verification/curator-service/ui/src/components/landing-page/LandingPage.tsx @@ -333,7 +333,6 @@ const LandingPage = (): JSX.Element => { sitekey={RECAPTCHA_SITE_KEY} size="invisible" ref={recaptchaRef} - style={{ visibility: 'hidden' }} /> ); 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 227ef4a11..9fd8484cb 100644 --- a/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx +++ b/verification/curator-service/ui/src/components/landing-page/SignInForm.tsx @@ -234,20 +234,6 @@ export default function SignInForm({ > Sign in - - This site is protected by reCAPTCHA and the Google{' '} - - Privacy Policy - {' '} - and{' '} - - Terms of Service - {' '} - apply. - Don't have an account?{' '} Sign up - - This site is protected by reCAPTCHA and the Google{' '} - - Privacy Policy - {' '} - and{' '} - - Terms of Service - {' '} - apply. - - Already have an account?{' '}