From 9c99295e43a229c8a61ba8b82b1a44372e024c94 Mon Sep 17 00:00:00 2001 From: Pomax Date: Thu, 27 Jan 2022 09:32:36 -0800 Subject: [PATCH] Add recaptcha (#1637) * Add recaptcha handling with feature flag --- .mergify.yml | 22 ++++++++++++++++------ README.md | 3 +++ components/navbar/navbar.jsx | 18 ++++++++++++------ js/app-user.js | 33 ++++++++++++++++++++++++--------- js/env-server.js | 4 +++- js/security-headers.js | 5 +++++ server.js | 8 +++++++- 7 files changed, 70 insertions(+), 23 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 0289deb..0642450 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,3 +1,14 @@ +queue_rules: + - name: dependabot + conditions: + - "status-success=continuous-integration/travis-ci/pr" + - "status-success=continuous-integration/travis-ci/push" + + - name: ready-to-merge + conditions: + - "status-success=continuous-integration/travis-ci/pr" + - "status-success=continuous-integration/travis-ci/push" + pull_request_rules: - name: Automatic merge for dependabot PRs conditions: @@ -6,10 +17,10 @@ pull_request_rules: - "status-success=continuous-integration/travis-ci/pr" - "status-success=continuous-integration/travis-ci/push" actions: - merge: + queue: method: squash - strict_method: rebase - strict: true + name: dependabot + - name: Automatic merge for PRs labelled "ready to merge" conditions: - "#approved-reviews-by>=1" @@ -17,7 +28,6 @@ pull_request_rules: - "status-success=continuous-integration/travis-ci/push" - "label=ready-to-merge" actions: - merge: + queue: method: squash - strict_method: rebase - strict: true + name: ready-to-merge diff --git a/README.md b/README.md index 9275905..77ad2f2 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ This starts a few image optimization scripts. `PULSE_API` | Default: `https://pulse-api.mofostaging.net/api/pulse`

URL to Pulse API. e.g., `http://test.example.com:8000/api/pulse`.
To set up a local instance of Pulse API, follow instructions on [Pulse API README doc](https://github.com/mozilla/network-pulse-api/blob/master/README.md). `PULSE_LOGIN_URL` | Default: `https://pulse-api.mofostaging.net/accounts/login/`

URL to use to login to Pulse. This needs to be a Pulse API login url. `PULSE_LOGOUT_URL` | Default: `https://pulse-api.mofostaging.net/accounts/logout/`

URL to use to logout of Pulse. This needs to be a Pulse API logout url. +`USE_RECAPTCHA` | Default: `true`

Whether or not to have recaptcha securing the sign up/sign in action. +`RECAPTCHA_KEY` | Default: empty string

The recaptcha site key to use, when recaptcha is enabled. `PROJECT_BATCH_SIZE`| Default: `24`

Number of projects you want to display as a batch. Make sure this number is divisible by 2 AND 3 so rows display evenly for different screen sizes. `PROFILE_BATCH_SIZE`| Default: `10`

Number of profiles you want to display as a batch. `LEARN_MORE_LINK` | Default: `https://www.mozillapulse.org/entry/120`

Link to learn more about what Pulse project is about. @@ -62,6 +64,7 @@ This starts a few image optimization scripts. `HEROKU_APP_NAME` | Default: `""`

The name of the review app (generated by Heroku). `GITHUB_TOKEN` | Default: `""`

GitHub token used by the review app slack webhook. `SLACK_WEBHOOK` | Default: `""`

Webhook of the Slack channel where the bot is posting. + ## Deployment ### Staging diff --git a/components/navbar/navbar.jsx b/components/navbar/navbar.jsx index b1f99cc..bf711cb 100644 --- a/components/navbar/navbar.jsx +++ b/components/navbar/navbar.jsx @@ -84,12 +84,18 @@ class NavBar extends React.Component { handleSignInBtnClick(event) { event.preventDefault(); - Analytics.ReactGA.event({ - category: `Account`, - action: `Login`, - label: `Login ${window.location.pathname}`, - transport: `beacon`, - }); + try { + Analytics.ReactGA.event({ + category: `Account`, + action: `Login`, + label: `Login ${window.location.pathname}`, + transport: `beacon`, + }); + } catch (err) { + // This does not need to succeed, but we also + // don't want a throw to prevent the actual + // login from happening. + } user.login(utility.getCurrentURL()); } diff --git a/js/app-user.js b/js/app-user.js index 2801fb6..bb680a7 100644 --- a/js/app-user.js +++ b/js/app-user.js @@ -3,6 +3,8 @@ import env from "./env-client"; import localstorage from "./localstorage.js"; import Service from "./service.js"; +const useRecaptcha = env.USE_RECAPTCHA; +const recaptchaKey = env.RECAPTCHA_KEY; const loginUrl = env.PULSE_LOGIN_URL; /** @@ -14,8 +16,11 @@ const Login = { /* * Generates the oauth url for logging a user in, with a redirect-when-finished URL */ - getLoginURL(redirectUrl) { - return `${loginUrl}?next=${encodeURIComponent(redirectUrl)}`; + getLoginURL(redirectUrl, recaptchaToken) { + const trailing = loginUrl.endsWith(`/`) ? `` : `/`; + const next = `?next=${encodeURIComponent(redirectUrl)}`; + const token = recaptchaToken ? `&token=${recaptchaToken}` : ``; + return [loginUrl, trailing, next, token].join(``); }, /* @@ -113,13 +118,23 @@ class User { } login(redirectUrl) { - // we record that the user started a login in localStorage, - // then the app gets closed and oauth happens. Once the - // oauth callback drops the user back at a URL that loads - // the app again, pages can run `user.verify` to finalise - // the login process. - localstorage.setItem(`pulse:user:attemptingLogin`, `True`); - window.location = Login.getLoginURL(redirectUrl); + const performLogin = (token) => { + // we record that the user started a login in localStorage, + // then the app gets closed and oauth happens. Once the + // oauth callback drops the user back at a URL that loads + // the app again, pages can run `user.verify` to finalise + // the login process. + localstorage.setItem(`pulse:user:attemptingLogin`, `True`); + window.location = Login.getLoginURL(redirectUrl, token); + }; + + if (useRecaptcha) { + grecaptcha.ready(() => + grecaptcha + .execute(recaptchaKey, { action: "submit" }) + .then((recaptcha_response) => performLogin(recaptcha_response)) + ); + } else performLogin(); } verify(location, history) { diff --git a/js/env-server.js b/js/env-server.js index f04ba3c..09013ab 100644 --- a/js/env-server.js +++ b/js/env-server.js @@ -6,7 +6,7 @@ import dotenv from "dotenv"; dotenv.config(); // load default.env so that anything didn't get set in .env or the host environment will get a default value -dotenv.config({ path: `config/default.env` }); +dotenv.config({ path: `./config/default.env` }); let envUtilities = { serializeSafeEnvAsJSON: () => { @@ -24,6 +24,8 @@ let envUtilities = { PULSE_API: process.env.PULSE_API, PULSE_LOGIN_URL: process.env.PULSE_LOGIN_URL, PULSE_LOGOUT_URL: process.env.PULSE_LOGOUT_URL, + USE_RECAPTCHA: process.env.USE_RECAPTCHA, + RECAPTCHA_KEY: process.env.RECAPTCHA_KEY, }; return JSON.stringify(config); diff --git a/js/security-headers.js b/js/security-headers.js index 4d772c0..30786cb 100644 --- a/js/security-headers.js +++ b/js/security-headers.js @@ -10,12 +10,15 @@ export default { `https://platform.twitter.com/widgets.js`, `https://www.googletagmanager.com/gtm.js`, `https://www.googletagmanager.com/debug/bootstrap`, + `https://www.google.com/recaptcha/api.js`, + `https://www.gstatic.com/recaptcha/releases/`, ], fontSrc: [ `'self'`, `https://code.cdn.mozilla.net`, `https://fonts.gstatic.com`, ], + frameSrc: [`https://www.google.com/`], styleSrc: [ `'self'`, `'unsafe-inline'`, @@ -28,6 +31,8 @@ export default { url.parse(env.PULSE_API).host || `https://network-pulse-api-staging.herokuapp.com/`, `https://www.mozilla.org/en-US/newsletter/`, + `https://www.google.com/recaptcha/api.js`, + `https://www.gstatic.com/recaptcha/releases/`, ], childSrc: [ `https://syndication.twitter.com`, diff --git a/server.js b/server.js index 7c8f234..939cfa8 100644 --- a/server.js +++ b/server.js @@ -137,6 +137,11 @@ function renderPage(appHtml, reactHelmet, canonicalUrl) { ? `` : ``; + let recaptcha = ``; + if (env.USE_RECAPTCHA) { + recaptcha = ``; + } + return ` @@ -148,7 +153,7 @@ function renderPage(appHtml, reactHelmet, canonicalUrl) { ${twitterCard} ${ogTags} - + @@ -164,6 +169,7 @@ function renderPage(appHtml, reactHelmet, canonicalUrl) { ${reactHelmet.title.toString()} + ${recaptcha}
${appHtml}