From 94f3326c595d8aaf678458c006ebb7e009df4e1c Mon Sep 17 00:00:00 2001 From: rajranjan0608 Date: Tue, 21 Jun 2022 13:22:36 +0530 Subject: [PATCH 1/3] v2 recaptcha when v3 fails --- client/src/components/FaucetForm.tsx | 23 +++++++-- client/src/components/ReCaptcha.ts | 74 +++++++++++++++++++++++----- client/src/config.json | 1 + client/src/configure.ts | 1 + middlewares/verifyCaptcha.ts | 59 +++++++++++++++++++--- server.ts | 2 +- 6 files changed, 135 insertions(+), 25 deletions(-) diff --git a/client/src/components/FaucetForm.tsx b/client/src/components/FaucetForm.tsx index f5da48d..8d865be 100644 --- a/client/src/components/FaucetForm.tsx +++ b/client/src/components/FaucetForm.tsx @@ -13,6 +13,7 @@ import { AxiosResponse } from 'axios' const FaucetForm = (props: any) => { const [chain, setChain] = useState(null) const [token, setToken] = useState(null) + const [isV2, setIsV2] = useState(false) const [chainConfigs, setChainConfigs] = useState([]) const [inputAddress, setInputAddress] = useState("") const [address, setAddress] = useState(null) @@ -28,7 +29,7 @@ const FaucetForm = (props: any) => { message: null }) - const recaptcha: ReCaptcha = new ReCaptcha(props.config.SITE_KEY, props.config.ACTION) + const recaptcha: ReCaptcha = new ReCaptcha(props.config.SITE_KEY, props.config.ACTION, props.config.V2_SITE_KEY) // Update chain configs useEffect(() => { @@ -250,9 +251,9 @@ const FaucetForm = (props: any) => { } } - async function getCaptchaToken(): Promise { - const token: string = await recaptcha.getToken() - return token + async function getCaptchaToken(): Promise<{token?:string, v2Token?: string}> { + const { token, v2Token } = await recaptcha.getToken(isV2) + return { token, v2Token } } function updateChain(option: any): void { @@ -281,13 +282,14 @@ const FaucetForm = (props: any) => { try { setIsLoading(true) - const token = await getCaptchaToken() + const { token, v2Token } = await getCaptchaToken() let { chain, erc20 } = getChainParams() const response = await props.axios.post(props.config.api.sendToken, { address, token, + v2Token, chain, erc20 }) @@ -296,6 +298,13 @@ const FaucetForm = (props: any) => { data = err?.response?.data || err } + if(typeof data?.message == "string") { + if(data.message.includes("Captcha verification failed")) { + setIsV2(true) + !isV2 && recaptcha?.loadV2Captcha(); + } + } + setSendTokenResponse({ txHash: data?.txHash, message: data?.message @@ -406,6 +415,8 @@ const FaucetForm = (props: any) => { ) const back = (): void => { + setIsV2(false) + recaptcha.loadReCaptcha(props.config.SITE_KEY, props.config.V2_SITE_KEY) setSendTokenResponse({ txHash: null, message: null @@ -473,6 +484,8 @@ const FaucetForm = (props: any) => { {sendTokenResponse?.message} +
+

This is a testnet faucet. Funds are not real.

