From 69367de59ed497411853e28bda252e4c06a8dc84 Mon Sep 17 00:00:00 2001 From: Rishabh Garg Date: Wed, 19 May 2021 19:56:45 +0530 Subject: [PATCH] Added SSL precondition for Stripe Connect UI (#1967) refs https://github.com/TryGhost/Team/issues/598 Stripe Webhooks require SSL in production, and so we should not be allowing connecting to Stripe in production mode unless the site is running with SSL. This change - - Updates Setup wizard to skip Stripe Connect steps if site is not on SSL in production - Adds warning on set subscriptions page Co-authored-by: Peter Zimon --- .../gh-launch-wizard/set-pricing.hbs | 38 ++++++++- .../gh-launch-wizard/set-pricing.js | 41 ++++++---- ghost/admin/app/controllers/launch.js | 80 +++++++++++++------ .../app/controllers/settings/membership.js | 6 ++ .../app/styles/layouts/fullscreen-wizard.css | 10 +++ ghost/admin/app/styles/layouts/settings.css | 39 +++++++++ ghost/admin/app/styles/patterns/buttons.css | 5 ++ ghost/admin/app/styles/patterns/forms.css | 4 + .../app/templates/settings/membership.hbs | 13 ++- .../admin/public/assets/icons/shield-lock.svg | 1 + 10 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 ghost/admin/public/assets/icons/shield-lock.svg diff --git a/ghost/admin/app/components/gh-launch-wizard/set-pricing.hbs b/ghost/admin/app/components/gh-launch-wizard/set-pricing.hbs index 85df67f6ec..49ac8392fd 100644 --- a/ghost/admin/app/components/gh-launch-wizard/set-pricing.hbs +++ b/ghost/admin/app/components/gh-launch-wizard/set-pricing.hbs @@ -1,7 +1,39 @@
- {{#if this.isHidden}} -

Subscriptions already set

-

You can change product subscription pricing and Portal look and feel in Settings.

+ {{#if this.isConnectDisallowed}} +
+
+ {{svg-jar "shield-lock"}} +

Your site is not secured

+

Paid memberships through Ghost can only be run on sites secured by SSL (HTTPS vs. HTTP). More information on adding a free SSL Certificate to your Ghost site can be found here.

+
+
+
Generate secure key
+
+
Connect with Stripe
+
+ Test mode +
+ +
+
+
+
+ +
+
+
+ {{else if this.isHidden}} +
+

Subscriptions already set

+

You can change product subscription pricing and signup options in Membership settings.

+
{{else}}
diff --git a/ghost/admin/app/components/gh-launch-wizard/set-pricing.js b/ghost/admin/app/components/gh-launch-wizard/set-pricing.js index dd2ec71771..299f7d4acb 100644 --- a/ghost/admin/app/components/gh-launch-wizard/set-pricing.js +++ b/ghost/admin/app/components/gh-launch-wizard/set-pricing.js @@ -1,4 +1,5 @@ import Component from '@glimmer/component'; +import envConfig from 'ghost-admin/config/environment'; import {action} from '@ember/object'; import {currencies, getSymbol} from 'ghost-admin/utils/currency'; import {inject as service} from '@ember/service'; @@ -35,6 +36,12 @@ export default class GhLaunchWizardSetPricingComponent extends Component { return this.currencies.findBy('value', this.currency); } + get isConnectDisallowed() { + const siteUrl = this.config.get('blogUrl'); + + return envConfig.environment !== 'development' && !/^https:/.test(siteUrl); + } + get disabled() { if (this.product) { return this.product.get('stripePrices') && this.product.get('stripePrices').length > 0; @@ -167,23 +174,27 @@ export default class GhLaunchWizardSetPricingComponent extends Component { @task *saveAndContinue() { - yield this.validateStripePlans(); + if (this.isHidden || !this.isConnectDisallowed) { + this.args.nextStep(); + } else { + yield this.validateStripePlans(); - if (this.stripePlanError) { - return false; + if (this.stripePlanError) { + return false; + } + const product = this.getProduct(); + const data = this.args.getData() || {}; + this.args.storeData({ + ...data, + product, + isFreeChecked: this.isFreeChecked, + isMonthlyChecked: this.isMonthlyChecked, + isYearlyChecked: this.isYearlyChecked, + monthlyAmount: this.stripeMonthlyAmount, + yearlyAmount: this.stripeYearlyAmount + }); + this.args.nextStep(); } - const product = this.getProduct(); - const data = this.args.getData() || {}; - this.args.storeData({ - ...data, - product, - isFreeChecked: this.isFreeChecked, - isMonthlyChecked: this.isMonthlyChecked, - isYearlyChecked: this.isYearlyChecked, - monthlyAmount: this.stripeMonthlyAmount, - yearlyAmount: this.stripeYearlyAmount - }); - this.args.nextStep(); } calculateDiscount(monthly, yearly) { diff --git a/ghost/admin/app/controllers/launch.js b/ghost/admin/app/controllers/launch.js index f36e40dbaf..e40db2649f 100644 --- a/ghost/admin/app/controllers/launch.js +++ b/ghost/admin/app/controllers/launch.js @@ -1,8 +1,34 @@ import Controller from '@ember/controller'; +import envConfig from 'ghost-admin/config/environment'; import {action} from '@ember/object'; import {inject as service} from '@ember/service'; import {tracked} from '@glimmer/tracking'; +const DEFAULT_STEPS = { + 'customise-design': { + title: 'Customise your site', + position: 'Step 1', + next: 'connect-stripe' + }, + 'connect-stripe': { + title: 'Connect to Stripe', + position: 'Step 2', + next: 'set-pricing', + back: 'customise-design', + skip: 'finalise' + }, + 'set-pricing': { + title: 'Set up subscriptions', + position: 'Step 3', + next: 'finalise', + back: 'connect-stripe' + }, + finalise: { + title: 'Launch your site', + position: 'Final step', + back: 'set-pricing' + } +}; export default class LaunchController extends Controller { @service config; @service router; @@ -15,34 +41,38 @@ export default class LaunchController extends Controller { @tracked step = 'customise-design'; @tracked data = null; - steps = { - 'customise-design': { - title: 'Customise your site', - position: 'Step 1', - next: 'connect-stripe' - }, - 'connect-stripe': { - title: 'Connect to Stripe', - position: 'Step 2', - next: 'set-pricing', - back: 'customise-design', - skip: 'finalise' - }, - 'set-pricing': { - title: 'Set up subscriptions', - position: 'Step 3', - next: 'finalise', - back: 'connect-stripe' - }, - finalise: { - title: 'Launch your site', - position: 'Final step', - back: 'set-pricing' - } - }; + steps = DEFAULT_STEPS; skippedSteps = []; + constructor(...args) { + super(...args); + const siteUrl = this.config.get('blogUrl'); + + if (envConfig.environment !== 'development' && !/^https:/.test(siteUrl)) { + this.steps = { + 'customise-design': { + title: 'Customise your site', + position: 'Step 1', + next: 'set-pricing' + }, + 'set-pricing': { + title: 'Set up subscriptions', + position: 'Step 2', + next: 'finalise', + back: 'customise-design' + }, + finalise: { + title: 'Launch your site', + position: 'Final step', + back: 'set-pricing' + } + }; + } else { + this.steps = DEFAULT_STEPS; + } + } + get currentStep() { return this.steps[this.step]; } diff --git a/ghost/admin/app/controllers/settings/membership.js b/ghost/admin/app/controllers/settings/membership.js index d561532457..629818be92 100644 --- a/ghost/admin/app/controllers/settings/membership.js +++ b/ghost/admin/app/controllers/settings/membership.js @@ -1,4 +1,5 @@ import Controller from '@ember/controller'; +import envConfig from 'ghost-admin/config/environment'; import {action} from '@ember/object'; import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency'; import {inject as service} from '@ember/service'; @@ -50,6 +51,11 @@ export default class MembersAccessController extends Controller { return CURRENCIES.findBy('value', this.currency); } + get isConnectDisallowed() { + const siteUrl = this.config.get('blogUrl'); + return envConfig.environment !== 'development' && !/^https:/.test(siteUrl); + } + leaveRoute(transition) { if (this.settings.get('hasDirtyAttributes')) { transition.abort(); diff --git a/ghost/admin/app/styles/layouts/fullscreen-wizard.css b/ghost/admin/app/styles/layouts/fullscreen-wizard.css index f888d8673c..dfea0d8232 100644 --- a/ghost/admin/app/styles/layouts/fullscreen-wizard.css +++ b/ghost/admin/app/styles/layouts/fullscreen-wizard.css @@ -85,6 +85,16 @@ line-height: 0; } +.gh-launch-wizard-settings-container .gh-setting-nossl-container { + border: 1px solid var(--whitegrey); + border-radius: 3px; +} + +.gh-launch-wizard-settings-container .gh-setting-nossl-container svg { + width: 48px; + height: 48px; +} + /* Connect Stripe settings */ .gh-launch-wizard-stripe-info { diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css index 6f92bb7121..34d376c622 100644 --- a/ghost/admin/app/styles/layouts/settings.css +++ b/ghost/admin/app/styles/layouts/settings.css @@ -1812,3 +1812,42 @@ p.theme-validation-details { width: 100%; } } + +.gh-setting-nossl { + border-top: 1px solid var(--whitegrey-d1); + display: flex; + flex-direction: column; + align-items: center; + margin: 16px -24px -12px; +} + +.gh-setting-nossl-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 32px; + text-align: center; + max-width: 520px; +} + +.gh-setting-nossl-container svg { + width: 44px; + height: 44px; + margin-bottom: 12px; +} + +.gh-setting-nossl-container svg path, +.gh-setting-nossl-container svg rect, +.gh-setting-nossl-container svg circle { + stroke-width: 1px; +} + +.gh-setting-nossl-container h4 { + font-size: 1.5rem; + font-weight: 600; +} + +.gh-setting-nossl-container p { + margin: 8px 0 0; + color: var(--midgrey); +} diff --git a/ghost/admin/app/styles/patterns/buttons.css b/ghost/admin/app/styles/patterns/buttons.css index e10ad14df9..be44e844d6 100644 --- a/ghost/admin/app/styles/patterns/buttons.css +++ b/ghost/admin/app/styles/patterns/buttons.css @@ -742,4 +742,9 @@ Usage: CTA buttons grouped together horizontally. .gh-btn-stripe-status.connected:before { background: var(--green); +} + +.stripe-connect.disabled { + pointer-events: none; + opacity: 0.5; } \ No newline at end of file diff --git a/ghost/admin/app/styles/patterns/forms.css b/ghost/admin/app/styles/patterns/forms.css index f56f6224e7..5646ad8d6d 100644 --- a/ghost/admin/app/styles/patterns/forms.css +++ b/ghost/admin/app/styles/patterns/forms.css @@ -557,6 +557,10 @@ textarea { transform: translateX(16px); } +.for-switch.disabled { + opacity: 0.5; + pointer-events: none; +} /* Select /* ---------------------------------------------------------- */ diff --git a/ghost/admin/app/templates/settings/membership.hbs b/ghost/admin/app/templates/settings/membership.hbs index eeb61f6ba0..b1044bad75 100644 --- a/ghost/admin/app/templates/settings/membership.hbs +++ b/ghost/admin/app/templates/settings/membership.hbs @@ -64,7 +64,7 @@

Membership tiers

- {{else}} - {{/if}}
+ {{#if this.isConnectDisallowed}} +
+
+ {{svg-jar "shield-lock"}} +

Your site is not secured

+

Paid memberships through Ghost can only be run on sites secured by SSL (HTTPS vs. HTTP). More information on adding a free SSL Certificate to your Ghost site can be found here.

+
+
+ {{/if}}
{{#liquid-if this.paidOpen}}
diff --git a/ghost/admin/public/assets/icons/shield-lock.svg b/ghost/admin/public/assets/icons/shield-lock.svg new file mode 100644 index 0000000000..0fac2e5db6 --- /dev/null +++ b/ghost/admin/public/assets/icons/shield-lock.svg @@ -0,0 +1 @@ +shield-lock \ No newline at end of file