Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1498 from SumOfUs/feature.recaptcha-changes
Browse files Browse the repository at this point in the history
Get reCAPTCHA token just before verification
  • Loading branch information
osahyoun committed Sep 9, 2019
2 parents 5883542 + afef217 commit b3f1b3f
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 30 deletions.
2 changes: 1 addition & 1 deletion app/controllers/api/payment/braintree_controller.rb
Expand Up @@ -92,7 +92,7 @@ def valid_user?(user)

def verify_bot
action = 'donate/' + params[:page_id]
@captcha = Recaptcha3.new(token: params[:recaptacha_token], action: action)
@captcha = Recaptcha3.new(token: params[:recaptcha_token], action: action)

unless @captcha.human?
msg = @captcha.errors.present? ? @captcha.errors : 'Invalid request'
Expand Down
Expand Up @@ -202,6 +202,7 @@ Thanks so much for everything you do!",
"petition.sign_it": "Sign the petition",
"petition.target_prefix": "TO",
"petition.thank_you": "Thanks for adding your name to \\"{petition_title}\\"",
"recaptcha_branding_html": "This site is protected by reCAPTCHA and the Google <a href=\\"https://policies.google.com/privacy\\">Privacy Policy</a> and <a href=\\"https://policies.google.com/terms\\">Terms of Service</a> apply.",
"recommend_pages.actions": "{action_count} actions",
"recommend_pages.learn_more": "learn more",
"recommend_pages.recommend_pages_title": "Here are other similar campaigns we need your support on!",
Expand Down Expand Up @@ -645,6 +646,7 @@ Thanks so much for everything you do!",
"petition.sign_it": "Sign the petition",
"petition.target_prefix": "TO",
"petition.thank_you": "Thanks for adding your name to \\"{petition_title}\\"",
"recaptcha_branding_html": "This site is protected by reCAPTCHA and the Google <a href=\\"https://policies.google.com/privacy\\">Privacy Policy</a> and <a href=\\"https://policies.google.com/terms\\">Terms of Service</a> apply.",
"recommend_pages.actions": "{action_count} actions",
"recommend_pages.learn_more": "learn more",
"recommend_pages.recommend_pages_title": "Here are other similar campaigns we need your support on!",
Expand Down
8 changes: 6 additions & 2 deletions app/javascript/components/Payment/Payment.css
Expand Up @@ -46,6 +46,10 @@
padding-bottom: 15px;
}

.Payment__fine-print .ReCaptchaBranding {
margin-top: 5px;
}

.Payment .gpay-button {
width: 100%;
height: 60px;
Expand Down Expand Up @@ -91,8 +95,8 @@
box-shadow: none;
}

@media(max-width: 700px) {
@media (max-width: 700px) {
.PaymentExpressDonationConflict {
width: 100%;
}
}
}
34 changes: 9 additions & 25 deletions app/javascript/components/Payment/Payment.js
Expand Up @@ -6,6 +6,7 @@ import braintreeClient from 'braintree-web/client';
import dataCollector from 'braintree-web/data-collector';
import { isEmpty } from 'lodash';
import ee from '../../shared/pub_sub';
import captcha from '../../shared/recaptcha';

import PayPal from '../Braintree/PayPal';
import BraintreeCardFields from '../Braintree/BraintreeCardFields';
Expand All @@ -14,6 +15,7 @@ import WelcomeMember from '../WelcomeMember/WelcomeMember';
import DonateButton from '../DonateButton';
import Checkbox from '../Checkbox/Checkbox';
import ShowIf from '../ShowIf';
import ReCaptchaBranding from '../ReCaptchaBranding';
import { resetMember } from '../../state/member/reducer';
import {
changeStep,
Expand All @@ -26,7 +28,6 @@ import ExpressDonation from '../ExpressDonation/ExpressDonation';
// Styles
import './Payment.css';

const RECAPTCHA_SITE_KEY = window.champaign.configuration.recaptcha3.siteKey;
const BRAINTREE_TOKEN_URL =
process.env.BRAINTREE_TOKEN_URL || '/api/payment/braintree/token';

Expand All @@ -48,7 +49,6 @@ export class Payment extends Component {
},
errors: [],
waitingForGoCardless: false,
recaptacha_token: null,
};
}

