Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_GOOGLE_RECAPTCHA_V3_SITE_KEY=6LdY-x0bAAAAALW7N1fb7eAFqCwgRNYQKY_GTuhm
28,213 changes: 0 additions & 28,213 deletions package-lock.json

This file was deleted.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"lodash": ">=4.17.5",
"markdown": "^0.5.0",
"moment": "^2.24.0",
"node-sass": "^4.13.0",
"node-sass": "^4.14.1",
"notifyjs": "^3.0.0",
"object-assign": "4.1.1",
"platform": "^1.3.5",
Expand All @@ -74,6 +74,7 @@
"react-dev-utils": "^6.0.0-next.3e165448",
"react-dom": "^16.8.6",
"react-fa": "^5.0.0",
"react-google-recaptcha-v3": "^1.9.4",
"react-helmet": "^5.2.1",
"react-i18next": "^7.7.0",
"react-id-swiper": "^1.6.8",
Expand Down Expand Up @@ -110,14 +111,14 @@
"whatwg-fetch": "2.0.3"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"babel-eslint": "^10.0.3",
"@apollo/react-testing": "^3.1.3",
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@storybook/addon-actions": "^5.0.8",
"@storybook/addon-links": "^5.0.8",
"@storybook/addons": "^3.4.12",
"@storybook/react": "^5.0.8",
"axios-mock-adapter": "^1.15.0",
"babel-eslint": "^10.0.3",
"cypress": "^2.1.0",
"identity-obj-proxy": "^3.0.0",
"jest-localstorage-mock": "^2.2.0",
Expand Down
64 changes: 34 additions & 30 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { BrowserRouter, Route, Switch, Redirect, useLocation } from 'react-router-dom';
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';

import styled from '@emotion/styled';

