ghost 5.3.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/screen.css +22 -7
- package/content/themes/casper/default.hbs +8 -5
- package/content/themes/casper/package.json +1 -1
- package/core/built/assets/ghost-dark-739c1f5546bd048eeeb253965ef36712.css +1 -0
- package/core/built/assets/{ghost.min-f4bba3a2a5ef256b82641345505d4f0f.js → ghost.min-36b64813b14c45075770658269d4b478.js} +166 -155
- package/core/built/assets/ghost.min-5211776b9497f36fac8c9e5f2584cbcc.css +1 -0
- package/core/built/assets/{vendor.min-4076498ccd6c8412365f43b156084ed8.js → vendor.min-be0129c9c6897c9f10425e2402881d77.js} +14 -13
- package/core/frontend/helpers/total_members.js +17 -0
- package/core/frontend/helpers/total_paid_members.js +16 -0
- package/core/frontend/utils/member-count.js +50 -0
- package/core/frontend/web/middleware/cors.js +2 -1
- package/core/server/api/endpoints/offers-public.js +2 -2
- package/core/server/api/endpoints/offers.js +8 -8
- package/core/server/api/endpoints/settings.js +62 -30
- package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +0 -4
- package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/offers.js +28 -0
- package/core/server/api/endpoints/utils/serializers/output/settings.js +2 -1
- package/core/server/api/endpoints/utils/validators/input/settings.js +22 -2
- package/core/server/models/tag.js +4 -0
- package/core/server/services/auth/session/express-session.js +1 -1
- package/core/server/services/members/SingleUseTokenProvider.js +3 -3
- package/core/server/services/members/config.js +4 -1
- package/core/server/services/members/middleware.js +14 -2
- package/core/server/services/members/settings.js +4 -90
- package/core/server/services/settings/emails/verify-email.js +166 -0
- package/core/server/services/settings/settings-bread-service.js +170 -4
- package/core/server/services/settings/settings-service.js +9 -1
- package/core/server/services/webhooks/serialize.js +5 -0
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/endpoints/admin/routes.js +6 -0
- package/core/server/web/api/endpoints/content/routes.js +2 -1
- package/core/server/web/api/middleware/cors.js +2 -1
- package/core/server/web/members/app.js +2 -4
- package/core/shared/config/defaults.json +3 -0
- package/package.json +12 -11
- package/yarn.lock +81 -68
- package/core/built/assets/ghost-dark-9e5d1f0dfae41232e5e34e4d0df53ae0.css +0 -1
- package/core/built/assets/ghost.min-e7cfbd1800f8e99b9158f74f1e39cd76.css +0 -1
- package/core/server/api/endpoints/utils/serializers/output/offers.js +0 -16
|
@@ -12611,20 +12611,21 @@ Object.keys(t).forEach((n=>{e.registerKeyCommand({str:n,run:()=>t[n](e,n)})})),t
|
|
|
12611
12611
|
let e=this._calloutTextEditor.post.tailPosition().toRange()
|
|
12612
12612
|
this._calloutTextEditor.selectRange(e)}_updatePayloadAttr(e,t){let n=this.args.payload
|
|
12613
12613
|
Ember.set(n,e,t),this.args.saveCard?.(n,!1)}},w=C(_.prototype,"config",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=C(_.prototype,"feature",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=C(_.prototype,"store",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=C(_.prototype,"membersUtils",[l],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=C(_.prototype,"ui",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),j=C(_.prototype,"isPickerVisible",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),C(_.prototype,"registerElement",[d],Object.getOwnPropertyDescriptor(_.prototype,"registerElement"),_.prototype),C(_.prototype,"setCalloutText",[h],Object.getOwnPropertyDescriptor(_.prototype,"setCalloutText"),_.prototype),C(_.prototype,"setCalloutEmoji",[p],Object.getOwnPropertyDescriptor(_.prototype,"setCalloutEmoji"),_.prototype),C(_.prototype,"setBackgroundColor",[f],Object.getOwnPropertyDescriptor(_.prototype,"setBackgroundColor"),_.prototype),C(_.prototype,"leaveEditMode",[m],Object.getOwnPropertyDescriptor(_.prototype,"leaveEditMode"),_.prototype),C(_.prototype,"focusElement",[g],Object.getOwnPropertyDescriptor(_.prototype,"focusElement"),_.prototype),C(_.prototype,"registerEditor",[b],Object.getOwnPropertyDescriptor(_.prototype,"registerEditor"),_.prototype),C(_.prototype,"changeEmoji",[v],Object.getOwnPropertyDescriptor(_.prototype,"changeEmoji"),_.prototype),C(_.prototype,"toggleEmoji",[y],Object.getOwnPropertyDescriptor(_.prototype,"toggleEmoji"),_.prototype),_)
|
|
12614
|
-
e.default=P,Ember._setComponentTemplate(T,P)})),define("koenig-editor/components/koenig-card-code",["exports","@tryghost/helpers"],(function(e,t){"use strict"
|
|
12615
|
-
var
|
|
12616
|
-
function
|
|
12614
|
+
e.default=P,Ember._setComponentTemplate(T,P)})),define("koenig-editor/components/koenig-card-code",["exports","@glimmer/component","@tryghost/helpers"],(function(e,t,n){"use strict"
|
|
12615
|
+
var r,i,o,a,s,l,u,c
|
|
12616
|
+
function d(e,t,n,r,i){var o={}
|
|
12617
12617
|
return Object.keys(r).forEach((function(e){o[e]=r[e]})),o.enumerable=!!o.enumerable,o.configurable=!!o.configurable,("value"in o||o.initializer)&&(o.writable=!0),o=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),o),i&&void 0!==o.initializer&&(o.value=o.initializer?o.initializer.call(i):void 0,o.initializer=void 0),void 0===o.initializer&&(Object.defineProperty(e,t,o),o=null),o}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
|
|
12618
|
-
const
|
|
12619
|
-
let
|
|
12620
|
-
return Ember.String.htmlSafe(e)}get cmMode(){let{language:e}=this.payload
|
|
12621
|
-
return
|
|
12618
|
+
const h=Ember.HTMLBars.template({id:"cPsBaPAv",block:'{"symbols":["card","@payload","@isEditing","@headerOffset","@isSelected","@selectCard","@deselectCard","@editCard","@saveCard","@saveAsSnippet","@addParagraphAfterCard","@moveCursorToPrevSection","@moveCursorToNextSection","@editor"],"statements":[[8,"koenig-card",[],[["@class","@style","@headerOffset","@toolbar","@payload","@isSelected","@isEditing","@selectCard","@deselectCard","@editCard","@saveCard","@saveAsSnippet","@onEnterEdit","@onLeaveEdit","@addParagraphAfterCard","@moveCursorToPrevSection","@moveCursorToNextSection","@editor"],[[30,[36,0],["ba b--white relative kg-card-hover miw-100",[30,[36,1],[[32,3]," bw2 pt1 pb1 pl2 nl6 pr6 nr6"],null]],null],[32,0,["cardStyle"]],[32,4],[32,0,["toolbar"]],[32,2],[32,5],[32,3],[32,6],[32,7],[32,8],[32,9],[32,10],[32,0,["enterEditMode"]],[32,0,["leaveEditMode"]],[32,11],[32,12],[32,13],[32,14]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,1],[[32,3]],null,[["default","else"],[{"statements":[[2," "],[8,"gh-cm-editor",[],[["@value","@class","@textareaClass","@autofocus","@lineWrapping","@update","@mode"],[[32,2,["code"]],"koenig-card-code--editor koenig-card-html--editor","o-0",true,true,[32,0,["updateCode"]],[32,0,["cmMode"]]]],null],[2,"\\n "],[11,"input"],[24,"aria-label","Code card language"],[16,2,[32,2,["language"]]],[24,"placeholder","Language..."],[24,0,"absolute w-20 pa1 ba b--lightgrey br2 f8 tracked-2 fw4 z-999 outline-0 anim-normal"],[16,5,[32,0,["languageInputStyle"]]],[24,4,"text"],[4,[38,2],["blur",[32,0,["updateLanguage"]]],null],[12],[13],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[10,"div"],[14,0,"koenig-card-html-rendered"],[12],[2,"\\n "],[10,"pre"],[12],[10,"code"],[15,0,[31,["line-numbers ",[30,[36,1],[[32,2,["language"]],[30,[36,0],["language-",[32,2,["language"]]],null]],null]]]],[12],[1,[32,0,["escapedCode"]]],[13],[13],[2,"\\n "],[13],[2,"\\n"],[6,[37,1],[[32,2,["language"]]],null,[["default"],[{"statements":[[2," "],[10,"div"],[14,0,"absolute top-2 right-2 flex justify-center items-center pa2"],[12],[2,"\\n "],[10,"span"],[14,0,"db nudge-top--2 fw5 f8 midlightgrey"],[12],[1,[32,2,["language"]]],[13],[2,"\\n "],[13],[2,"\\n"]],"parameters":[]}]]],[2," "],[10,"div"],[14,0,"koenig-card-click-overlay"],[12],[13],[2,"\\n"]],"parameters":[]}]]],[2,"\\n"],[6,[37,1],[[30,[36,6],[[30,[36,5],[[32,3]],null],[30,[36,4],[[32,5],[30,[36,3],[[32,2,["caption"]]],null]],null]],null]],null,[["default"],[{"statements":[[2," "],[8,[32,1,["CaptionInput"]],[],[["@class","@caption","@update","@placeholder"],["z-999",[32,2,["caption"]],[32,0,["updateCaption"]],"Type caption for code block (optional)"]],null],[2,"\\n"]],"parameters":[]}]]]],"parameters":[1]}]]]],"hasEval":false,"upvars":["concat","if","on","clean-basic-html","or","not","and"]}',moduleName:"koenig-editor/components/koenig-card-code.hbs"}),{Handlebars:p}=Ember,{countWords:f}=n.utils,m={html:"htmlmixed",xhtml:"htmlmixed",hbs:"handlebars",js:"javascript"}
|
|
12619
|
+
let g=(r=Ember._tracked,i=Ember._action,o=Ember._action,a=Ember._action,s=Ember._action,l=Ember._action,u=class extends t.default{get isEmpty(){return Ember.isBlank(this.args.payload.code)}get counts(){return{wordCount:f(this.args.payload.code)}}get toolbar(){return!this.args.isEditing&&{items:[{buttonClass:"fw4 flex items-center white",icon:"koenig/kg-edit",iconClass:"fill-white",title:"Edit",text:"",action:this.args.editCard}]}}get escapedCode(){let e=p.Utils.escapeExpression(this.args.payload.code)
|
|
12620
|
+
return Ember.String.htmlSafe(e)}get cmMode(){let{language:e}=this.args.payload
|
|
12621
|
+
return m[e]||e}get cardStyle(){let e=this.args.isEditing?"background-color: #f4f8fb; border-color: #f4f8fb":""
|
|
12622
12622
|
return Ember.String.htmlSafe(e)}get languageInputStyle(){let e=["top: 6px","right: 6px"]
|
|
12623
|
-
return this.showLanguageInput||e.push("opacity: 0"),Ember.String.htmlSafe(e.join("; "))}
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
e.
|
|
12623
|
+
return this.showLanguageInput||e.push("opacity: 0"),Ember.String.htmlSafe(e.join("; "))}constructor(){var e,t,n,r
|
|
12624
|
+
super(...arguments),e=this,t="showLanguageInput",r=this,(n=c)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})
|
|
12625
|
+
let i=this.args.payload||{}
|
|
12626
|
+
i.code||(i.code=""),this.payload=i,this.args.registerComponent(this)}updateCode(e){this._hideLanguageInput(),this._updatePayloadAttr("code",e)}updateCaption(e){this._updatePayloadAttr("caption",e)}updateLanguage(e){this._updatePayloadAttr("language",e.target.value)}enterEditMode(){this._addMousemoveHandler()}leaveEditMode(){this._removeMousemoveHandler(),this.isEmpty&&Ember.run.scheduleOnce("afterRender",this,this.args.deleteCard)}_updatePayloadAttr(e,t){let n=this.args.payload,r=this.args.saveCard
|
|
12627
|
+
n[e]=t,r(n,!1)}_hideLanguageInput(){this.showLanguageInput=!1}_showLanguageInput(){this.showLanguageInput=!0}_addMousemoveHandler(){this._mousemoveHandler=Ember.run.bind(this,this._showLanguageInput),window.addEventListener("mousemove",this._mousemoveHandler)}_removeMousemoveHandler(){window.removeEventListener("mousemove",this._mousemoveHandler)}},c=d(u.prototype,"showLanguageInput",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!0}}),d(u.prototype,"updateCode",[i],Object.getOwnPropertyDescriptor(u.prototype,"updateCode"),u.prototype),d(u.prototype,"updateCaption",[o],Object.getOwnPropertyDescriptor(u.prototype,"updateCaption"),u.prototype),d(u.prototype,"updateLanguage",[a],Object.getOwnPropertyDescriptor(u.prototype,"updateLanguage"),u.prototype),d(u.prototype,"enterEditMode",[s],Object.getOwnPropertyDescriptor(u.prototype,"enterEditMode"),u.prototype),d(u.prototype,"leaveEditMode",[l],Object.getOwnPropertyDescriptor(u.prototype,"leaveEditMode"),u.prototype),u)
|
|
12628
|
+
e.default=g,Ember._setComponentTemplate(h,g)})),define("koenig-editor/components/koenig-card-email-cta",["exports","mobiledoc-kit/utils/browser","@glimmer/component","koenig-editor/components/koenig-text-replacement-html-input","ember-concurrency"],(function(e,t,n,r,i){"use strict"
|
|
12628
12629
|
var o,a,s,l,u,c,d,h,p,f,m,g,b,v,y,_,w,M,k,E,A,j,O,x,C,T,S,P,L,D
|
|
12629
12630
|
function z(e,t,n,r){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})}function N(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function R(e,t,n,r,i){var o={}
|
|
12630
12631
|
return Object.keys(r).forEach((function(e){o[e]=r[e]})),o.enumerable=!!o.enumerable,o.configurable=!!o.configurable,("value"in o||o.initializer)&&(o.writable=!0),o=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),o),i&&void 0!==o.initializer&&(o.value=o.initializer?o.initializer.call(i):void 0,o.initializer=void 0),void 0===o.initializer&&(Object.defineProperty(e,t,o),o=null),o}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
|
|
@@ -20865,4 +20866,4 @@ if(!i)return v(e)
|
|
|
20865
20866
|
if(!b(i)||v(i).isOlderThan(4)){var o=v(e).getStackTop()
|
|
20866
20867
|
y(i,new p(o.client,c.a.clone(o.scope)))}return v(i)}catch(t){return v(e)}}(e):v(e)}function b(e){return!!(e&&e.__SENTRY__&&e.__SENTRY__.hub)}function v(e){return e&&e.__SENTRY__&&e.__SENTRY__.hub||(e.__SENTRY__=e.__SENTRY__||{},e.__SENTRY__.hub=new p),e.__SENTRY__.hub}function y(e,t){return!!e&&(e.__SENTRY__=e.__SENTRY__||{},e.__SENTRY__.hub=t,!0)}}]])
|
|
20867
20868
|
|
|
20868
|
-
//# sourceMappingURL=vendor.min-
|
|
20869
|
+
//# sourceMappingURL=vendor.min-be0129c9c6897c9f10425e2402881d77.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// # Total Members Helper
|
|
2
|
+
// Usage: `{{total_members}}`
|
|
3
|
+
|
|
4
|
+
const {SafeString} = require('../services/handlebars');
|
|
5
|
+
const {memberCountRounding, getMemberStats} = require('../utils/member-count');
|
|
6
|
+
|
|
7
|
+
module.exports = async function total_members () { //eslint-disable-line
|
|
8
|
+
if (this.total) {
|
|
9
|
+
return new SafeString(memberCountRounding(this.total));
|
|
10
|
+
} else {
|
|
11
|
+
let memberStats = await getMemberStats();
|
|
12
|
+
const {total} = memberStats;
|
|
13
|
+
return new SafeString(total > 0 ? memberCountRounding(total) : 0);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports.async = true;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// {{total_paid_members}} helper
|
|
2
|
+
|
|
3
|
+
const {SafeString} = require('../services/handlebars');
|
|
4
|
+
const {memberCountRounding, getMemberStats} = require('../utils/member-count');
|
|
5
|
+
|
|
6
|
+
module.exports = async function total_paid_members () { //eslint-disable-line
|
|
7
|
+
if (this.paid) {
|
|
8
|
+
return new SafeString(memberCountRounding(this.paid));
|
|
9
|
+
} else {
|
|
10
|
+
let memberStats = await getMemberStats();
|
|
11
|
+
const {paid} = memberStats;
|
|
12
|
+
return new SafeString(paid > 0 ? memberCountRounding(paid) : 0);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports.async = true;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const humanNumber = require('human-number');
|
|
2
|
+
const {api} = require('../services/proxy');
|
|
3
|
+
|
|
4
|
+
async function getMemberStats() {
|
|
5
|
+
let memberStats = this.data || await api.stats.memberCountHistory.query();
|
|
6
|
+
const {free, paid, comped} = memberStats.meta.totals;
|
|
7
|
+
let total = free + paid + comped;
|
|
8
|
+
return {free, paid, comped, total};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const numberWithCommas = (n) => {
|
|
12
|
+
return n.toLocaleString();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const rounding = (n, roundTo) => {
|
|
16
|
+
return Math.floor(n / roundTo) * roundTo;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Rounding https://github.com/TryGhost/Team/issues/1667
|
|
20
|
+
const memberCountRounding = (memberCount) => {
|
|
21
|
+
if (memberCount <= 50) {
|
|
22
|
+
return memberCount;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (memberCount > 50 && memberCount <= 100) {
|
|
26
|
+
return `${numberWithCommas(rounding(memberCount, 10))}+`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (memberCount > 100 && memberCount <= 1000) {
|
|
30
|
+
return `${numberWithCommas(rounding(memberCount, 50))}+`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (memberCount > 1000 && memberCount <= 10000) {
|
|
34
|
+
return `${numberWithCommas(rounding(memberCount, 100))}+`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (memberCount > 10000 && memberCount <= 100000) {
|
|
38
|
+
return `${numberWithCommas(rounding(memberCount, 1000))}+`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (memberCount > 100000 && memberCount <= 1000000) {
|
|
42
|
+
return `${humanNumber(rounding(memberCount, 10000)).toLowerCase()}+`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (memberCount > 1000000) {
|
|
46
|
+
return `${humanNumber(rounding(memberCount, 100000)).toLowerCase()}+`;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = {memberCountRounding, getMemberStats};
|
|
@@ -13,7 +13,8 @@ function corsOptionsDelegate(req, callback) {
|
|
|
13
13
|
const origin = req.header('Origin');
|
|
14
14
|
const corsOptions = {
|
|
15
15
|
origin: false, // disallow cross-origin requests by default
|
|
16
|
-
credentials: true // required to allow admin-client to login to private sites
|
|
16
|
+
credentials: true, // required to allow admin-client to login to private sites
|
|
17
|
+
maxAge: config.get('caching:cors:maxAge')
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
if (!origin || origin === 'null') {
|
|
@@ -16,8 +16,8 @@ module.exports = {
|
|
|
16
16
|
permissions: true,
|
|
17
17
|
async query(frame) {
|
|
18
18
|
const offers = await offersService.api.listOffers(frame.options);
|
|
19
|
-
|
|
20
|
-
offers
|
|
19
|
+
return {
|
|
20
|
+
data: offers
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
},
|
|
@@ -33,8 +33,8 @@ module.exports = {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
return {
|
|
37
|
+
data: [offer]
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -57,8 +57,8 @@ module.exports = {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
return {
|
|
61
|
+
data: [offer]
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
},
|
|
@@ -70,8 +70,8 @@ module.exports = {
|
|
|
70
70
|
},
|
|
71
71
|
async query(frame) {
|
|
72
72
|
const offer = await offersService.api.createOffer(frame.data.offers[0]);
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
return {
|
|
74
|
+
data: [offer]
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -2,17 +2,15 @@ const Promise = require('bluebird');
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const models = require('../../models');
|
|
4
4
|
const routeSettings = require('../../services/route-settings');
|
|
5
|
-
const tpl = require('@tryghost/tpl');
|
|
6
5
|
const {BadRequestError} = require('@tryghost/errors');
|
|
7
6
|
const settingsService = require('../../services/settings/settings-service');
|
|
8
7
|
const membersService = require('../../services/members');
|
|
9
8
|
const stripeService = require('../../services/stripe');
|
|
10
|
-
|
|
9
|
+
const tpl = require('@tryghost/tpl');
|
|
11
10
|
const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
|
|
12
11
|
|
|
13
12
|
const messages = {
|
|
14
13
|
failedSendingEmail: 'Failed Sending Email'
|
|
15
|
-
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
async function getStripeConnectData(frame) {
|
|
@@ -59,6 +57,65 @@ module.exports = {
|
|
|
59
57
|
}
|
|
60
58
|
},
|
|
61
59
|
|
|
60
|
+
verifyKeyUpdate: {
|
|
61
|
+
headers: {
|
|
62
|
+
cacheInvalidate: true
|
|
63
|
+
},
|
|
64
|
+
permissions: {
|
|
65
|
+
method: 'edit'
|
|
66
|
+
},
|
|
67
|
+
data: [
|
|
68
|
+
'token'
|
|
69
|
+
],
|
|
70
|
+
async query(frame) {
|
|
71
|
+
await settingsBREADService.verifyKeyUpdate(frame.data.token);
|
|
72
|
+
|
|
73
|
+
// We need to return all settings here, because we have calculated settings that might change
|
|
74
|
+
const browse = await settingsBREADService.browse(frame.options.context);
|
|
75
|
+
|
|
76
|
+
return browse;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated
|
|
82
|
+
*/
|
|
83
|
+
updateMembersEmail: {
|
|
84
|
+
statusCode: 204,
|
|
85
|
+
permissions: {
|
|
86
|
+
method: 'edit'
|
|
87
|
+
},
|
|
88
|
+
data: [
|
|
89
|
+
'email',
|
|
90
|
+
'type'
|
|
91
|
+
],
|
|
92
|
+
async query(frame) {
|
|
93
|
+
const {email, type} = frame.data;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Mapped internally to the newer method of changing emails
|
|
97
|
+
const actionToKeyMapping = {
|
|
98
|
+
supportAddressUpdate: 'members_support_address'
|
|
99
|
+
};
|
|
100
|
+
const edit = {
|
|
101
|
+
key: actionToKeyMapping[type],
|
|
102
|
+
value: email
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
await settingsBREADService.edit([edit], frame.options, null);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
throw new BadRequestError({
|
|
108
|
+
err,
|
|
109
|
+
message: tpl(messages.failedSendingEmail)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @todo can get removed, since this is moved to verifyKeyUpdate
|
|
117
|
+
* @deprecated: keep to not break existing email verification links, but remove after 1 - 2 releases
|
|
118
|
+
*/
|
|
62
119
|
validateMembersEmailUpdate: {
|
|
63
120
|
options: [
|
|
64
121
|
'token',
|
|
@@ -108,33 +165,6 @@ module.exports = {
|
|
|
108
165
|
}
|
|
109
166
|
},
|
|
110
167
|
|
|
111
|
-
updateMembersEmail: {
|
|
112
|
-
statusCode: 204,
|
|
113
|
-
permissions: {
|
|
114
|
-
method: 'edit'
|
|
115
|
-
},
|
|
116
|
-
data: [
|
|
117
|
-
'email',
|
|
118
|
-
'type'
|
|
119
|
-
],
|
|
120
|
-
async query(frame) {
|
|
121
|
-
const {email, type} = frame.data;
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
// Send magic link to update fromAddress
|
|
125
|
-
await membersService.settings.sendEmailAddressUpdateMagicLink({
|
|
126
|
-
email,
|
|
127
|
-
type
|
|
128
|
-
});
|
|
129
|
-
} catch (err) {
|
|
130
|
-
throw new BadRequestError({
|
|
131
|
-
err,
|
|
132
|
-
message: tpl(messages.failedSendingEmail)
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
|
|
138
168
|
disconnectStripeConnectIntegration: {
|
|
139
169
|
statusCode: 204,
|
|
140
170
|
permissions: {
|
|
@@ -197,6 +227,8 @@ module.exports = {
|
|
|
197
227
|
|
|
198
228
|
// We need to return all settings here, because we have calculated settings that might change
|
|
199
229
|
const browse = await settingsBREADService.browse(frame.options.context);
|
|
230
|
+
browse.meta = result.meta || {};
|
|
231
|
+
|
|
200
232
|
return browse;
|
|
201
233
|
}
|
|
202
234
|
},
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const utils = require('../../../index');
|
|
2
|
+
|
|
3
|
+
module.exports = (model, frame) => {
|
|
4
|
+
// Offer data is already returned as json via members service
|
|
5
|
+
const jsonModel = model;
|
|
6
|
+
|
|
7
|
+
if (utils.isContentAPI(frame)) {
|
|
8
|
+
const serialized = {
|
|
9
|
+
id: jsonModel.id,
|
|
10
|
+
name: jsonModel.name,
|
|
11
|
+
display_title: jsonModel.display_title,
|
|
12
|
+
display_description: jsonModel.display_description,
|
|
13
|
+
type: jsonModel.type,
|
|
14
|
+
cadence: jsonModel.cadence,
|
|
15
|
+
amount: jsonModel.amount,
|
|
16
|
+
duration: jsonModel.duration,
|
|
17
|
+
duration_in_months: jsonModel.duration_in_months,
|
|
18
|
+
currency_restriction: jsonModel.currency_restriction,
|
|
19
|
+
currency: jsonModel.currency,
|
|
20
|
+
status: jsonModel.status,
|
|
21
|
+
tier: jsonModel.tier
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return serialized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return jsonModel;
|
|
28
|
+
};
|
|
@@ -52,7 +52,7 @@ function serializeSettings(models, apiConfig, frame) {
|
|
|
52
52
|
|
|
53
53
|
frame.response = {
|
|
54
54
|
settings: mappers.settings(filteredSettings),
|
|
55
|
-
meta: {}
|
|
55
|
+
meta: models.meta ?? {}
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
if (frame.options.group) {
|
|
@@ -89,6 +89,7 @@ module.exports = {
|
|
|
89
89
|
browse: serializeSettings,
|
|
90
90
|
read: serializeSettings,
|
|
91
91
|
edit: serializeSettings,
|
|
92
|
+
verifyKeyUpdate: serializeSettings,
|
|
92
93
|
|
|
93
94
|
download: serializeData,
|
|
94
95
|
upload: serializeData,
|
|
@@ -2,11 +2,12 @@ const Promise = require('bluebird');
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const {ValidationError, BadRequestError} = require('@tryghost/errors');
|
|
4
4
|
const validator = require('@tryghost/validator');
|
|
5
|
+
const tpl = require('@tryghost/tpl');
|
|
5
6
|
|
|
6
7
|
const messages = {
|
|
7
8
|
invalidEmailReceived: 'Please send a valid email',
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
invalidEmailValueReceived: 'Please enter a valid email address.',
|
|
10
|
+
invalidEmailTypeReceived: 'Invalid email type received'
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
module.exports = {
|
|
@@ -22,6 +23,10 @@ module.exports = {
|
|
|
22
23
|
'secondary_navigation'
|
|
23
24
|
];
|
|
24
25
|
|
|
26
|
+
const emailTypeSettings = [
|
|
27
|
+
'members_support_address'
|
|
28
|
+
];
|
|
29
|
+
|
|
25
30
|
if (arrayTypeSettings.includes(setting.key)) {
|
|
26
31
|
const typeError = new ValidationError({
|
|
27
32
|
message: `Value in ${setting.key} should be an array.`,
|
|
@@ -43,6 +48,18 @@ module.exports = {
|
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
|
|
52
|
+
if (emailTypeSettings.includes(setting.key)) {
|
|
53
|
+
const email = setting.value;
|
|
54
|
+
|
|
55
|
+
if (typeof email !== 'string' || (!validator.isEmail(email) && email !== 'noreply')) {
|
|
56
|
+
const typeError = new ValidationError({
|
|
57
|
+
message: tpl(messages.invalidEmailValueReceived),
|
|
58
|
+
property: setting.key
|
|
59
|
+
});
|
|
60
|
+
errors.push(typeError);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
});
|
|
47
64
|
|
|
48
65
|
// Prevent setting icon to the resized one when sending all settings received from browse again in the edit endpoint
|
|
@@ -56,6 +73,9 @@ module.exports = {
|
|
|
56
73
|
}
|
|
57
74
|
},
|
|
58
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @deprecated
|
|
78
|
+
*/
|
|
59
79
|
updateMembersEmail(apiConfig, frame) {
|
|
60
80
|
const {email, type} = frame.data;
|
|
61
81
|
|
|
@@ -23,7 +23,7 @@ function getExpressSessionMiddleware() {
|
|
|
23
23
|
maxAge: constants.SIX_MONTH_MS,
|
|
24
24
|
httpOnly: true,
|
|
25
25
|
path: urlUtils.getSubdir() + '/ghost',
|
|
26
|
-
sameSite: 'none',
|
|
26
|
+
sameSite: urlUtils.isSSL(config.get('url')) ? 'none' : 'lax',
|
|
27
27
|
secure: urlUtils.isSSL(config.get('url'))
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const {
|
|
2
|
+
const {ValidationError} = require('@tryghost/errors');
|
|
3
3
|
|
|
4
4
|
class SingleUseTokenProvider {
|
|
5
5
|
/**
|
|
@@ -41,7 +41,7 @@ class SingleUseTokenProvider {
|
|
|
41
41
|
const model = await this.model.findOne({token});
|
|
42
42
|
|
|
43
43
|
if (!model) {
|
|
44
|
-
throw new
|
|
44
|
+
throw new ValidationError({
|
|
45
45
|
message: 'Invalid token provided'
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -51,7 +51,7 @@ class SingleUseTokenProvider {
|
|
|
51
51
|
const tokenLifetimeMilliseconds = Date.now() - createdAtEpoch;
|
|
52
52
|
|
|
53
53
|
if (tokenLifetimeMilliseconds > this.validity) {
|
|
54
|
-
throw new
|
|
54
|
+
throw new ValidationError({
|
|
55
55
|
message: 'Token expired'
|
|
56
56
|
});
|
|
57
57
|
}
|
|
@@ -154,11 +154,14 @@ class MembersConfigProvider {
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
getSigninURL(token, type) {
|
|
157
|
+
getSigninURL(token, type, referrer) {
|
|
158
158
|
const siteUrl = this._urlUtils.urlFor({relativeUrl: '/members/'}, true);
|
|
159
159
|
const signinURL = new URL(siteUrl);
|
|
160
160
|
signinURL.searchParams.set('token', token);
|
|
161
161
|
signinURL.searchParams.set('action', type);
|
|
162
|
+
if (referrer) {
|
|
163
|
+
signinURL.searchParams.set('r', referrer);
|
|
164
|
+
}
|
|
162
165
|
return signinURL.toString();
|
|
163
166
|
}
|
|
164
167
|
}
|
|
@@ -143,8 +143,8 @@ const createSessionFromMagicLink = async function (req, res, next) {
|
|
|
143
143
|
// req.query is a plain object, copy it to a URLSearchParams object so we can call toString()
|
|
144
144
|
const searchParams = new URLSearchParams('');
|
|
145
145
|
Object.keys(req.query).forEach((param) => {
|
|
146
|
-
// don't copy the token
|
|
147
|
-
if (param !== 'token') {
|
|
146
|
+
// don't copy the "token" or "r" params
|
|
147
|
+
if (param !== 'token' && param !== 'r') {
|
|
148
148
|
searchParams.set(param, req.query[param]);
|
|
149
149
|
}
|
|
150
150
|
});
|
|
@@ -182,6 +182,18 @@ const createSessionFromMagicLink = async function (req, res, next) {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
if (action === 'signin') {
|
|
186
|
+
const referrer = req.query.r;
|
|
187
|
+
const siteUrl = urlUtils.getSiteUrl();
|
|
188
|
+
|
|
189
|
+
if (referrer && referrer.startsWith(siteUrl)) {
|
|
190
|
+
const redirectUrl = new URL(referrer);
|
|
191
|
+
redirectUrl.searchParams.set('success', true);
|
|
192
|
+
redirectUrl.searchParams.set('action', 'signin');
|
|
193
|
+
return res.redirect(redirectUrl.pathname + redirectUrl.search);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
// Do a standard 302 redirect to the homepage, with success=true
|
|
186
198
|
searchParams.set('success', true);
|
|
187
199
|
res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
|