Expand Down Expand Up @@ -85,27 +85,8 @@ export class Payment extends Component {
console.warn('could not fetch Braintree token');
});
this.bindGlobalEvents();
if (RECAPTCHA_SITE_KEY) this.loadReCaptcha();
}

loadReCaptcha() {
try {
grecaptcha
.execute(RECAPTCHA_SITE_KEY, { action: `donate/${this.props.page.id}` })
.then(
token => {
this.setState({
recaptacha_token: token,
});
},
error => {
console.warn('Error fetching recaptcha token ', error);
}
);
} catch (error) {
console.warn('Error trying to execute grecaptcha.', error);
}
}
bindGlobalEvents() {
ee.on('fundraiser:actions:make_payment', this.makePayment);
}
Expand Down Expand Up @@ -252,13 +233,16 @@ export class Payment extends Component {
}
};

submit = data => {
submit = async data => {
const recaptcha_action = `donate/${this.props.page.id}`;
const recaptcha_token = await captcha.execute({ action: recaptcha_action });

const payload = {
...this.donationData(),
payment_method_nonce: data.nonce,
device_data: this.state.deviceData,
recaptacha_token: this.state.recaptacha_token,
recaptacha_action: `donate/${this.props.page.id}`,
recaptcha_token,
recaptcha_action,
};

this.emitTransactionSubmitted();
Expand Down Expand Up @@ -327,7 +311,6 @@ export class Payment extends Component {
} else {
errors = [<FormattedMessage id="fundraiser.unknown_error" />];
}
if (RECAPTCHA_SITE_KEY) this.loadReCaptcha();
this.setState({ errors: errors });
this.onError(response);
};
Expand Down Expand Up @@ -466,6 +449,7 @@ export class Payment extends Component {
For further information, please contact info@sumofus.org.
`}
/>
<ReCaptchaBranding />
</div>

{this.props.showDirectDebit && (
Expand Down
21 changes: 21 additions & 0 deletions app/javascript/components/ReCaptchaBranding.tsx
@@ -0,0 +1,21 @@
import * as React from 'react';
import { FormattedHTMLMessage } from 'react-intl';

const ReCaptchaBranding = () => (
<p className="ReCaptchaBranding">
<FormattedHTMLMessage
id="recaptcha_branding_html"
defaultMessage={
<p>
{'This site is protected by reCAPTCHA and the Google '}
<a href="https://policies.google.com/privacy">Privacy Policy </a>
{' and '}
<a href="https://policies.google.com/terms">Terms of Service</a>
{' apply.'}
</p>
}
/>
</p>
);

export default ReCaptchaBranding;
Expand Up @@ -188,6 +188,7 @@ Thanks so much for everything you do!",
"petition.sign_it": "Sign the petition",
"petition.target_prefix": "TO",
"petition.thank_you": "Thanks for adding your name to \\"{petition_title}\\"",
"recaptcha_branding_html": "This site is protected by reCAPTCHA and the Google <a href=\\"https://policies.google.com/privacy\\">Privacy Policy</a> and <a href=\\"https://policies.google.com/terms\\">Terms of Service</a> apply.",
"recommend_pages.actions": "{action_count} actions",
"recommend_pages.learn_more": "learn more",
"recommend_pages.recommend_pages_title": "Here are other similar campaigns we need your support on!",
Expand Down
57 changes: 57 additions & 0 deletions app/javascript/shared/recaptcha/index.ts
@@ -0,0 +1,57 @@
import './recaptcha.css';

const MAX_RETRIES = 4;
export interface IReCaptchaInstance {
ready: (cb: () => any) => void;
execute: (siteKey: string, options: IExecuteOptions) => Promise<string>;
}
interface IExecuteOptions {
action: string;
}

export const isReady = () => {
return window.grecaptcha != null && window.grecaptcha.execute != null;
};

export const load = (): Promise<IReCaptchaInstance> => {
if (isReady()) {
return Promise.resolve(window.grecaptcha as IReCaptchaInstance);
}

const siteKey = window.champaign.configuration.recaptcha3.siteKey;

const src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.async = true;
script.defer = true;
document.body.appendChild(script);

return new Promise((resolve, reject) => {
let retries = 0;
const id = setTimeout(() => {
if (window.grecaptcha) {
clearTimeout(id);
return resolve(window.grecaptcha);
} else if (retries >= MAX_RETRIES) {
clearTimeout(id);
return reject(new Error('Could not load reCAPTCHA'));
} else {
retries = retries + 1;
}
}, 400);
});
};

export const execute = async (options: IExecuteOptions): Promise<string> => {
const siteKey = window.champaign.configuration.recaptcha3.siteKey;
const captcha = await load();
return captcha.execute(siteKey, options);
};

export default {
load,
isReady,
execute,
};
3 changes: 3 additions & 0 deletions app/javascript/shared/recaptcha/recaptcha.css
@@ -0,0 +1,3 @@
.grecaptcha-badge {
visibility: hidden;
}
6 changes: 5 additions & 1 deletion app/javascript/window.d.ts
Expand Up @@ -4,22 +4,26 @@ import { Store } from 'redux';
import { Fundraiser } from './plugins/fundraiser';
import { Petition } from './plugins/petition';
import Plugin, { IPluginConfig } from './plugins/plugin';
import { IReCaptchaInstance } from './shared/recaptcha';
import { IAppState, IFormField } from './types';

declare global {
// tslint:disable-next-line:interface-name
interface Window {
grecaptcha?: IReCaptchaInstance;
champaign: IChampaignGlobalObject;
I18n: II18n & typeof I18n;
store: Store<any>;
ee: EventEmitter;
}
}

interface IChampaignGlobalObject {
configuration: {
environment: string;
defaultCurrency: string;
recaptcha3: {
siteKey: string;
};
[key: string]: any;
};
page: IChampaignPage;
Expand Down
1 change: 1 addition & 0 deletions config/locales/member_facing.de.yml
Expand Up @@ -262,3 +262,4 @@ de:
message: 'Cookies erleichtern die Nutzung unserer Webseite.'
dismiss_button_text: 'Einverstanden, weiter'
privacy_policy_link_text: 'Datenschutz'
recaptcha_branding_html: 'Diese Website ist durch reCAPTCHA geschützt und es gelten die <a href="https://policies.google.com/privacy?hl=de">Datenschutzbestimmungen</a> and <a href="https://policies.google.com/terms?hl=de">Nutzungsbedingungen</a> von Google.'
1 change: 1 addition & 0 deletions config/locales/member_facing.en.yml
Expand Up @@ -269,3 +269,4 @@ en:
message: 'Cookies are important to the proper functioning of this site.'
dismiss_button_text: 'Agree & Proceed'
privacy_policy_link_text: 'Privacy Policy'
recaptcha_branding_html: 'This site is protected by reCAPTCHA and the Google <a href="https://policies.google.com/privacy">Privacy Policy</a> and <a href="https://policies.google.com/terms">Terms of Service</a> apply.'
1 change: 1 addition & 0 deletions config/locales/member_facing.es.yml
Expand Up @@ -270,3 +270,4 @@ es:
message: 'Las cookies son importantes para el funcionamiento de este sitio.'
dismiss_button_text: 'Aceptar & Continuar'
privacy_policy_link_text: 'Política de privacidad'
recaptcha_branding_html: 'Este sitio está protegido por reCAPTCHA y se aplican la <a href="https://policies.google.com/privacy?hl=es">Política de Privacidad</a> y los <a href="https://policies.google.com/terms?hl=es">Términos de Servicio</a> de Google.'
2 changes: 1 addition & 1 deletion config/locales/member_facing.fr.yml
Expand Up @@ -248,8 +248,8 @@ fr:
opt_in_reason: "<p>Nous demandons aux membres SumOfUs de confirmer qu’ils/elles souhaitent toujours recevoir nos courriels à ce sujet et concernant d’autres campagnes urgentes.</p><p><b>Souhaitez-vous confirmer aujourd’hui?</b></p>"
decline: "Pas maintenant"
accept: "Oui"

cookie_consent:
message: 'Les cookies sont importants pour le bon fonctionnement de notre site.'
dismiss_button_text: 'Accepter et continuer'
privacy_policy_link_text: 'Mentions Légales'
recaptcha_branding_html: "Ce site est protégé par reCAPTCHA. Les <a href='https://policies.google.com/privacy?hl=fr'>règles de confidentialité</a> et les <a href='https://policies.google.com/terms?hl=fr'>conditions d'utilisation</a> de Google s'appliquent."

0 comments on commit b3f1b3f

Please sign in to comment.