-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding recaptcha_v3 #2031
Adding recaptcha_v3 #2031
Changes from 1 commit
4ea7443
7dd5bbb
9c37b1a
21e8573
ce3a2da
07ccd82
ead9e95
499cbc4
1f8ac1f
4131a9b
2b728c2
3d2daec
257dbf6
a02b0cf
bbb4714
eaa8e91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React, { useEffect } from "react"; | ||
import { useIdentity } from "@wpmedia/arc-themes-components"; | ||
import useRecaptcha, { RECAPTCHA_V2, RECAPTCHA_V3 } from "../../utils/useRecaptcha"; | ||
import ReCAPTCHA from "react-google-recaptcha"; | ||
import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3"; | ||
import RecaptchaV3 from "./reCaptchaV3"; | ||
|
||
export const ARCXP_CAPTCHA= "ArcXP_captchaToken" | ||
|
||
const BotChallengeProtection = ({ challengeIn, setCaptchaToken, className, captchaError, setCaptchaError, resetRecaptcha }) => { | ||
const { isInitialized } = useIdentity(); | ||
const { recaptchaVersion, siteKey, isRecaptchaEnabled } = useRecaptcha(challengeIn); | ||
|
||
const onChange = (value) => { | ||
setCaptchaToken(value); | ||
setCaptchaError(null); | ||
localStorage.setItem(ARCXP_CAPTCHA, value); | ||
}; | ||
|
||
if (!isInitialized) { | ||
return null; | ||
} | ||
|
||
if (isRecaptchaEnabled && !!siteKey && !!recaptchaVersion) { | ||
if (recaptchaVersion === RECAPTCHA_V2) { | ||
return ( | ||
<section | ||
className={`${className}__bot-protection-section`} | ||
data-testid="bot-challege-protection-container" | ||
> | ||
<ReCAPTCHA sitekey={siteKey} onChange={onChange} onExpired={() => {}}/> | ||
{captchaError && <Paragraph>{phrases.t("identity-block.bot-protection-error")}</Paragraph>} | ||
</section> | ||
); | ||
} | ||
if (recaptchaVersion === RECAPTCHA_V3) { | ||
return ( | ||
<GoogleReCaptchaProvider reCaptchaKey={siteKey} scriptProps={{ async: true }}> | ||
<RecaptchaV3 setCaptchaToken={setCaptchaToken} resetRecaptcha={resetRecaptcha} /> | ||
</GoogleReCaptchaProvider> | ||
); | ||
} | ||
} else { | ||
return null; | ||
} | ||
}; | ||
|
||
export default BotChallengeProtection; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { useEffect, useCallback } from "react"; | ||
import { useGoogleReCaptcha } from "react-google-recaptcha-v3"; | ||
import { ARCXP_CAPTCHA } from "./index"; | ||
|
||
const RecaptchaV3 = ({ setCaptchaToken, resetRecaptcha }) => { | ||
const { executeRecaptcha } = useGoogleReCaptcha(); | ||
|
||
const handleReCaptcha3Verify = useCallback(async () => { | ||
if (!executeRecaptcha) { | ||
console.log("ArcXP - Execute recaptcha not yet available"); | ||
return; | ||
} | ||
const token = await executeRecaptcha(); | ||
setCaptchaToken(token); | ||
localStorage.setItem(ARCXP_CAPTCHA, token); | ||
}, [executeRecaptcha]); | ||
|
||
useEffect(() => { | ||
handleReCaptcha3Verify(); | ||
}, [executeRecaptcha, resetRecaptcha]); | ||
|
||
return null; | ||
}; | ||
|
||
export default RecaptchaV3; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,47 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import React, { useState } from "react"; | ||
import PropTypes from "@arc-fusion/prop-types"; | ||
import { useFusionContext } from "fusion:context"; | ||
import getProperties from "fusion:properties"; | ||
import getTranslatedPhrases from "fusion:intl"; | ||
import { Input, useIdentity } from "@wpmedia/arc-themes-components"; | ||
import { Input, useIdentity, Paragraph } from "@wpmedia/arc-themes-components"; | ||
import HeadlinedSubmitForm from "../../components/headlined-submit-form"; | ||
import useLogin from "../../components/login"; | ||
import BotChallengeProtection from "../../components/bot-challenge-protection"; | ||
import useOIDCLogin from "../../utils/useOIDCLogin"; | ||
import validateURL from "../../utils/validate-redirect-url"; | ||
import { RECAPTCHA_LOGIN } from "../../utils/useRecaptcha"; | ||
|
||
const BLOCK_CLASS_NAME = "b-login-form"; | ||
|
||
export function definedMessageByCode(code) { | ||
return errorCodes[code] || errorCodes["0"]; | ||
} | ||
|
||
const errorCodes = { | ||
100015: "identity-block.login-form-error.account-is-disabled", | ||
130001: "identity-block.login-form-error.captcha-token-invalid", | ||
130051: "identity-block.login-form-error.unverified-email-address", | ||
100013: "identity-block.login-form-error.max-devices", | ||
0: "identity-block.login-form-error.invalid-email-password", | ||
}; | ||
|
||
const Login = ({ customFields }) => { | ||
const { redirectURL, redirectToPreviousPage, loggedInPageLocation, OIDC } = customFields; | ||
|
||
const url_string = window.location.href; | ||
const url = new URL(url_string); | ||
const urlString = window.location.href; | ||
const url = new URL(urlString); | ||
|
||
const { isAdmin, arcSite } = useFusionContext(); | ||
const { locale } = getProperties(arcSite); | ||
const phrases = getTranslatedPhrases(locale); | ||
|
||
const isOIDC = OIDC && url.searchParams.get("client_id") && url.searchParams.get("response_type") === "code"; | ||
const isOIDC = | ||
OIDC && url.searchParams.get("client_id") && url.searchParams.get("response_type") === "code"; | ||
const { Identity, isInitialized } = useIdentity(); | ||
const [captchaToken, setCaptchaToken] = useState(); | ||
const [resetRecaptcha, setResetRecaptcha] = useState(true); | ||
const [error, setError] = useState(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the purpose of this variable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @edwardcho1231 similar to reCaptchav2, reCaptchav3 also expires, but in order to grab a new token we need to call executeRecaptcha(). Thus, resetRecaptcha is changing from true to false and viceversa everytime the reCaptcha3 is expired, thus the useEffect() detects this change and we need to grab a new one. |
||
const [captchaError, setCaptchaError] = useState(); | ||
const { loginRedirect } = useLogin({ | ||
isAdmin, | ||
redirectURL, | ||
|
@@ -41,10 +59,14 @@ const Login = ({ customFields }) => { | |
<HeadlinedSubmitForm | ||
buttonLabel={phrases.t("identity-block.log-in")} | ||
className={BLOCK_CLASS_NAME} | ||
formErrorText={error} | ||
headline={phrases.t("identity-block.log-in")} | ||
onSubmit={({ email, password }) => | ||
Identity.login(email, password, {rememberMe: true}) | ||
headline={phrases.t("identity-block.log-in-headline")} | ||
onSubmit={({ email, password }) => { | ||
setError(null); | ||
setCaptchaError(null); | ||
Identity.login(email, password, { | ||
rememberMe: true, | ||
recaptchaToken: captchaToken, | ||
}) | ||
.then(() => { | ||
if (isOIDC) { | ||
loginByOIDC(); | ||
|
@@ -53,12 +75,29 @@ const Login = ({ customFields }) => { | |
window.location = validatedURL; | ||
} | ||
}) | ||
.catch(() => setError(phrases.t("identity-block.login-form-error"))) | ||
} | ||
.catch((e) => { | ||
setResetRecaptcha(!resetRecaptcha); | ||
if (e?.code === "130001") { | ||
setCaptchaError(true); | ||
} | ||
else { | ||
setError(phrases.t(definedMessageByCode(e.code))); | ||
} | ||
if (grecaptcha) { | ||
grecaptcha.reset(); | ||
} | ||
|
||
}); | ||
}} | ||
> | ||
{error ? ( | ||
<div className={`${BLOCK_CLASS_NAME}__login-form-error`}> | ||
<Paragraph>{error}</Paragraph> | ||
</div> | ||
) : null} | ||
<Input | ||
autoComplete="email" | ||
label={phrases.t("identity-block.email")} | ||
label={phrases.t("identity-block.email-label")} | ||
name="email" | ||
required | ||
showDefaultError={false} | ||
|
@@ -73,6 +112,17 @@ const Login = ({ customFields }) => { | |
showDefaultError={false} | ||
type="password" | ||
/> | ||
<BotChallengeProtection | ||
className={BLOCK_CLASS_NAME} | ||
challengeIn={RECAPTCHA_LOGIN} | ||
setCaptchaToken={setCaptchaToken} | ||
captchaError={captchaError} | ||
setCaptchaError={setCaptchaError} | ||
resetRecaptcha={resetRecaptcha} | ||
/> | ||
<Paragraph className={`${BLOCK_CLASS_NAME}__privacy-statement`}> | ||
{phrases.t("identity-block.privacy-statement")} | ||
</Paragraph> | ||
</HeadlinedSubmitForm> | ||
); | ||
}; | ||
|
@@ -98,10 +148,11 @@ Login.propTypes = { | |
"The URL to which a user would be redirected to if visiting a login page when already logged in.", | ||
}), | ||
OIDC: PropTypes.bool.tag({ | ||
name: 'Login with OIDC', | ||
defaultValue: false, | ||
description: 'Used when authenticating a third party site with OIDC PKCE flow. This will use an ArcXp Org as an auth provider', | ||
}), | ||
name: "Login with OIDC", | ||
defaultValue: false, | ||
description: | ||
"Used when authenticating a third party site with OIDC PKCE flow. This will use an ArcXp Org as an auth provider", | ||
}), | ||
}), | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting an error when trying to log-in with recaptcha v2 without clicking reCaptcha box. Error says that Paragraph is not defined. I think we need import statement for
Paragraph
.