From 50c900c3addb8bb57a1b1ec4934fe3d546b2ff0c Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 12:04:09 +0100
Subject: [PATCH 1/8] Get ReCAPTCHA token just before submitting transaction
---
app/javascript/components/Payment/Payment.js | 32 +++---------
app/javascript/shared/recaptcha/index.ts | 53 ++++++++++++++++++++
app/javascript/window.d.ts | 6 ++-
3 files changed, 65 insertions(+), 26 deletions(-)
create mode 100644 app/javascript/shared/recaptcha/index.ts
diff --git a/app/javascript/components/Payment/Payment.js b/app/javascript/components/Payment/Payment.js
index 2f78ec1ed..6e8a2dbb6 100644
--- a/app/javascript/components/Payment/Payment.js
+++ b/app/javascript/components/Payment/Payment.js
@@ -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';
@@ -26,7 +27,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';
@@ -48,7 +48,6 @@ export class Payment extends Component {
},
errors: [],
waitingForGoCardless: false,
- recaptacha_token: null,
};
}
@@ -85,27 +84,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);
}
@@ -252,13 +232,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}`,
+ recaptacha_token,
+ recaptacha_action,
};
this.emitTransactionSubmitted();
@@ -327,7 +310,6 @@ export class Payment extends Component {
} else {
errors = [];
}
- if (RECAPTCHA_SITE_KEY) this.loadReCaptcha();
this.setState({ errors: errors });
this.onError(response);
};
diff --git a/app/javascript/shared/recaptcha/index.ts b/app/javascript/shared/recaptcha/index.ts
new file mode 100644
index 000000000..f6024bc38
--- /dev/null
+++ b/app/javascript/shared/recaptcha/index.ts
@@ -0,0 +1,53 @@
+const MAX_RETRIES = 4;
+export interface IReCaptchaInstance {
+ ready: (cb: () => any) => void;
+ execute: (siteKey: string, options: IExecuteOptions) => Promise;
+}
+interface IExecuteOptions {
+ action: string;
+}
+
+export const isReady = () => {
+ return window.grecaptcha != null && window.grecaptcha.execute != null;
+};
+
+export const load = (): Promise => {
+ 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) {
+ resolve(window.grecaptcha);
+ } else if (retries >= MAX_RETRIES) {
+ reject(new Error('Could not load reCAPTCHA'));
+ } else {
+ retries = retries + 1;
+ }
+ }, 400);
+ });
+};
+
+export const execute = async (options: IExecuteOptions): Promise => {
+ const siteKey = window.champaign.configuration.recaptcha3.siteKey;
+ const captcha = await load();
+ return captcha.execute(siteKey, options);
+};
+
+export default {
+ load,
+ isReady,
+ execute,
+};
diff --git a/app/javascript/window.d.ts b/app/javascript/window.d.ts
index 594f2efcf..ce04a2993 100644
--- a/app/javascript/window.d.ts
+++ b/app/javascript/window.d.ts
@@ -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;
ee: EventEmitter;
}
}
-
interface IChampaignGlobalObject {
configuration: {
environment: string;
defaultCurrency: string;
+ recaptcha3: {
+ siteKey: string;
+ };
[key: string]: any;
};
page: IChampaignPage;
From 5a945e9cde49425ec8d782ea5127f2772df83fbd Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 12:08:32 +0100
Subject: [PATCH 2/8] Clear timeout if captcha.load resolves or rejects
---
app/javascript/shared/recaptcha/index.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/javascript/shared/recaptcha/index.ts b/app/javascript/shared/recaptcha/index.ts
index f6024bc38..bc53c2f74 100644
--- a/app/javascript/shared/recaptcha/index.ts
+++ b/app/javascript/shared/recaptcha/index.ts
@@ -30,9 +30,11 @@ export const load = (): Promise => {
let retries = 0;
const id = setTimeout(() => {
if (window.grecaptcha) {
- resolve(window.grecaptcha);
+ clearTimeout(id);
+ return resolve(window.grecaptcha);
} else if (retries >= MAX_RETRIES) {
- reject(new Error('Could not load reCAPTCHA'));
+ clearTimeout(id);
+ return reject(new Error('Could not load reCAPTCHA'));
} else {
retries = retries + 1;
}
From c6ec06cffbd8f26a83bddcceaf7a827b052bcfcc Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 12:49:03 +0100
Subject: [PATCH 3/8] Fix variable name typo
---
app/controllers/api/payment/braintree_controller.rb | 2 +-
app/javascript/components/Payment/Payment.js | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/controllers/api/payment/braintree_controller.rb b/app/controllers/api/payment/braintree_controller.rb
index 484e81d5d..abbdd9f0c 100644
--- a/app/controllers/api/payment/braintree_controller.rb
+++ b/app/controllers/api/payment/braintree_controller.rb
@@ -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'
diff --git a/app/javascript/components/Payment/Payment.js b/app/javascript/components/Payment/Payment.js
index 6e8a2dbb6..c198f150f 100644
--- a/app/javascript/components/Payment/Payment.js
+++ b/app/javascript/components/Payment/Payment.js
@@ -240,8 +240,8 @@ export class Payment extends Component {
...this.donationData(),
payment_method_nonce: data.nonce,
device_data: this.state.deviceData,
- recaptacha_token,
- recaptacha_action,
+ recaptcha_token,
+ recaptcha_action,
};
this.emitTransactionSubmitted();
From 1ca6a57676e85d0306f9aa860dda01276d165227 Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 13:20:06 +0100
Subject: [PATCH 4/8] Make g-recaptcha logo visible
---
app/javascript/shared/recaptcha/index.ts | 2 ++
app/javascript/shared/recaptcha/recaptcha.css | 3 +++
2 files changed, 5 insertions(+)
create mode 100644 app/javascript/shared/recaptcha/recaptcha.css
diff --git a/app/javascript/shared/recaptcha/index.ts b/app/javascript/shared/recaptcha/index.ts
index bc53c2f74..59418b490 100644
--- a/app/javascript/shared/recaptcha/index.ts
+++ b/app/javascript/shared/recaptcha/index.ts
@@ -1,3 +1,5 @@
+import './recaptcha.css';
+
const MAX_RETRIES = 4;
export interface IReCaptchaInstance {
ready: (cb: () => any) => void;
diff --git a/app/javascript/shared/recaptcha/recaptcha.css b/app/javascript/shared/recaptcha/recaptcha.css
new file mode 100644
index 000000000..c7d0c3299
--- /dev/null
+++ b/app/javascript/shared/recaptcha/recaptcha.css
@@ -0,0 +1,3 @@
+.grecaptcha-badge {
+ z-index: 1000;
+}
From 21b2056e94b73aa99c7e851253c707cfdfa8cdf1 Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 13:54:15 +0100
Subject: [PATCH 5/8] Hide reCAPTCHA logo, display branding text
---
app/javascript/components/Payment/Payment.js | 3 +++
app/javascript/shared/recaptcha/recaptcha.css | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/app/javascript/components/Payment/Payment.js b/app/javascript/components/Payment/Payment.js
index c198f150f..6b62253d8 100644
--- a/app/javascript/components/Payment/Payment.js
+++ b/app/javascript/components/Payment/Payment.js
@@ -15,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,
@@ -455,6 +456,8 @@ export class Payment extends Component {
)}
+
+
);
}
diff --git a/app/javascript/shared/recaptcha/recaptcha.css b/app/javascript/shared/recaptcha/recaptcha.css
index c7d0c3299..545f6b084 100644
--- a/app/javascript/shared/recaptcha/recaptcha.css
+++ b/app/javascript/shared/recaptcha/recaptcha.css
@@ -1,3 +1,3 @@
.grecaptcha-badge {
- z-index: 1000;
+ visibility: hidden;
}
From 6c16728fc47b4e0413d0883556bc0cc580c85a72 Mon Sep 17 00:00:00 2001
From: Vince Martinez Fasoro
Date: Mon, 9 Sep 2019 14:41:02 +0100
Subject: [PATCH 6/8] Localise reCAPTCHA branding message
---
app/javascript/components/Payment/Payment.css | 8 +++++--
app/javascript/components/Payment/Payment.js | 3 +--
.../components/ReCaptchaBranding.tsx | 21 +++++++++++++++++++
config/locales/member_facing.de.yml | 1 +
config/locales/member_facing.en.yml | 1 +
config/locales/member_facing.es.yml | 1 +
config/locales/member_facing.fr.yml | 1 +
7 files changed, 32 insertions(+), 4 deletions(-)
create mode 100644 app/javascript/components/ReCaptchaBranding.tsx
diff --git a/app/javascript/components/Payment/Payment.css b/app/javascript/components/Payment/Payment.css
index f8726034b..3ffeb5b68 100644
--- a/app/javascript/components/Payment/Payment.css
+++ b/app/javascript/components/Payment/Payment.css
@@ -46,6 +46,10 @@
padding-bottom: 15px;
}
+.Payment__fine-print .ReCaptchaBranding {
+ margin-top: 5px;
+}
+
.Payment .gpay-button {
width: 100%;
height: 60px;
@@ -91,8 +95,8 @@
box-shadow: none;
}
-@media(max-width: 700px) {
+@media (max-width: 700px) {
.PaymentExpressDonationConflict {
width: 100%;
}
-}
\ No newline at end of file
+}
diff --git a/app/javascript/components/Payment/Payment.js b/app/javascript/components/Payment/Payment.js
index 6b62253d8..38b413221 100644
--- a/app/javascript/components/Payment/Payment.js
+++ b/app/javascript/components/Payment/Payment.js
@@ -449,6 +449,7 @@ export class Payment extends Component {
For further information, please contact info@sumofus.org.
`}
/>
+
{this.props.showDirectDebit && (
@@ -456,8 +457,6 @@ export class Payment extends Component {
)}
-
-
);
}
diff --git a/app/javascript/components/ReCaptchaBranding.tsx b/app/javascript/components/ReCaptchaBranding.tsx
new file mode 100644
index 000000000..4269824b7
--- /dev/null
+++ b/app/javascript/components/ReCaptchaBranding.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { FormattedHTMLMessage } from 'react-intl';
+
+const ReCaptchaBranding = () => (
+
+
+ {'This site is protected by reCAPTCHA and the Google '}
+ Privacy Policy
+ {' and '}
+ Terms of Service
+ {' apply.'}
+
+ }
+ />
+
+);
+
+export default ReCaptchaBranding;
diff --git a/config/locales/member_facing.de.yml b/config/locales/member_facing.de.yml
index b150ffe98..d429124a5 100644
--- a/config/locales/member_facing.de.yml
+++ b/config/locales/member_facing.de.yml
@@ -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 Datenschutzbestimmungen and Nutzungsbedingungen von Google.'
diff --git a/config/locales/member_facing.en.yml b/config/locales/member_facing.en.yml
index 26ec61d89..23aae7a57 100644
--- a/config/locales/member_facing.en.yml
+++ b/config/locales/member_facing.en.yml
@@ -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 Privacy Policy and Terms of Service apply.'
diff --git a/config/locales/member_facing.es.yml b/config/locales/member_facing.es.yml
index 9263d1d3b..6454ffac0 100644
--- a/config/locales/member_facing.es.yml
+++ b/config/locales/member_facing.es.yml
@@ -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 Política de Privacidad y los Términos de Servicio de Google.'
diff --git a/config/locales/member_facing.fr.yml b/config/locales/member_facing.fr.yml
index 5e2184b5c..dabd2ca09 100644
--- a/config/locales/member_facing.fr.yml
+++ b/config/locales/member_facing.fr.yml
@@ -253,3 +253,4 @@ fr:
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 règles de confidentialité et les conditions d'utilisation de Google s'appliquent.'
From 631ea2c0ab38447236b4ca6d9ba2df735eb8978d Mon Sep 17 00:00:00 2001
From: Omar Sahyoun
Date: Mon, 9 Sep 2019 16:20:39 +0100
Subject: [PATCH 7/8] Wrap in double quotes
---
config/locales/member_facing.fr.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/config/locales/member_facing.fr.yml b/config/locales/member_facing.fr.yml
index dabd2ca09..caa62fc17 100644
--- a/config/locales/member_facing.fr.yml
+++ b/config/locales/member_facing.fr.yml
@@ -248,9 +248,8 @@ fr:
opt_in_reason: "Nous demandons aux membres SumOfUs de confirmer qu’ils/elles souhaitent toujours recevoir nos courriels à ce sujet et concernant d’autres campagnes urgentes.
Souhaitez-vous confirmer aujourd’hui?
"
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 règles de confidentialité et les conditions d'utilisation de Google s'appliquent.'
+ recaptcha_branding_html: "Ce site est protégé par reCAPTCHA. Les règles de confidentialité et les conditions d'utilisation de Google s'appliquent."
\ No newline at end of file
From afef217b63835c0b02fc29708aeff10a8e2261cc Mon Sep 17 00:00:00 2001
From: Omar Sahyoun
Date: Mon, 9 Sep 2019 16:31:28 +0100
Subject: [PATCH 8/8] Updates snapshots
---
.../DonationBands/__snapshots__/DonationBands.test.js.snap | 2 ++
.../components/__snapshots__/ComponentWrapper.test.js.snap | 1 +
2 files changed, 3 insertions(+)
diff --git a/app/javascript/components/DonationBands/__snapshots__/DonationBands.test.js.snap b/app/javascript/components/DonationBands/__snapshots__/DonationBands.test.js.snap
index 7c1bb9530..10b7836a8 100644
--- a/app/javascript/components/DonationBands/__snapshots__/DonationBands.test.js.snap
+++ b/app/javascript/components/DonationBands/__snapshots__/DonationBands.test.js.snap
@@ -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 Privacy Policy and Terms of Service 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!",
@@ -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 Privacy Policy and Terms of Service 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!",
diff --git a/app/javascript/components/__snapshots__/ComponentWrapper.test.js.snap b/app/javascript/components/__snapshots__/ComponentWrapper.test.js.snap
index 8ba6fec5e..41898f1ba 100644
--- a/app/javascript/components/__snapshots__/ComponentWrapper.test.js.snap
+++ b/app/javascript/components/__snapshots__/ComponentWrapper.test.js.snap
@@ -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 Privacy Policy and Terms of Service 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!",