import './i18n';
Expand Down Expand Up @@ -88,36 +90,38 @@ const App = () => {
<BreakpointProvider queries={defaultQuery}>
<BrowserRouter>
<Suspense fallback={<></>}>
<ToTop>
<Referrals />
<Announcement />
<Container>
<Header />

<Switch>
<Route exact path="/" render={props => <Redirect to={`/${lang}${props.location.search}`} />} />
<Route exact path="/:lang(en|de|ru)/terms-and-conditions" component={TermsConditions} />
<Route exact path="/:lang(en|de|ru)/privacy" component={Privacy} />
<Route exact path="/:lang(en|de|ru)/profile/:user?" component={Profile} />
<Route exact path="/:lang(en|de|ru)/order/:orderRef" component={Order} />
<Route exact path="/:lang(en|de|ru)/orders/:orderRef?" component={Orders} />
<Route exact path="/:lang(en|de|ru)" render={props => <Home {...props} store={store} />} />
<Route exact path="/:lang(en|de|ru)/instant-white-label/" component={WhiteLabelSEO} />
<Route exact path="/:lang(en|de|ru)/faqs/:id?" component={FAQ} />
<Route exact path="/:lang(en|de|ru)/about" component={About} />
<Route exact path="/:lang(en|de|ru)/signin" component={SignIn} />
<Route exact path="/:lang(en|de|ru)/signout" component={SignOut} />
<Route exact path="/:lang(en|de|ru)/signup" component={SignUp} />
<Route exact path="/:lang(en|de|ru)/forgot-password" component={ForgotPassword} />
<Route exact path="/:lang(en|de|ru)/convert/:quote-to-:base" render={props => <Pair {...props} store={store} />} />
<Route exact path="/:lang(en|de|ru)/not-found" component={NotFound} />
<Route component={NotFoundRedirect} />
</Switch>

<Footer />
</Container>
<Intercom />
</ToTop>
<GoogleReCaptchaProvider reCaptchaKey={process.env.REACT_APP_GOOGLE_RECAPTCHA_V3_SITE_KEY}>
<ToTop>
<Referrals />
<Announcement />
<Container>
<Header />

<Switch>
<Route exact path="/" render={props => <Redirect to={`/${lang}${props.location.search}`} />} />
<Route exact path="/:lang(en|de|ru)/terms-and-conditions" component={TermsConditions} />
<Route exact path="/:lang(en|de|ru)/privacy" component={Privacy} />
<Route exact path="/:lang(en|de|ru)/profile/:user?" component={Profile} />
<Route exact path="/:lang(en|de|ru)/order/:orderRef" component={Order} />
<Route exact path="/:lang(en|de|ru)/orders/:orderRef?" component={Orders} />
<Route exact path="/:lang(en|de|ru)" render={props => <Home {...props} store={store} />} />
<Route exact path="/:lang(en|de|ru)/instant-white-label/" component={WhiteLabelSEO} />
<Route exact path="/:lang(en|de|ru)/faqs/:id?" component={FAQ} />
<Route exact path="/:lang(en|de|ru)/about" component={About} />
<Route exact path="/:lang(en|de|ru)/signin" component={SignIn} />
<Route exact path="/:lang(en|de|ru)/signout" component={SignOut} />
<Route exact path="/:lang(en|de|ru)/signup" component={SignUp} />
<Route exact path="/:lang(en|de|ru)/forgot-password" component={ForgotPassword} />
<Route exact path="/:lang(en|de|ru)/convert/:quote-to-:base" render={props => <Pair {...props} store={store} />} />
<Route exact path="/:lang(en|de|ru)/not-found" component={NotFound} />
<Route component={NotFoundRedirect} />
</Switch>

<Footer />
</Container>
<Intercom />
</ToTop>
</GoogleReCaptchaProvider>
</Suspense>
</BrowserRouter>
</BreakpointProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { Component } from 'react';
import { I18n } from 'react-i18next';
import Checkbox from '../Checkbox/Checkbox';
import styles from '../OrderInitial.scss';
import { Helmet } from 'react-helmet';

import styled from '@emotion/styled';

import Checkbox from '../Checkbox/Checkbox';
import styles from '../OrderInitial.scss';
import OrderPreReleased from '../../OrderPreReleased/OrderPreReleased';
import OrderFailed from '../../OrderFailure/OrderFailure';
import withBotSafeguard from './withBotSafeguard';
import constant from '../../../../../../constant';

const PaymentNewTabText = styled.h4`
text-align: center;
Expand Down Expand Up @@ -50,7 +54,7 @@ class OrderInitial extends Component {
}
}

tooglePaymentIFrame() {
togglePaymentIFrame() {
this.setState({
showPaymentIFrame: !this.state.showPaymentIFrame,
});
Expand All @@ -74,6 +78,8 @@ class OrderInitial extends Component {
this.setState({ paymentStatus: data });

if (data === 'error') {
const { triggerBotValidation } = props;
if (triggerBotValidation) triggerBotValidation();
document.querySelector('#safecharge_payment_iframe').src = this.props.order.payment_url;
}

Expand Down Expand Up @@ -176,7 +182,7 @@ class OrderInitial extends Component {
title={this.state.enablePayment ? '' : t('order.tooltipTC')}
style={{ pointerEvents: 'auto' }}
onClick={() => {
props.order.payment_url && this.state.enablePayment && this.tooglePaymentIFrame();
props.order.payment_url && this.state.enablePayment && this.togglePaymentIFrame();
}}
>
<i className="fas fa-credit-card" aria-hidden="true" style={{ position: 'relative', left: -13 }} />
Expand Down Expand Up @@ -244,4 +250,4 @@ const getUrlPram = parameter => {
return value;
};

export default OrderInitial;
export default withBotSafeguard(OrderInitial, constant.reCaptchaActions.FIAT_PAYMENT);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useEffect, useState } from 'react';
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';

import styled from '@emotion/styled';

import OrderFailed from '../../OrderFailure/OrderFailure';
import verifyRecaptchaV3IsHuman from '../../../../../../utils/recaptchaVerification';

const Spinner = styled.div`
position: block;
padding: 25px;
margin: auto;
width: 40%;
`;

const withBotSafeguard = (ComponentToSafeguard, actionName) => props => {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [isVerifiedHuman, setIsVerifiedAsHuman] = useState('');
const [isVerificationInProgress, setIsVerificationInProgress] = useState(false);

const { executeRecaptcha } = useGoogleReCaptcha();

const triggerBotValidation = () => {
(async () => {
setIsVerificationInProgress(true);

const token = await executeRecaptcha(actionName);
const isHuman = await verifyRecaptchaV3IsHuman(token);
setIsVerifiedAsHuman(isHuman);

setIsVerificationInProgress(false);
if (isInitialLoading) setIsInitialLoading(false);
})();
};

useEffect(() => {
if (executeRecaptcha) triggerBotValidation();
}, [executeRecaptcha]);

if (isInitialLoading || isVerificationInProgress) {
return (
<Spinner>
<img src="/img/spinner.gif" alt="" />
</Spinner>
);
}
if (isVerifiedHuman) {
return <ComponentToSafeguard {...props} triggerBotValidation={triggerBotValidation} />;
}
return <OrderFailed title="error.notfound1" />;
};

export default withBotSafeguard;
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ const config = {
API_BASE_URL: 'https://api.n.exchange/en/api/v1',
// API_BASE_URL: 'http://localhost:8000/en/api/v1',
SUPPORT_EMAIL: '[email protected]',

PRICE_FETCH_INTERVAL: 60000,
ORDER_BOOK_FETCH_INTERVAL: 10000,
ORDER_DETAILS_FETCH_INTERVAL: 20000,
RECENT_ORDERS_INTERVAL: 20000,
RECENT_ORDERS_COUNT: 5,
PRICE_COMPARISON_INTERVAL: 60000,

REFERRAL_CODE: null,
KYC_DETAILS_FETCH_INTERVAL: 20000,
ADVANCED_MODE_ENABLED: true,
Expand Down
7 changes: 7 additions & 0 deletions src/constant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const constant = {
reCaptchaActions: {
FIAT_PAYMENT: 'fiat_payment',
},
};

export default constant;
16 changes: 16 additions & 0 deletions src/utils/recaptchaVerification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axios from 'axios';

import config from '../config';

const { API_BASE_URL } = config;

const verifyRecaptchaV3IsHuman = async token =>
axios
.post(`${API_BASE_URL}/recaptcha/v3`, { response_token: token })
.then(res => {
const { success } = res.data;
return success;
})
.catch(_ => false);

export default verifyRecaptchaV3IsHuman;