From 6960e7255d803ed179a3cd5ae541ba17fa9d5bcc Mon Sep 17 00:00:00 2001 From: Giacomo Licari Date: Sun, 22 Dec 2024 13:24:31 +0100 Subject: [PATCH 1/2] Add support for Cloudflare Captcha --- .github/workflows/publish-api.yaml | 46 ++++++------ .github/workflows/publish-ui.yaml | 52 +++++++------- api/.env.example | 6 +- api/api/routes.py | 4 ++ api/api/services/__init__.py | 1 + api/api/services/captcha.py | 72 +++++++++++++++---- api/api/services/csrf.py | 13 ++-- api/api/services/validator.py | 15 +++- api/api/settings.py | 6 +- api/tests/conftest.py | 11 ++- api/tests/temp_env_var.py | 1 + api/tests/test_api_cli.py | 2 +- app/.env.example | 2 +- app/Dockerfile | 4 +- app/package.json | 1 + app/src/App.tsx | 7 +- .../components/Captcha/CloudflareCaptcha.tsx | 28 ++++++++ .../Captcha/{Captcha.tsx => HCaptcha.tsx} | 6 +- app/src/components/FaucetForm/Faucet.tsx | 66 ++++++++--------- app/yarn.lock | 23 +++--- 20 files changed, 233 insertions(+), 133 deletions(-) create mode 100644 app/src/components/Captcha/CloudflareCaptcha.tsx rename app/src/components/Captcha/{Captcha.tsx => HCaptcha.tsx} (65%) diff --git a/.github/workflows/publish-api.yaml b/.github/workflows/publish-api.yaml index 84358f8..5d91a11 100644 --- a/.github/workflows/publish-api.yaml +++ b/.github/workflows/publish-api.yaml @@ -77,28 +77,28 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - eks-deployment-restart: - # Run job on branch dev only - if: github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - needs: build-and-push-image - permissions: - id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC - contents: read - steps: - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v4.0.0 - with: - audience: sts.amazonaws.com - role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }} - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ secrets.DEV_AWS_REGION }} + # eks-deployment-restart: + # # Run job on branch dev only + # if: github.ref == 'refs/heads/dev' + # runs-on: ubuntu-latest + # needs: build-and-push-image + # permissions: + # id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC + # contents: read + # steps: + # - name: configure aws credentials + # uses: aws-actions/configure-aws-credentials@v4.0.0 + # with: + # audience: sts.amazonaws.com + # role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }} + # role-session-name: GitHub_to_AWS_via_FederatedOIDC + # aws-region: ${{ secrets.DEV_AWS_REGION }} - - name: Configure kubectl for EKS - run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }} + # - name: Configure kubectl for EKS + # run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }} - - name: Restart Bridge Explorer Deployment - if: github.ref == 'refs/heads/dev' - run: | - kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }} - kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_API }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }} \ No newline at end of file + # - name: Restart Deployment + # if: github.ref == 'refs/heads/dev' + # run: | + # kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }} + # kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_API }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }} \ No newline at end of file diff --git a/.github/workflows/publish-ui.yaml b/.github/workflows/publish-ui.yaml index 490bef2..6daa219 100644 --- a/.github/workflows/publish-ui.yaml +++ b/.github/workflows/publish-ui.yaml @@ -55,7 +55,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - "REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.DEV_REACT_APP_HCAPTCHA_SITE_KEY }}" + "REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.DEV_REACT_APP_CAPTCHA_SITE_KEY }}" "REACT_APP_FAUCET_API_URL=${{ secrets.DEV_REACT_APP_FAUCET_API_URL}}" - name: Gnosis Chain - Main branch / tags - Build and push Docker image @@ -67,7 +67,7 @@ jobs: tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-gc labels: ${{ steps.meta.outputs.labels }} build-args: | - "REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.PROD_GC_REACT_APP_HCAPTCHA_SITE_KEY }}" + "REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.PROD_GC_REACT_APP_CAPTCHA_SITE_KEY }}" "REACT_APP_FAUCET_API_URL=${{ secrets.PROD_GC_REACT_APP_FAUCET_API_URL}}" - name: Chiado Chain - Main branch / tags - Build and push Docker image @@ -79,31 +79,31 @@ jobs: tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-chiado labels: ${{ steps.meta.outputs.labels }} build-args: | - "REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.PROD_CHIADO_REACT_APP_HCAPTCHA_SITE_KEY }}" + "REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.PROD_CHIADO_REACT_APP_CAPTCHA_SITE_KEY }}" "REACT_APP_FAUCET_API_URL=${{ secrets.PROD_CHIADO_REACT_APP_FAUCET_API_URL}}" - eks-deployment-restart: - # Run job on branch dev only - if: github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - needs: build-and-push-image - permissions: - id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC - contents: read - steps: - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v4.0.0 - with: - audience: sts.amazonaws.com - role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }} - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ secrets.DEV_AWS_REGION }} + # eks-deployment-restart: + # # Run job on branch dev only + # if: github.ref == 'refs/heads/dev' + # runs-on: ubuntu-latest + # needs: build-and-push-image + # permissions: + # id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC + # contents: read + # steps: + # - name: configure aws credentials + # uses: aws-actions/configure-aws-credentials@v4.0.0 + # with: + # audience: sts.amazonaws.com + # role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }} + # role-session-name: GitHub_to_AWS_via_FederatedOIDC + # aws-region: ${{ secrets.DEV_AWS_REGION }} - - name: Configure kubectl for EKS - run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }} + # - name: Configure kubectl for EKS + # run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }} - - name: Restart Bridge Explorer Deployment - if: github.ref == 'refs/heads/dev' - run: | - kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }} - kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_UI }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }} \ No newline at end of file + # - name: Restart Deployment + # if: github.ref == 'refs/heads/dev' + # run: | + # kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }} + # kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_UI }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }} \ No newline at end of file diff --git a/api/.env.example b/api/.env.example index f158dc3..e0ce30d 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,8 +1,10 @@ -FAUCET_AMOUNT=0.1 +FAUCET_AMOUNT=0.001 FAUCET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 FAUCET_RPC_URL=https://rpc.chiadochain.net FAUCET_CHAIN_ID=10200 FAUCET_DATABASE_URI=sqlite:// CAPTCHA_VERIFY_ENDPOINT=https://api.hcaptcha.com/siteverify CAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000 -CAPTCHA_SITE_KEY=xxxxx-xxxxx-xxxxx-xxxxx \ No newline at end of file +CAPTCHA_SITE_KEY=xxxxx-xxxxx-xxxxx-xxxxx +CSRF_PRIVATE_KEY="!!CREATE_YOUR_RSA_PRIVATE_KEY!!" +CSRF_SECRET_SALT="test-salt" \ No newline at end of file diff --git a/api/api/routes.py b/api/api/routes.py index b8afab0..d1554de 100644 --- a/api/api/routes.py +++ b/api/api/routes.py @@ -8,6 +8,7 @@ claim_token) from .services.database import AccessKey, Token, Transaction + apiv1 = Blueprint("version1", "version1") @@ -116,6 +117,9 @@ def ask(): @apiv1.route("/cli/ask", methods=["POST"]) def cli_ask(): + if not current_app.config['FAUCET_ENABLE_CLI_API']: + return jsonify(errors=['Endpoint disabled']), 403 + access_key_id = request.headers.get('X-faucet-access-key-id', None) secret_access_key = request.headers.get('X-faucet-secret-access-key', None) diff --git a/api/api/services/__init__.py b/api/api/services/__init__.py index 77abb75..11a079b 100644 --- a/api/api/services/__init__.py +++ b/api/api/services/__init__.py @@ -4,3 +4,4 @@ from .token import Token from .transaction import Web3Singleton, claim_native, claim_token from .validator import AskEndpointValidator +from .captcha import CaptchaSingleton diff --git a/api/api/services/captcha.py b/api/api/services/captcha.py index 5008eb6..de1beb3 100644 --- a/api/api/services/captcha.py +++ b/api/api/services/captcha.py @@ -5,16 +5,62 @@ logging.basicConfig(level=logging.INFO) -def captcha_verify(client_response, catpcha_api_url, secret_key, remote_ip, site_key): - request = requests.post(catpcha_api_url, data={ - 'response': client_response, - 'secret': secret_key, - 'remoteip': remote_ip, - 'sitekey': site_key - }) - - logging.info('Captcha verify response: %s' % request.json()) - - if request.status_code != 200: - return False - return request.json()['success'] == True +class Captcha: + def __init__(self, provider): + self.provider = provider + + def verify(self, client_response, catpcha_api_url, secret_key, remote_ip, site_key=None): + logging.info('Captcha: Remote IP %s' % remote_ip) + + if self.provider == 'HCAPTCHA': + request = requests.post(catpcha_api_url, data={ + 'response': client_response, + 'secret': secret_key, + 'remoteip': remote_ip, + 'sitekey': site_key + }) + + logging.info('Captcha: verify response %s' % request.json()) + + if request.status_code != 200: + return False + return request.json()['success'] is True + elif self.provider == 'CLOUDFLARE': + request = requests.post(catpcha_api_url, data={ + 'response': client_response, + 'secret': secret_key, + 'remoteip': remote_ip + }) + + logging.info('Captcha: verify response %s' % request.json()) + + if request.status_code != 200: + return False + return request.json()['success'] is True + else: + raise NotImplementedError + + +class CaptchaSingleton: + _instance = None + + def __new__(cls, provider): + if not hasattr(cls, 'instance'): + cls.instance = Captcha(provider) + return cls.instance + + +# def captcha_verify(client_response, catpcha_api_url, secret_key, remote_ip, site_key): +# logging.info('Captcha: Remote IP %s' % remote_ip) +# request = requests.post(catpcha_api_url, data={ +# 'response': client_response, +# 'secret': secret_key, +# 'remoteip': remote_ip, +# 'sitekey': site_key +# }) + +# logging.info('Captcha: verify response %s' % request.json()) + +# if request.status_code != 200: +# return False +# return request.json()['success'] == True diff --git a/api/api/services/csrf.py b/api/api/services/csrf.py index 3e2f11c..b514e63 100644 --- a/api/api/services/csrf.py +++ b/api/api/services/csrf.py @@ -6,9 +6,10 @@ from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA -# Waiting period: the minimum time interval between UI asks for the CSFR token -# and the time it asks for funds. -CSRF_TIMESTAMP_MIN_SECONDS = 15 +# Waiting period: the minimum time interval between the UI asks +# for the CSFR token to /api/v1/info and the time the UI can ask for funds. +# This check aims to block any bots that could be triggering actions through the UI. +CSRF_TIMESTAMP_MIN_SECONDS = 5 class CSRFTokenItem: @@ -45,9 +46,9 @@ def validate_token(self, request_id, token, timestamp): decrypted_text = cipher_rsa.decrypt(bytes.fromhex(token)).decode() expected_text = '%s%s%f' % (request_id, self._salt, timestamp) if decrypted_text == expected_text: - # Check that timestamp is OK, the diff between now() and creation time in seconds - # must be greater than min. waiting period. - # Waiting period: the minimum time interval between UI asks for the CSFR token and the time it asks for funds. + # Check that the timestamp is OK, the diff between now() and creation time in seconds + # must be greater than the minimum waiting period. + # Waiting period: the minimum time interval between UI asks for the CSFR token and the time the UI can ask for funds. seconds_diff = (datetime.now()-datetime.fromtimestamp(timestamp)).total_seconds() if seconds_diff > CSRF_TIMESTAMP_MIN_SECONDS: return True diff --git a/api/api/services/validator.py b/api/api/services/validator.py index 8972c4f..266ff69 100644 --- a/api/api/services/validator.py +++ b/api/api/services/validator.py @@ -5,7 +5,7 @@ from flask import current_app, request from web3 import Web3 -from .captcha import captcha_verify +from .captcha import CaptchaSingleton from .csrf import CSRF from .database import AccessKeyConfig, BlockedUsers, Token, Transaction from .rate_limit import Strategy @@ -140,14 +140,23 @@ def data_validation(self): def captcha_validation(self): error_key = 'captcha' - # check hcatpcha - catpcha_verified = captcha_verify( + + captcha = CaptchaSingleton(current_app.config['CAPTCHA_PROVIDER']) + catpcha_verified = captcha.verify( self.request_data.get('captcha'), current_app.config['CAPTCHA_VERIFY_ENDPOINT'], current_app.config['CAPTCHA_SECRET_KEY'], self.ip_address, current_app.config['CAPTCHA_SITE_KEY'] ) + # check hcatpcha + # catpcha_verified = captcha_verify( + # self.request_data.get('captcha'), + # current_app.config['CAPTCHA_VERIFY_ENDPOINT'], + # current_app.config['CAPTCHA_SECRET_KEY'], + # self.ip_address, + # current_app.config['CAPTCHA_SITE_KEY'] + # ) if not catpcha_verified: self.errors.append('%s: validation failed' % error_key) diff --git a/api/api/settings.py b/api/api/settings.py index 8e5c029..9ac9e06 100644 --- a/api/api/settings.py +++ b/api/api/settings.py @@ -19,14 +19,18 @@ FAUCET_ADDRESS: LocalAccount = Account.from_key(FAUCET_PRIVATE_KEY).address FAUCET_RATE_LIMIT_STRATEGY = rate_limit_strategy FAUCET_RATE_LIMIT_TIME_LIMIT_SECONDS = int(os.getenv('FAUCET_RATE_LIMIT_TIME_LIMIT_SECONDS', 86400)) # 86400 = 24h +FAUCET_ENABLE_CLI_API = os.getenv('FAUCET_ENABLE_CLI_API', "False") == "True" SQLALCHEMY_DATABASE_URI = os.getenv('FAUCET_DATABASE_URI') CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', '*') +CAPTCHA_PROVIDER = os.getenv('CAPTCHA_PROVIDER', 'CLOUDFLARE') CAPTCHA_VERIFY_ENDPOINT = os.getenv('CAPTCHA_VERIFY_ENDPOINT') CAPTCHA_SECRET_KEY = os.getenv('CAPTCHA_SECRET_KEY') -CAPTCHA_SITE_KEY = os.getenv('CAPTCHA_SITE_KEY') +CAPTCHA_SITE_KEY = os.getenv('CAPTCHA_SITE_KEY', None) # It's mandatory for HCAPTCHA +if CAPTCHA_PROVIDER == 'HCAPTCHA' and CAPTCHA_SITE_KEY is None: + raise ValueError('CAPTCHA_SITE_KEY is mandatory for HCAPTCHA') CSRF_PRIVATE_KEY = os.getenv('CSRF_PRIVATE_KEY') CSRF_SECRET_SALT = os.getenv('CSRF_SECRET_SALT') diff --git a/api/tests/conftest.py b/api/tests/conftest.py index c02621b..f6eaa35 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -4,7 +4,6 @@ from api.services import CSRF, Strategy from api.services.database import Token, db -from flask.testing import FlaskClient from api import create_app @@ -25,13 +24,19 @@ def mock_claim_erc20(self, *args): tx_hash = '0x1' + '%d' % self.erc20_tx_counter * 63 self.erc20_tx_counter += 1 return tx_hash + + def mock_captcha_verify(self, *args): + class Test: + def verify(self, *args): + return True + return Test def _mock(self, env_variables=None): # Mock values self.patchers = [ mock.patch('api.routes.claim_native', self.mock_claim_native), mock.patch('api.routes.claim_token', self.mock_claim_erc20), - mock.patch('api.services.validator.captcha_verify', return_value=True), + mock.patch('api.services.validator.CaptchaSingleton', self.mock_captcha_verify), mock.patch('api.api.print_info', return_value=None) ] if env_variables: @@ -133,4 +138,4 @@ def setUp(self): self.csrf = CSRF.instance # use same token for the whole test # use a timestamp that would be actually validated by the CSRF class. - self.csrf_token = self.csrf.generate_token(timestamp=self.valid_csrf_timestamp) + self.csrf_token = self.csrf.generate_token(timestamp=self.valid_csrf_timestamp) \ No newline at end of file diff --git a/api/tests/temp_env_var.py b/api/tests/temp_env_var.py index ef369d2..a3fd286 100644 --- a/api/tests/temp_env_var.py +++ b/api/tests/temp_env_var.py @@ -38,6 +38,7 @@ 'FAUCET_RATE_LIMIT_TIME_LIMIT_SECONDS': '10', 'FAUCET_DATABASE_URI': 'sqlite://', # run in-memory # 'FAUCET_DATABASE_URI': 'sqlite:///test.db', + 'FAUCET_ENABLE_CLI_API': 'True', 'CAPTCHA_SECRET_KEY': CAPTCHA_TEST_SECRET_KEY, 'CSRF_PRIVATE_KEY': privatekey.export_key().decode(), 'CSRF_SECRET_SALT': 'testsalt' diff --git a/api/tests/test_api_cli.py b/api/tests/test_api_cli.py index 4c39641..588efbd 100644 --- a/api/tests/test_api_cli.py +++ b/api/tests/test_api_cli.py @@ -10,7 +10,7 @@ ERC20_TOKEN_ADDRESS, FAUCET_CHAIN_ID) -class TestAPICli(BaseTest): +class TestAPICliEnabledEndpoints(BaseTest): def test_ask_route_parameters(self): access_key_id, secret_access_key = generate_access_key() http_headers = { diff --git a/app/.env.example b/app/.env.example index 8d828ba..477dd51 100644 --- a/app/.env.example +++ b/app/.env.example @@ -1,2 +1,2 @@ -REACT_APP_HCAPTCHA_SITE_KEY= +REACT_APP_CAPTCHA_SITE_KEY= REACT_APP_FAUCET_API_URL=http://localhost:8000/api/v1 \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile index fc31313..fa7b3f6 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -8,9 +8,9 @@ RUN yarn COPY . . ENV NODE_ENV production -ARG REACT_APP_HCAPTCHA_SITE_KEY +ARG REACT_APP_CAPTCHA_SITE_KEY ARG REACT_APP_FAUCET_API_URL -ENV REACT_APP_HCAPTCHA_SITE_KEY ${REACT_APP_HCAPTCHA_SITE_KEY} +ENV REACT_APP_CAPTCHA_SITE_KEY ${REACT_APP_HAPTCHA_SITE_KEY} ENV REACT_APP_FAUCET_API_URL ${REACT_APP_FAUCET_API_URL} RUN yarn build diff --git a/app/package.json b/app/package.json index 49e5358..d3efd79 100644 --- a/app/package.json +++ b/app/package.json @@ -17,6 +17,7 @@ "react-scripts": "5.0.1", "react-select": "^5.8.0", "react-toastify": "^9.1.3", + "react-turnstile": "^1.1.4", "typescript": "^5.3.3", "web-vitals": "^2.1.0" }, diff --git a/app/src/App.tsx b/app/src/App.tsx index b89391a..4feeb35 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -48,11 +48,8 @@ function App(): JSX.Element { toast.error("Network error") }) .finally(() => { - // 5 seconds waiting period - setTimeout(function () { - setFaucetLoading(false) - setLoading(false) - }, 5000) + setFaucetLoading(false) + setLoading(false) }) }, []) diff --git a/app/src/components/Captcha/CloudflareCaptcha.tsx b/app/src/components/Captcha/CloudflareCaptcha.tsx new file mode 100644 index 0000000..65654f4 --- /dev/null +++ b/app/src/components/Captcha/CloudflareCaptcha.tsx @@ -0,0 +1,28 @@ +import React from "react" +import Turnstile from "react-turnstile"; + +const siteKey = process.env.REACT_APP_CAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001" + +interface CaptchaProps { + setCaptchaToken: (token: string) => void, + // windowWidth: number, + // captchaRef: null +} + +const CloudflareCaptchaWidget: React.FC = ({ setCaptchaToken }) => { + + const onVerifyCaptcha = (token: string) => { + console.log(token) + setCaptchaToken(token) + } + + return ( + + ) +} + +export default CloudflareCaptchaWidget diff --git a/app/src/components/Captcha/Captcha.tsx b/app/src/components/Captcha/HCaptcha.tsx similarity index 65% rename from app/src/components/Captcha/Captcha.tsx rename to app/src/components/Captcha/HCaptcha.tsx index 2b6adad..54ccf5e 100644 --- a/app/src/components/Captcha/Captcha.tsx +++ b/app/src/components/Captcha/HCaptcha.tsx @@ -1,7 +1,7 @@ import React, { RefObject } from "react" import HCaptcha from "@hcaptcha/react-hcaptcha" -const siteKey = process.env.REACT_APP_HCAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001" +const siteKey = process.env.REACT_APP_CAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001" // common test key interface CaptchaProps { setCaptchaToken: (token: string) => void, @@ -9,7 +9,7 @@ interface CaptchaProps { captchaRef: RefObject } -const Captcha: React.FC = ({ setCaptchaToken, windowWidth, captchaRef }) => { +const HCaptchaWidget: React.FC = ({ setCaptchaToken, windowWidth, captchaRef }) => { const onVerifyCaptcha = (token: string) => { setCaptchaToken(token) @@ -25,4 +25,4 @@ const Captcha: React.FC = ({ setCaptchaToken, windowWidth, captcha ) } -export default Captcha +export default HCaptchaWidget diff --git a/app/src/components/FaucetForm/Faucet.tsx b/app/src/components/FaucetForm/Faucet.tsx index 0d7e038..c98c316 100644 --- a/app/src/components/FaucetForm/Faucet.tsx +++ b/app/src/components/FaucetForm/Faucet.tsx @@ -1,10 +1,9 @@ -import { useState, useRef, ChangeEvent, FormEvent, useEffect, Dispatch, SetStateAction } from "react" +import { useState, ChangeEvent, FormEvent, useEffect, Dispatch, SetStateAction } from "react" import "./Faucet.css" import { toast } from "react-toastify" import axios from "axios" -import Captcha from "../Captcha/Captcha" +import Captcha from "../Captcha/CloudflareCaptcha" import TokenSelect, { Token } from "../TokenSelect/TokenSelect" -import HCaptcha from "@hcaptcha/react-hcaptcha" import { formatLimit } from "../../utils" interface FaucetProps { @@ -29,8 +28,6 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId, time const [windowWidth, setWindowWidth] = useState(window.innerWidth) const [requestOngoing, setRequestOngoing] = useState(false) - const captchaRef = useRef(null) - useEffect(() => { const handleResize = () => setWindowWidth(window.innerWidth) window.addEventListener("resize", handleResize) @@ -106,34 +103,33 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId, time 'X-CSRFToken': csrfToken } - setTimeout(function() { - axios - .post(apiURL, requestData, { - headers: headers - }) - .then((response) => { - setWalletAddress("") - - if (enabledTokens.length > 1 ) { - setToken(null) - } - - // Reset captcha - setCaptchaToken("") - captchaRef.current?.resetCaptcha() - - setLoading(false) - setRequestOngoing(false) - - toast.success("Token sent to your wallet address") - setTxHash(`${response.data.transactionHash}`) - }) - .catch((error) => { - toast.error(formatErrors(error.response.data.errors)) - setLoading(false) - setRequestOngoing(false) - }) - }, 10000) // 10 seconds delay + axios + .post(apiURL, requestData, { + headers: headers + }) + .then((response) => { + setWalletAddress("") + + if (enabledTokens.length > 1 ) { + setToken(null) + } + + // Reset captcha + setCaptchaToken("") + // For HCAPTCHA: + // captchaRef.current?.resetCaptcha() + + setLoading(false) + setRequestOngoing(false) + + toast.success("Token sent to your wallet address") + setTxHash(`${response.data.transactionHash}`) + }) + .catch((error) => { + toast.error(formatErrors(error.response.data.errors)) + setLoading(false) + setRequestOngoing(false) + }) } catch (error) { if (error instanceof Error) { toast.error(error.message) @@ -184,8 +180,8 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId, time