diff --git a/client/src/components/ReCaptcha.ts b/client/src/components/ReCaptcha.ts index f51861d..27a59ff 100644 --- a/client/src/components/ReCaptcha.ts +++ b/client/src/components/ReCaptcha.ts @@ -6,26 +6,78 @@ declare global { export default class ReCaptcha { siteKey: string + v2siteKey?: string action: string - constructor(SITE_KEY: string, ACTION: string) { - loadReCaptcha(SITE_KEY) + + constructor(SITE_KEY: string, ACTION: string, V2_SITE_KEY: string) { + this.loadReCaptcha(SITE_KEY, V2_SITE_KEY) + this.siteKey = SITE_KEY + this.v2siteKey = V2_SITE_KEY this.action = ACTION } - async getToken(): Promise { - let token = "" - await window.grecaptcha.execute(this.siteKey, {action: this.action}) + async getToken(isV2 = false): Promise<{token?: string, v2Token?: string}> { + let token = "", v2Token = "" + !isV2 && await window.grecaptcha.execute(this.siteKey, {action: this.action}) .then((res: string) => { token = res }) - return token + + if(isV2){ + const widgetID = getWidgetID() + v2Token = await window.grecaptcha.getResponse(widgetID) + } + + return { token, v2Token } + } + + loadV2Captcha = () => { + window.grecaptcha = null + + const script = document.createElement('script') + script.src = `https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit` + script.setAttribute('async', '') + script.setAttribute('defer', '') + + const elem = document.createElement('div') + elem.id = "v2CaptchaContainer" + + document.body.appendChild(elem) + document.body.appendChild(script) + + return true + } + + loadReCaptcha = (siteKey: string, v2SiteKey: string): boolean => { + window.grecaptcha = null + + const script = document.createElement('script') + script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}` + + // script for loading v2 captcha when required + const oncallbackscript = document.createElement('script') + + oncallbackscript.innerHTML = `var onloadCallback = function() { + console.log("loading ${v2SiteKey}") + const elem = document.getElementsByClassName('v2-recaptcha')[0]; + elem.innerHTML = "" + const v2CaptchaContainer = document.getElementById('v2CaptchaContainer'); + const widgetID = window.grecaptcha.render(elem, { + 'sitekey' : "${v2SiteKey}", + 'theme': 'dark' + }) + const widgetElem = \`\` + v2CaptchaContainer.innerHTML = widgetElem + };` + + document.body.appendChild(oncallbackscript) + document.body.appendChild(script) + return true } } -const loadReCaptcha = (siteKey: string): boolean => { - const script = document.createElement('script') - script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}` - document.body.appendChild(script) - return true +const getWidgetID = () => { + const elem = document.getElementById('widgetID'); + return elem!.value } \ No newline at end of file diff --git a/client/src/config.json b/client/src/config.json index 1c432bf..847a94c 100644 --- a/client/src/config.json +++ b/client/src/config.json @@ -5,6 +5,7 @@ "apiTimeout": 10000, "CAPTCHA": { "siteKey": "6LerTgYgAAAAALa7WiJs3q0pM30PH6JH4oDi_DaK", + "v2siteKey": "6LcPmIYgAAAAADKWHw28ercYsZV7QbDMz_SUeK-Q", "action": "faucetdrip" } } diff --git a/client/src/configure.ts b/client/src/configure.ts index 1555608..2f546d6 100644 --- a/client/src/configure.ts +++ b/client/src/configure.ts @@ -14,6 +14,7 @@ export const config = { faucetAddress: 'faucetAddress' }, SITE_KEY: configurations.CAPTCHA.siteKey, + V2_SITE_KEY: configurations.CAPTCHA.v2siteKey, ACTION: configurations.CAPTCHA.action, banner: configurations.banner } \ No newline at end of file diff --git a/middlewares/verifyCaptcha.ts b/middlewares/verifyCaptcha.ts index e060b2a..032efc1 100644 --- a/middlewares/verifyCaptcha.ts +++ b/middlewares/verifyCaptcha.ts @@ -2,35 +2,78 @@ const axios = require('axios') export class VerifyCaptcha { secret: string + v2secret?: string middleware = (req: any, res: any, next: () => void) => this.verifyCaptcha(req, res, next) - constructor(app: any, CAPTCHA_SECRET: string) { + constructor(app: any, CAPTCHA_SECRET: string, V2_CAPTCHA_SECRET?: string) { if(typeof CAPTCHA_SECRET != "string") { throw "Captcha Secret should be string" } this.secret = CAPTCHA_SECRET + this.v2secret = V2_CAPTCHA_SECRET } - async shouldAllow(token: string): Promise { - const URL = `https://www.google.com/recaptcha/api/siteverify?secret=${this.secret}&response=${token}` - const response = await axios.post(URL) + async verifyV2Token(v2Token: string): Promise { + if(v2Token) { + const URL = `https://www.google.com/recaptcha/api/siteverify?secret=${this.v2secret}&response=${v2Token}` + let response + + try { + response = await axios.post(URL) + } catch(err: any){ + console.log("Recaptcha V2 error:", err?.message) + } + + const data = response?.data + + if(data?.success) { + return true; + } + } + + return false + } + + async verifyV3Token(v3Token: string): Promise { + const URL = `https://www.google.com/recaptcha/api/siteverify?secret=${this.secret}&response=${v3Token}` + let response + + try { + response = await axios.post(URL) + } catch(err: any){ + console.log("Recaptcha V3 error:", err?.message) + } + const data = response?.data if(data?.success) { - if(data?.score > 0.5) { - return true + if(data?.action == 'faucetdrip') { + if(data?.score > 1) { + return true + } } } return false } + async shouldAllow(token: string, v2Token: string): Promise { + if(await this.verifyV3Token(token)) { + return true + } else { + if(await this.verifyV2Token(v2Token)) { + return true + } + } + return false + } + async verifyCaptcha(req: any, res: any, next: () => void) { - const shouldAllow = await this.shouldAllow(req?.body?.token) + const shouldAllow = await this.shouldAllow(req?.body?.token, req?.body?.v2Token) if(shouldAllow) { next() } else { - return res.status(400).send({message: "Captcha verification failed! Try refreshing."}) + return res.status(400).send({message: "Captcha verification failed! Try solving below."}) } } } \ No newline at end of file diff --git a/server.ts b/server.ts index ed1adc2..a864a5e 100644 --- a/server.ts +++ b/server.ts @@ -37,7 +37,7 @@ new RateLimiter(app, [ ...erc20tokens ]) -const captcha: VerifyCaptcha = new VerifyCaptcha(app, process.env.CAPTCHA_SECRET!) +const captcha: VerifyCaptcha = new VerifyCaptcha(app, process.env.CAPTCHA_SECRET!, process.env.V2_CAPTCHA_SECRET) let evms = new Map() From 5fa5f68d7805d5b6f703a38ab3c5feee16fb6408 Mon Sep 17 00:00:00 2001 From: rajranjan0608 Date: Tue, 21 Jun 2022 22:12:08 +0530 Subject: [PATCH 2/3] rendering same recaptcha for v2 and v3 --- client/src/components/FaucetForm.tsx | 10 ++++-- client/src/components/ReCaptcha.ts | 47 +++++++++------------------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/client/src/components/FaucetForm.tsx b/client/src/components/FaucetForm.tsx index 8d865be..52688f9 100644 --- a/client/src/components/FaucetForm.tsx +++ b/client/src/components/FaucetForm.tsx @@ -301,7 +301,7 @@ const FaucetForm = (props: any) => { if(typeof data?.message == "string") { if(data.message.includes("Captcha verification failed")) { setIsV2(true) - !isV2 && recaptcha?.loadV2Captcha(); + !isV2 && recaptcha?.loadV2Captcha(props.config.V2_SITE_KEY); } } @@ -414,9 +414,13 @@ const FaucetForm = (props: any) => { ) - const back = (): void => { + const resetRecaptcha = (): void => { setIsV2(false) - recaptcha.loadReCaptcha(props.config.SITE_KEY, props.config.V2_SITE_KEY) + recaptcha.loadReCaptcha(props.config.SITE_KEY) + } + + const back = (): void => { + resetRecaptcha() setSendTokenResponse({ txHash: null, message: null diff --git a/client/src/components/ReCaptcha.ts b/client/src/components/ReCaptcha.ts index 27a59ff..04a283f 100644 --- a/client/src/components/ReCaptcha.ts +++ b/client/src/components/ReCaptcha.ts @@ -10,7 +10,7 @@ export default class ReCaptcha { action: string constructor(SITE_KEY: string, ACTION: string, V2_SITE_KEY: string) { - this.loadReCaptcha(SITE_KEY, V2_SITE_KEY) + this.loadReCaptcha(SITE_KEY) this.siteKey = SITE_KEY this.v2siteKey = V2_SITE_KEY @@ -32,46 +32,27 @@ export default class ReCaptcha { return { token, v2Token } } - loadV2Captcha = () => { - window.grecaptcha = null - - const script = document.createElement('script') - script.src = `https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit` - script.setAttribute('async', '') - script.setAttribute('defer', '') + loadV2Captcha = (v2siteKey: string) => { + const v2CaptchaContainer = document.getElementsByClassName('v2-recaptcha')[0] + + const widgetID = window.grecaptcha.render(v2CaptchaContainer, { + 'sitekey' : v2siteKey, + 'theme': 'dark' + }) - const elem = document.createElement('div') - elem.id = "v2CaptchaContainer" + const widgetContainer = document.createElement('div') + const widgetElem = `` + widgetContainer.innerHTML = widgetElem - document.body.appendChild(elem) - document.body.appendChild(script) + document.body.appendChild(widgetContainer) return true } - loadReCaptcha = (siteKey: string, v2SiteKey: string): boolean => { - window.grecaptcha = null - + loadReCaptcha = (siteKey: string): boolean => { const script = document.createElement('script') script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}` - // script for loading v2 captcha when required - const oncallbackscript = document.createElement('script') - - oncallbackscript.innerHTML = `var onloadCallback = function() { - console.log("loading ${v2SiteKey}") - const elem = document.getElementsByClassName('v2-recaptcha')[0]; - elem.innerHTML = "" - const v2CaptchaContainer = document.getElementById('v2CaptchaContainer'); - const widgetID = window.grecaptcha.render(elem, { - 'sitekey' : "${v2SiteKey}", - 'theme': 'dark' - }) - const widgetElem = \`\` - v2CaptchaContainer.innerHTML = widgetElem - };` - - document.body.appendChild(oncallbackscript) document.body.appendChild(script) return true } @@ -79,5 +60,5 @@ export default class ReCaptcha { const getWidgetID = () => { const elem = document.getElementById('widgetID'); - return elem!.value + return elem!?.value } \ No newline at end of file From c9a386b93b020446ef442c2ab7f7637dbb5f7f3d Mon Sep 17 00:00:00 2001 From: rajranjan0608 Date: Tue, 21 Jun 2022 23:55:51 +0530 Subject: [PATCH 3/3] reset captcha widget --- client/src/components/FaucetForm.tsx | 99 ++++++++++++++-------------- client/src/components/ReCaptcha.ts | 46 ++++++++----- middlewares/verifyCaptcha.ts | 2 +- 3 files changed, 80 insertions(+), 67 deletions(-) diff --git a/client/src/components/FaucetForm.tsx b/client/src/components/FaucetForm.tsx index 52688f9..c4ac059 100644 --- a/client/src/components/FaucetForm.tsx +++ b/client/src/components/FaucetForm.tsx @@ -13,6 +13,7 @@ import { AxiosResponse } from 'axios' const FaucetForm = (props: any) => { const [chain, setChain] = useState(null) const [token, setToken] = useState(null) + const [widgetID, setwidgetID] = useState(undefined) const [isV2, setIsV2] = useState(false) const [chainConfigs, setChainConfigs] = useState([]) const [inputAddress, setInputAddress] = useState("") @@ -29,7 +30,13 @@ const FaucetForm = (props: any) => { message: null }) - const recaptcha: ReCaptcha = new ReCaptcha(props.config.SITE_KEY, props.config.ACTION, props.config.V2_SITE_KEY) + const recaptcha: ReCaptcha = new ReCaptcha( + props.config.SITE_KEY, + props.config.ACTION, + props.config.V2_SITE_KEY, + setwidgetID, + widgetID + ) // Update chain configs useEffect(() => { @@ -416,7 +423,7 @@ const FaucetForm = (props: any) => { const resetRecaptcha = (): void => { setIsV2(false) - recaptcha.loadReCaptcha(props.config.SITE_KEY) + recaptcha.resetV2Captcha() } const back = (): void => { @@ -472,60 +479,56 @@ const FaucetForm = (props: any) => {
- { - !sendTokenResponse.txHash - ? -
-

- Drops are limited to - - {chainConfigs[token!]?.RATELIMIT?.MAX_LIMIT} request in {toString(chainConfigs[token!]?.RATELIMIT?.WINDOW_SIZE)}. - -

+
+

+ Drops are limited to + + {chainConfigs[token!]?.RATELIMIT?.MAX_LIMIT} request in {toString(chainConfigs[token!]?.RATELIMIT?.WINDOW_SIZE)}. + +

-
- updateAddress(e.target.value)} autoFocus/> -
- {sendTokenResponse?.message} +
+ updateAddress(e.target.value)} autoFocus/> +
+ {sendTokenResponse?.message} -
- -
-

This is a testnet faucet. Funds are not real.

-
+
- +
+

This is a testnet faucet. Funds are not real.

- : + + +
+ +
+

+ {sendTokenResponse?.message} +

+
+ Transaction ID

- {sendTokenResponse.message} + + {sendTokenResponse?.txHash} +

- -
- Transaction ID -

- - {sendTokenResponse.txHash} - -

-
- -
- } + + +
diff --git a/client/src/components/ReCaptcha.ts b/client/src/components/ReCaptcha.ts index 04a283f..116ce3b 100644 --- a/client/src/components/ReCaptcha.ts +++ b/client/src/components/ReCaptcha.ts @@ -8,13 +8,17 @@ export default class ReCaptcha { siteKey: string v2siteKey?: string action: string + widgetID: string | undefined + setWidgetID: any - constructor(SITE_KEY: string, ACTION: string, V2_SITE_KEY: string) { + constructor(SITE_KEY: string, ACTION: string, V2_SITE_KEY: string, setWidgetID: any, widgetID: string | undefined) { this.loadReCaptcha(SITE_KEY) this.siteKey = SITE_KEY this.v2siteKey = V2_SITE_KEY this.action = ACTION + this.widgetID = widgetID + this.setWidgetID = setWidgetID } async getToken(isV2 = false): Promise<{token?: string, v2Token?: string}> { @@ -25,26 +29,37 @@ export default class ReCaptcha { }) if(isV2){ - const widgetID = getWidgetID() - v2Token = await window.grecaptcha.getResponse(widgetID) + v2Token = await window.grecaptcha.getResponse(this.widgetID) } return { token, v2Token } } + resetV2Captcha = () => { + const v2CaptchaContainer = document.getElementsByClassName('v2-recaptcha')[0] + if(v2CaptchaContainer) { + if(this.widgetID) { + window.grecaptcha.reset(this.widgetID) + } + v2CaptchaContainer.style.display = "none" + } + } + loadV2Captcha = (v2siteKey: string) => { const v2CaptchaContainer = document.getElementsByClassName('v2-recaptcha')[0] - const widgetID = window.grecaptcha.render(v2CaptchaContainer, { - 'sitekey' : v2siteKey, - 'theme': 'dark' - }) - - const widgetContainer = document.createElement('div') - const widgetElem = `` - widgetContainer.innerHTML = widgetElem - - document.body.appendChild(widgetContainer) + if(this.widgetID || this.widgetID == "0") { + const v2CaptchaContainer = document.getElementsByClassName('v2-recaptcha')[0] + if(v2CaptchaContainer) { + v2CaptchaContainer.style.display = "block" + } + } else { + const widgetID = window.grecaptcha.render(v2CaptchaContainer, { + 'sitekey' : v2siteKey, + 'theme': 'dark' + }) + this.setWidgetID(widgetID) + } return true } @@ -56,9 +71,4 @@ export default class ReCaptcha { document.body.appendChild(script) return true } -} - -const getWidgetID = () => { - const elem = document.getElementById('widgetID'); - return elem!?.value } \ No newline at end of file diff --git a/middlewares/verifyCaptcha.ts b/middlewares/verifyCaptcha.ts index 032efc1..905b0a6 100644 --- a/middlewares/verifyCaptcha.ts +++ b/middlewares/verifyCaptcha.ts @@ -48,7 +48,7 @@ export class VerifyCaptcha { if(data?.success) { if(data?.action == 'faucetdrip') { - if(data?.score > 1) { + if(data?.score > 0.5) { return true } }