0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

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 <zimo@ghost.org>
This commit is contained in:
Rishabh Garg 2021-05-19 19:56:45 +05:30 committed by GitHub
parent 5422496dbc
commit 69367de59e
10 changed files with 192 additions and 45 deletions

View file

@ -1,7 +1,39 @@
<div class="gh-launch-wizard-settings-container"> <div class="gh-launch-wizard-settings-container">
{{#if this.isHidden}} {{#if this.isConnectDisallowed}}
<h4 class="gh-setting-title">Subscriptions already set</h4> <div class="gh-stack overflow-y-auto flex-grow-1">
<p>You can change product subscription pricing and Portal look and feel in Settings.</p> <div class="gh-setting-nossl-container">
<span class="red">{{svg-jar "shield-lock"}}</span>
<h4>Your site is not secured</h4>
<p>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 <a href="https://ghost.org/integrations/lets-encrypt/" target="_blank" rel="noopener noreferrer">found here</a>.</p>
</div>
<div class="w-100 mt6">
<div class="gh-setting-title">Generate secure key</div>
<div class="flex items-center mb4 gh-members-connectbutton-container justify-between mt2">
<div class="stripe-connect disabled"><span>Connect with Stripe</span></div>
<div class="ml2 flex items-center flex-nowrap">
<span class="mr2 f8 midgrey nowrap">Test mode</span>
<div class="for-switch small disabled">
<label class="switch" for="stripe-connect-test-mode">
<input type="checkbox" class="gh-input" disabled="disabled">
<span class="input-toggle-component mt1"></span>
</label>
</div>
</div>
</div>
<div class="gh-setting-action">
<GhTextarea
class="gh-launch-wizard-stripe-connect-token"
placeholder="Paste your secure key here"
disabled="disabled"
/>
</div>
</div>
</div>
{{else if this.isHidden}}
<div class="gh-stack overflow-y-auto flex-grow-1">
<h4 class="gh-setting-title">Subscriptions already set</h4>
<p>You can change product subscription pricing and signup options in Membership settings.</p>
</div>
{{else}} {{else}}
<div class="gh-stack overflow-y-auto flex-grow-1"> <div class="gh-stack overflow-y-auto flex-grow-1">
<div class="gh-stack-item flex-column"> <div class="gh-stack-item flex-column">

View file

@ -1,4 +1,5 @@
import Component from '@glimmer/component'; import Component from '@glimmer/component';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object'; import {action} from '@ember/object';
import {currencies, getSymbol} from 'ghost-admin/utils/currency'; import {currencies, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service'; import {inject as service} from '@ember/service';
@ -35,6 +36,12 @@ export default class GhLaunchWizardSetPricingComponent extends Component {
return this.currencies.findBy('value', this.currency); return this.currencies.findBy('value', this.currency);
} }
get isConnectDisallowed() {
const siteUrl = this.config.get('blogUrl');
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
}
get disabled() { get disabled() {
if (this.product) { if (this.product) {
return this.product.get('stripePrices') && this.product.get('stripePrices').length > 0; return this.product.get('stripePrices') && this.product.get('stripePrices').length > 0;
@ -167,23 +174,27 @@ export default class GhLaunchWizardSetPricingComponent extends Component {
@task @task
*saveAndContinue() { *saveAndContinue() {
yield this.validateStripePlans(); if (this.isHidden || !this.isConnectDisallowed) {
this.args.nextStep();
} else {
yield this.validateStripePlans();
if (this.stripePlanError) { if (this.stripePlanError) {
return false; 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) { calculateDiscount(monthly, yearly) {

View file

@ -1,8 +1,34 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object'; import {action} from '@ember/object';
import {inject as service} from '@ember/service'; import {inject as service} from '@ember/service';
import {tracked} from '@glimmer/tracking'; 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 { export default class LaunchController extends Controller {
@service config; @service config;
@service router; @service router;
@ -15,34 +41,38 @@ export default class LaunchController extends Controller {
@tracked step = 'customise-design'; @tracked step = 'customise-design';
@tracked data = null; @tracked data = null;
steps = { steps = 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'
}
};
skippedSteps = []; 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() { get currentStep() {
return this.steps[this.step]; return this.steps[this.step];
} }

View file

@ -1,4 +1,5 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object'; import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency'; import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service'; import {inject as service} from '@ember/service';
@ -50,6 +51,11 @@ export default class MembersAccessController extends Controller {
return CURRENCIES.findBy('value', this.currency); return CURRENCIES.findBy('value', this.currency);
} }
get isConnectDisallowed() {
const siteUrl = this.config.get('blogUrl');
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
}
leaveRoute(transition) { leaveRoute(transition) {
if (this.settings.get('hasDirtyAttributes')) { if (this.settings.get('hasDirtyAttributes')) {
transition.abort(); transition.abort();

View file

@ -85,6 +85,16 @@
line-height: 0; 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 */ /* Connect Stripe settings */
.gh-launch-wizard-stripe-info { .gh-launch-wizard-stripe-info {

View file

@ -1812,3 +1812,42 @@ p.theme-validation-details {
width: 100%; 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);
}

View file

@ -743,3 +743,8 @@ Usage: CTA buttons grouped together horizontally.
.gh-btn-stripe-status.connected:before { .gh-btn-stripe-status.connected:before {
background: var(--green); background: var(--green);
} }
.stripe-connect.disabled {
pointer-events: none;
opacity: 0.5;
}

View file

@ -557,6 +557,10 @@ textarea {
transform: translateX(16px); transform: translateX(16px);
} }
.for-switch.disabled {
opacity: 0.5;
pointer-events: none;
}
/* Select /* Select
/* ---------------------------------------------------------- */ /* ---------------------------------------------------------- */

View file

@ -64,7 +64,7 @@
<div class="gh-setting-members-tierscontainer"> <div class="gh-setting-members-tierscontainer">
<div class="gh-settings-members-tiersheader"> <div class="gh-settings-members-tiersheader">
<h4 class="gh-main-section-header small bn">Membership tiers</h4> <h4 class="gh-main-section-header small bn">Membership tiers</h4>
<button type="button" class="gh-btn gh-btn-outline gh-btn-stripe-status {{if this.settings.stripeConnectAccountId "connected" ""}}" {{action (toggle "showStripeConnect" this)}}> <button type="button" class="gh-btn gh-btn-outline gh-btn-stripe-status {{if this.isConnectDisallowed "disabled"}} {{if this.settings.stripeConnectAccountId "connected" ""}}" {{action (toggle "showStripeConnect" this)}}>
<span> <span>
{{if this.settings.stripeConnectAccountId "Connected to Stripe" "Stripe not connected"}} {{if this.settings.stripeConnectAccountId "Connected to Stripe" "Stripe not connected"}}
</span> </span>
@ -114,11 +114,20 @@
{{#if this.settings.stripeConnectAccountId}} {{#if this.settings.stripeConnectAccountId}}
<button type="button" class="gh-btn" {{action (toggle "paidOpen" this)}} data-test-toggle-pub-info><span>{{if this.paidOpen "Close" "Expand"}}</span></button> <button type="button" class="gh-btn" {{action (toggle "paidOpen" this)}} data-test-toggle-pub-info><span>{{if this.paidOpen "Close" "Expand"}}</span></button>
{{else}} {{else}}
<button type="button" class="stripe-connect" {{action (toggle "showStripeConnect" this)}}> <button type="button" class="stripe-connect {{if this.isConnectDisallowed "disabled"}}" {{action (toggle "showStripeConnect" this)}}>
<span>Connect with Stripe</span> <span>Connect with Stripe</span>
</button> </button>
{{/if}} {{/if}}
</div> </div>
{{#if this.isConnectDisallowed}}
<div class="gh-setting-nossl">
<div class="gh-setting-nossl-container">
<span class="red">{{svg-jar "shield-lock"}}</span>
<h4>Your site is not secured</h4>
<p>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 <a href="https://ghost.org/integrations/lets-encrypt/" target="_blank" rel="noopener noreferrer">found here</a>.</p>
</div>
</div>
{{/if}}
<div class="gh-expandable-content"> <div class="gh-expandable-content">
{{#liquid-if this.paidOpen}} {{#liquid-if this.paidOpen}}
<div class="gh-setting-content-extended"> <div class="gh-setting-content-extended">

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>shield-lock</title><path class="a" d="M2.25,3.923v7.614A11.907,11.907,0,0,0,9.882,22.65l1.041.4a3,3,0,0,0,2.154,0l1.041-.4A11.907,11.907,0,0,0,21.75,11.537V3.923a1.487,1.487,0,0,0-.868-1.362A21.7,21.7,0,0,0,12,.75,21.7,21.7,0,0,0,3.118,2.561,1.487,1.487,0,0,0,2.25,3.923Z"/><rect class="a" x="7.5" y="9.75" width="9" height="7.5" rx="1.5" ry="1.5"/><path class="a" d="M12,5.25h0a3,3,0,0,0-3,3v1.5h6V8.25A3,3,0,0,0,12,5.25Z"/><path class="a" d="M12,13.154a.375.375,0,1,1-.375.375A.375.375,0,0,1,12,13.154"/></svg>

After

Width:  |  Height:  |  Size: 703 B