ghost 5.17.1 → 5.18.0

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.
Files changed (122) hide show
  1. package/README.md +2 -2
  2. package/components/tryghost-adapter-manager-5.18.0.tgz +0 -0
  3. package/components/{tryghost-api-framework-5.17.1.tgz → tryghost-api-framework-5.18.0.tgz} +0 -0
  4. package/components/{tryghost-api-version-compatibility-service-5.17.1.tgz → tryghost-api-version-compatibility-service-5.18.0.tgz} +0 -0
  5. package/components/tryghost-bootstrap-socket-5.18.0.tgz +0 -0
  6. package/components/tryghost-constants-5.18.0.tgz +0 -0
  7. package/components/tryghost-custom-theme-settings-service-5.18.0.tgz +0 -0
  8. package/components/tryghost-domain-events-5.18.0.tgz +0 -0
  9. package/components/tryghost-email-analytics-provider-mailgun-5.18.0.tgz +0 -0
  10. package/components/tryghost-email-analytics-service-5.18.0.tgz +0 -0
  11. package/components/tryghost-email-content-generator-5.18.0.tgz +0 -0
  12. package/components/tryghost-express-dynamic-redirects-5.18.0.tgz +0 -0
  13. package/components/tryghost-extract-api-key-5.18.0.tgz +0 -0
  14. package/components/tryghost-html-to-plaintext-5.18.0.tgz +0 -0
  15. package/components/tryghost-job-manager-5.18.0.tgz +0 -0
  16. package/components/{tryghost-link-redirects-5.17.1.tgz → tryghost-link-redirects-5.18.0.tgz} +0 -0
  17. package/components/tryghost-link-replacer-5.18.0.tgz +0 -0
  18. package/components/tryghost-link-tracking-5.18.0.tgz +0 -0
  19. package/components/tryghost-magic-link-5.18.0.tgz +0 -0
  20. package/components/tryghost-mailgun-client-5.18.0.tgz +0 -0
  21. package/components/tryghost-member-analytics-service-5.18.0.tgz +0 -0
  22. package/components/{tryghost-member-attribution-5.17.1.tgz → tryghost-member-attribution-5.18.0.tgz} +0 -0
  23. package/components/tryghost-member-events-5.18.0.tgz +0 -0
  24. package/components/tryghost-members-analytics-ingress-5.18.0.tgz +0 -0
  25. package/components/tryghost-members-api-5.18.0.tgz +0 -0
  26. package/components/tryghost-members-csv-5.18.0.tgz +0 -0
  27. package/components/tryghost-members-events-service-5.18.0.tgz +0 -0
  28. package/components/tryghost-members-importer-5.18.0.tgz +0 -0
  29. package/components/tryghost-members-offers-5.18.0.tgz +0 -0
  30. package/components/tryghost-members-payments-5.18.0.tgz +0 -0
  31. package/components/tryghost-members-ssr-5.18.0.tgz +0 -0
  32. package/components/tryghost-members-stripe-service-5.18.0.tgz +0 -0
  33. package/components/tryghost-minifier-5.18.0.tgz +0 -0
  34. package/components/tryghost-mw-api-version-mismatch-5.18.0.tgz +0 -0
  35. package/components/tryghost-mw-cache-control-5.18.0.tgz +0 -0
  36. package/components/{tryghost-mw-error-handler-5.17.1.tgz → tryghost-mw-error-handler-5.18.0.tgz} +0 -0
  37. package/components/tryghost-mw-session-from-token-5.18.0.tgz +0 -0
  38. package/components/tryghost-mw-update-user-last-seen-5.18.0.tgz +0 -0
  39. package/components/tryghost-mw-vhost-5.18.0.tgz +0 -0
  40. package/components/{tryghost-oembed-service-5.17.1.tgz → tryghost-oembed-service-5.18.0.tgz} +0 -0
  41. package/components/{tryghost-package-json-5.17.1.tgz → tryghost-package-json-5.18.0.tgz} +0 -0
  42. package/components/tryghost-referrers-5.18.0.tgz +0 -0
  43. package/components/tryghost-security-5.18.0.tgz +0 -0
  44. package/components/{tryghost-session-service-5.17.1.tgz → tryghost-session-service-5.18.0.tgz} +0 -0
  45. package/components/tryghost-settings-path-manager-5.18.0.tgz +0 -0
  46. package/components/tryghost-staff-service-5.18.0.tgz +0 -0
  47. package/components/tryghost-stats-service-5.18.0.tgz +0 -0
  48. package/components/{tryghost-update-check-service-5.17.1.tgz → tryghost-update-check-service-5.18.0.tgz} +0 -0
  49. package/components/tryghost-verification-trigger-5.18.0.tgz +0 -0
  50. package/components/{tryghost-version-notifications-data-service-5.17.1.tgz → tryghost-version-notifications-data-service-5.18.0.tgz} +0 -0
  51. package/content/themes/casper/assets/built/screen.css +1 -1
  52. package/content/themes/casper/assets/built/screen.css.map +1 -1
  53. package/content/themes/casper/assets/css/screen.css +18 -21
  54. package/content/themes/casper/package.json +1 -1
  55. package/core/built/admin/assets/{chunk.143.a252ca5afc89e52c049a.js → chunk.143.6d23a3157dae7a9c899d.js} +6 -6
  56. package/core/built/admin/assets/{chunk.174.37fefc669899f0fd0064.js → chunk.174.e997dfffceeaa0ce4636.js} +155 -154
  57. package/core/built/admin/assets/{chunk.178.23e9f243eba5320fe15b.js → chunk.178.52a9ca26217a593eda67.js} +4 -4
  58. package/core/built/admin/assets/{chunk.579.a9bccec4d650a7be727a.js → chunk.427.4483d5bbdaf2a65888ac.js} +8969 -8890
  59. package/core/built/admin/assets/{chunk.579.a9bccec4d650a7be727a.js.LICENSE.txt → chunk.427.4483d5bbdaf2a65888ac.js.LICENSE.txt} +0 -0
  60. package/core/built/admin/assets/{ghost-fa7772f8aa2522b3935efed71fa75619.js → ghost-7e6e9479705e7e772bb5ea3b6476cd52.js} +2801 -2758
  61. package/core/built/admin/assets/ghost-dark-a41f7645a406e0df78b7152f3f805e66.css +1 -0
  62. package/core/built/admin/assets/ghost-ff0bee94743aa886ce35305a5b46fac3.css +1 -0
  63. package/core/built/admin/assets/{vendor-733135cd6cbca8126c6fa223d63a5bf3.css → vendor-3e6947aa681f0fb82b193090e520dc73.css} +24 -92
  64. package/core/built/admin/assets/{vendor-325e038b8609f0979f6578cae7a87f9e.js → vendor-4da5d2584fbe1442e25e4271a5513f1c.js} +545 -546
  65. package/core/built/admin/index.html +7 -7
  66. package/core/frontend/public/ghost.min.css +1 -1
  67. package/core/frontend/web/middleware/static-theme.js +2 -0
  68. package/core/server/api/endpoints/member-signin-urls.js +1 -1
  69. package/core/server/api/endpoints/utils/serializers/input/posts.js +1 -6
  70. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +8 -15
  71. package/core/server/services/members/middleware.js +1 -0
  72. package/core/server/services/webhooks/serialize.js +1 -1
  73. package/core/server/web/admin/app.js +2 -0
  74. package/core/server/web/api/endpoints/admin/routes.js +1 -2
  75. package/core/server/web/members/app.js +9 -1
  76. package/core/server/web/shared/middleware/api/spam-prevention.js +65 -9
  77. package/core/server/web/shared/middleware/brute.js +8 -0
  78. package/core/shared/config/defaults.json +8 -2
  79. package/core/shared/labs.js +3 -3
  80. package/package.json +101 -101
  81. package/yarn.lock +4473 -1242
  82. package/components/tryghost-adapter-manager-5.17.1.tgz +0 -0
  83. package/components/tryghost-bootstrap-socket-5.17.1.tgz +0 -0
  84. package/components/tryghost-constants-5.17.1.tgz +0 -0
  85. package/components/tryghost-custom-theme-settings-service-5.17.1.tgz +0 -0
  86. package/components/tryghost-domain-events-5.17.1.tgz +0 -0
  87. package/components/tryghost-email-analytics-provider-mailgun-5.17.1.tgz +0 -0
  88. package/components/tryghost-email-analytics-service-5.17.1.tgz +0 -0
  89. package/components/tryghost-email-content-generator-5.17.1.tgz +0 -0
  90. package/components/tryghost-express-dynamic-redirects-5.17.1.tgz +0 -0
  91. package/components/tryghost-extract-api-key-5.17.1.tgz +0 -0
  92. package/components/tryghost-html-to-plaintext-5.17.1.tgz +0 -0
  93. package/components/tryghost-job-manager-5.17.1.tgz +0 -0
  94. package/components/tryghost-link-replacer-5.17.1.tgz +0 -0
  95. package/components/tryghost-link-tracking-5.17.1.tgz +0 -0
  96. package/components/tryghost-magic-link-5.17.1.tgz +0 -0
  97. package/components/tryghost-mailgun-client-5.17.1.tgz +0 -0
  98. package/components/tryghost-member-analytics-service-5.17.1.tgz +0 -0
  99. package/components/tryghost-member-events-5.17.1.tgz +0 -0
  100. package/components/tryghost-members-analytics-ingress-5.17.1.tgz +0 -0
  101. package/components/tryghost-members-api-5.17.1.tgz +0 -0
  102. package/components/tryghost-members-csv-5.17.1.tgz +0 -0
  103. package/components/tryghost-members-events-service-5.17.1.tgz +0 -0
  104. package/components/tryghost-members-importer-5.17.1.tgz +0 -0
  105. package/components/tryghost-members-offers-5.17.1.tgz +0 -0
  106. package/components/tryghost-members-payments-5.17.1.tgz +0 -0
  107. package/components/tryghost-members-ssr-5.17.1.tgz +0 -0
  108. package/components/tryghost-members-stripe-service-5.17.1.tgz +0 -0
  109. package/components/tryghost-minifier-5.17.1.tgz +0 -0
  110. package/components/tryghost-mw-api-version-mismatch-5.17.1.tgz +0 -0
  111. package/components/tryghost-mw-cache-control-5.17.1.tgz +0 -0
  112. package/components/tryghost-mw-session-from-token-5.17.1.tgz +0 -0
  113. package/components/tryghost-mw-update-user-last-seen-5.17.1.tgz +0 -0
  114. package/components/tryghost-mw-vhost-5.17.1.tgz +0 -0
  115. package/components/tryghost-referrers-5.17.1.tgz +0 -0
  116. package/components/tryghost-security-5.17.1.tgz +0 -0
  117. package/components/tryghost-settings-path-manager-5.17.1.tgz +0 -0
  118. package/components/tryghost-staff-service-5.17.1.tgz +0 -0
  119. package/components/tryghost-stats-service-5.17.1.tgz +0 -0
  120. package/components/tryghost-verification-trigger-5.17.1.tgz +0 -0
  121. package/core/built/admin/assets/ghost-597fb8e8b1b91dd0ac4d9f2d75bd67fb.css +0 -1
  122. package/core/built/admin/assets/ghost-dark-e4ccecd9903d35d360d71fe859cbb3bf.css +0 -1
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.17%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.18%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -36,8 +36,8 @@
36
36
  }
37
37
  </style>
38
38
 
39
- <link integrity="" rel="stylesheet" href="assets/vendor-733135cd6cbca8126c6fa223d63a5bf3.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-597fb8e8b1b91dd0ac4d9f2d75bd67fb.css" title="light">
39
+ <link integrity="" rel="stylesheet" href="assets/vendor-3e6947aa681f0fb82b193090e520dc73.css">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-ff0bee94743aa886ce35305a5b46fac3.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-325e038b8609f0979f6578cae7a87f9e.js"></script>
60
- <script src="assets/chunk.579.a9bccec4d650a7be727a.js"></script>
61
- <script src="assets/chunk.143.a252ca5afc89e52c049a.js"></script>
62
- <script src="assets/ghost-fa7772f8aa2522b3935efed71fa75619.js"></script>
59
+ <script src="assets/vendor-4da5d2584fbe1442e25e4271a5513f1c.js"></script>
60
+ <script src="assets/chunk.427.4483d5bbdaf2a65888ac.js"></script>
61
+ <script src="assets/chunk.143.6d23a3157dae7a9c899d.js"></script>
62
+ <script src="assets/ghost-7e6e9479705e7e772bb5ea3b6476cd52.js"></script>
63
63
  </body>
64
64
  </html>
@@ -1 +1 @@
1
- /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.black{color:#15171a}.darkgrey{color:#394047}.midgrey{color:#7c8b9a}.lightgrey{color:#ced4d9}.blue{color:#14b8ff}.red{color:#f50b23}.orange{color:#ffb41f}.green{color:#30cf43}.darkgrey-hover:hover{color:#394047}.midgrey-hover:hover{color:#7c8b9a}.lightgrey-hover:hover{color:#ced4d9}.blue-hover:hover{color:#14b8ff}.red-hover:hover{color:#f50b23}.orange-hover:hover{color:#ffb41f}.green-hover:hover{color:#30cf43}*,:after,:before{box-sizing:border-box}html{overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;line-height:1.65;letter-spacing:.2px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{width:100%;height:100%}body{overflow:auto;overflow-x:hidden;color:#343f44;font-size:1.4rem}.gh-view{-ms-flex-positive:1;flex-grow:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}h1,h2{margin:0 0 .3em;color:#343f44;line-height:1.15em;text-rendering:optimizeLegibility;text-indent:-1px;font-size:2.9rem}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{display:block;padding:10px 12px;width:100%;height:40px;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;font-size:1.6rem;line-height:1em;font-weight:300;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;transition:border-color .15s linear;-webkit-appearance:none}.gh-input:focus{outline:0;border-color:#b4cbda}.gh-btn{display:inline-block;outline:none;border:1px solid #d6e3eb;color:#829aa8;text-decoration:none!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;fill:#829aa8;border-radius:5px;transition:all .2s ease;-webkit-font-smoothing:subpixel-antialiased}.gh-btn span{display:block;padding:0 12px;height:33px;font-size:1.3rem;line-height:33px;font-weight:400;text-align:center;letter-spacing:.2px;border-radius:4px}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#3da1d6,#2288bf);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{position:relative;display:block}.gh-input-icon svg{position:absolute;top:50%;left:10px;z-index:2;height:14px;width:auto;fill:color(var(--midgrey) l(15%));transform:translateY(-7px)}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;overflow:hidden;height:100%}.gh-viewport{overflow:hidden;max-height:100%}.gh-main,.gh-viewport{-ms-flex-positive:1;flex-grow:1;display:-ms-flexbox;display:flex}.gh-main{position:relative;background:#fff;overflow-y:auto}.gh-flow{overflow-y:auto;min-height:100%;background:linear-gradient(315deg,#efefef,#fff)}.gh-flow,.gh-flow-content-wrap{flex-grow:1;display:flex;flex-direction:column}.gh-flow-content-wrap{flex-shrink:0;justify-content:center;align-items:center;margin:0 24px;padding-bottom:8vh}.gh-flow-content-wrap .site-icon{width:70px;height:70px;border-radius:3px}.gh-flow-content{display:flex;flex-direction:column;max-width:520px;width:100%;margin:4rem 0 6rem;padding:40px;background:#fff;color:var(--darkgrey);font-size:1.9rem;line-height:1.5em;font-weight:300;border-radius:3px;box-shadow:0 2.8px 2.2px rgba(0,0,0,.02),0 6.7px 5.3px rgba(0,0,0,.02),0 12.5px 10px rgba(0,0,0,.02),0 22.3px 17.9px rgba(0,0,0,.03),0 41.8px 33.4px rgba(0,0,0,.03),0 100px 80px rgba(0,0,0,.05)}.gh-flow-content.unsubscribe{align-items:center;justify-content:center;max-width:560px;min-height:200px;margin:4rem 0;text-align:center}@media (max-width:500px){.gh-flow-content{padding:0;background:transparent;box-shadow:none}}.gh-flow-content header{display:flex;flex-direction:column;align-items:center}.gh-flow-content h1{margin-bottom:24px;color:#15171a;font-size:4.1rem;font-weight:700;line-height:1.15em}.gh-flow-content.unsubscribe h1{font-size:3.2rem}@media (max-width:600px){.gh-flow-content.unsubscribe h1,.gh-flow-content h1{font-size:6vw}}.gh-flow-content.unsubscribe p{margin:0 0 .4em;color:#394047;font-size:1.8rem}@media (max-width:500px){.gh-flow-content.unsubscribe p{font-size:1.6rem;line-height:1.5}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{position:relative;margin-bottom:2.5rem}.gh-flow-content .form-group.error .gh-input{border-color:#f50b23;box-shadow:0 0 0 3px rgba(239,24,24,.15)}.gh-flow-content .main-error{position:absolute;right:0;margin:0;color:#7c8b9a;font-size:1.35rem;font-weight:400;text-align:center;user-select:text}.gh-flow-em{font-weight:500}.unsubscribe-footer{text-align:center;font-size:1.5rem}@media (max-width:500px){.unsubscribe-footer{padding:0 24px;font-size:1.4rem;line-height:1.4em}}.unsubscribe-footer p{color:#7c8b9a;margin:0 0 .4rem}.unsubscribe-footer a{color:#15171a;text-decoration:none}.unsubscribe-footer a:hover{text-decoration:underline}.gh-signin{margin-bottom:1.5rem}.gh-signin .gh-input,.gh-signin .gh-input:-webkit-autofill:first-line{height:54px;padding:12px 16px;font-size:1.8rem;border-radius:8px}.gh-signin .gh-input::placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input::-webkit-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input:-ms-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input::-moz-placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input:focus{border-color:#30cf43;box-shadow:0 0 0 3px rgba(26,170,96,.15)}.gh-signin .gh-btn{margin:0;width:100%;height:54px;max-width:unset;margin-top:32px;background:#15171a;font-weight:300;line-height:54px;border-radius:8px;transition:all .4s ease;-webkit-font-smoothing:subpixel-antialiased}.gh-signin .gh-btn span{color:#fff;font-size:1.8rem}.error-content{flex-grow:1;justify-content:center;user-select:text;padding:8vw}.error-content,.error-details{display:flex;align-items:center}.error-details{margin-bottom:4rem}.error-ghost{margin:15px;height:115px}@media (max-width:630px){.error-ghost{display:none}}.error-code{margin:0;color:#c5d2d9;font-size:10vw;font-weight:600;line-height:.9em;letter-spacing:-.4vw}.error-description{margin:0;padding:0;border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em}.error-message{display:flex;flex-direction:column;margin:15px;align-items:center}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;transition:background .3s,color .3s;text-decoration:none}.error-stack{margin:1rem auto;padding:2rem;max-width:800px;background-color:hsla(0,0%,100%,.3)}.error-stack-list{margin:0;padding:0;list-style-type:none}.error-stack-list li{display:block}.error-stack-list li:before{content:"\21AA";display:inline-block;margin-right:.5rem;color:#bbb;font-size:1.2rem}.error-stack-function{font-weight:700}
1
+ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.black{color:#15171a}.darkgrey{color:#394047}.midgrey{color:#7c8b9a}.lightgrey{color:#ced4d9}.blue{color:#14b8ff}.red{color:#f50b23}.orange{color:#ffb41f}.green{color:#30cf43}.darkgrey-hover:hover{color:#394047}.midgrey-hover:hover{color:#7c8b9a}.lightgrey-hover:hover{color:#ced4d9}.blue-hover:hover{color:#14b8ff}.red-hover:hover{color:#f50b23}.orange-hover:hover{color:#ffb41f}.green-hover:hover{color:#30cf43}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px;text-rendering:optimizeLegibility}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{background:linear-gradient(315deg,#efefef,#fff);min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-content-wrap{display:flex;flex-direction:column;flex-grow:1}.gh-flow-content-wrap{align-items:center;flex-shrink:0;justify-content:center;margin:0 24px;padding-bottom:8vh}.gh-flow-content-wrap .site-icon{border-radius:3px;height:70px;width:70px}.gh-flow-content{background:#fff;border-radius:3px;box-shadow:0 2.8px 2.2px rgba(0,0,0,.02),0 6.7px 5.3px rgba(0,0,0,.02),0 12.5px 10px rgba(0,0,0,.02),0 22.3px 17.9px rgba(0,0,0,.03),0 41.8px 33.4px rgba(0,0,0,.03),0 100px 80px rgba(0,0,0,.05);color:var(--darkgrey);display:flex;flex-direction:column;font-size:1.9rem;font-weight:300;line-height:1.5em;margin:4rem 0 6rem;max-width:520px;padding:40px;width:100%}.gh-flow-content.unsubscribe{align-items:center;justify-content:center;margin:4rem 0;max-width:560px;min-height:200px;text-align:center}@media (max-width:500px){.gh-flow-content{background:transparent;box-shadow:none;padding:0}}.gh-flow-content header{align-items:center;display:flex;flex-direction:column}.gh-flow-content h1{color:#15171a;font-size:4.1rem;font-weight:700;line-height:1.15em;margin-bottom:24px}.gh-flow-content.unsubscribe h1{font-size:3.2rem}@media (max-width:600px){.gh-flow-content h1,.gh-flow-content.unsubscribe h1{font-size:6vw}}.gh-flow-content.unsubscribe p{color:#394047;font-size:1.8rem;margin:0 0 .4em}@media (max-width:500px){.gh-flow-content.unsubscribe p{font-size:1.6rem;line-height:1.5}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem;position:relative}.gh-flow-content .form-group.error .gh-input{border-color:#f50b23;box-shadow:0 0 0 3px rgba(239,24,24,.15)}.gh-flow-content .main-error{color:#7c8b9a;font-size:1.35rem;font-weight:400;margin:0;position:absolute;right:0;text-align:center;user-select:text}.gh-flow-em{font-weight:500}.unsubscribe-footer{font-size:1.5rem;text-align:center}@media (max-width:500px){.unsubscribe-footer{font-size:1.4rem;line-height:1.4em;padding:0 24px}}.unsubscribe-footer p{color:#7c8b9a;margin:0 0 .4rem}.unsubscribe-footer a{color:#15171a;text-decoration:none}.unsubscribe-footer a:hover{text-decoration:underline}.gh-signin{margin-bottom:1.5rem}.gh-signin .gh-input,.gh-signin .gh-input:-webkit-autofill:first-line{border-radius:8px;font-size:1.8rem;height:54px;padding:12px 16px}.gh-signin .gh-input::placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input::-webkit-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input:-ms-input-placeholder{color:#abb4be;font-weight:400}.gh-signin .gh-input::-moz-placeholder{color:#abb4be;font-weight:400;opacity:1}.gh-signin .gh-input:focus{border-color:#30cf43;box-shadow:0 0 0 3px rgba(26,170,96,.15)}.gh-signin .gh-btn{-webkit-font-smoothing:subpixel-antialiased;background:#15171a;border-radius:8px;font-weight:300;height:54px;line-height:54px;margin:0;margin-top:32px;max-width:unset;transition:all .4s ease;width:100%}.gh-signin .gh-btn span{color:#fff;font-size:1.8rem}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
@@ -32,6 +32,8 @@ function forwardToExpressStatic(req, res, next) {
32
32
 
33
33
  const configMaxAge = config.get('caching:theme:maxAge');
34
34
 
35
+ // @NOTE: the maxAge config passed below are in milliseconds and the config
36
+ // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
35
37
  express.static(themeEngine.getActive().path, {
36
38
  maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : constants.ONE_YEAR_MS
37
39
  }
@@ -22,7 +22,7 @@ module.exports = {
22
22
  });
23
23
  }
24
24
 
25
- const magicLink = await membersService.api.getMagicLink(model.get('email'));
25
+ const magicLink = await membersService.api.getMagicLink(model.get('email'), 'signin');
26
26
 
27
27
  return {
28
28
  member_id: model.get('id'),
@@ -6,7 +6,6 @@ const localUtils = require('../../index');
6
6
  const mobiledoc = require('../../../../../lib/mobiledoc');
7
7
  const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
8
8
  const clean = require('./utils/clean');
9
- const labs = require('../../../../../../shared/labs');
10
9
 
11
10
  function removeSourceFormats(frame) {
12
11
  if (frame.options.formats?.includes('mobiledoc') || frame.options.formats?.includes('lexical')) {
@@ -25,11 +24,7 @@ function defaultRelations(frame) {
25
24
  return false;
26
25
  }
27
26
 
28
- if (labs.isSet('emailClicks')) {
29
- frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.paid_conversions', 'count.clicks'];
30
- } else {
31
- frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.paid_conversions'];
32
- }
27
+ frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.paid_conversions', 'count.clicks'];
33
28
  }
34
29
 
35
30
  function setDefaultOrder(frame) {
@@ -10,7 +10,6 @@ const extraAttrs = require('../utils/extra-attrs');
10
10
  const gating = require('../utils/post-gating');
11
11
  const url = require('../utils/url');
12
12
 
13
- const labs = require('../../../../../../../shared/labs');
14
13
  const utils = require('../../../index');
15
14
 
16
15
  const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta;
@@ -110,20 +109,14 @@ module.exports = async (model, frame, options = {}) => {
110
109
  });
111
110
  }
112
111
 
113
- if (labs.isSet('emailClicks')) {
114
- if (jsonModel.email && jsonModel.count) {
115
- jsonModel.email.opened_count = Math.min(
116
- Math.max(
117
- jsonModel.email.opened_count || 0,
118
- jsonModel.count.clicks || 0
119
- ),
120
- jsonModel.email.email_count
121
- );
122
- }
123
- }
124
-
125
- if (!labs.isSet('memberAttribution') && !labs.isSet('emailClicks')) {
126
- delete jsonModel.count;
112
+ if (jsonModel.email && jsonModel.count) {
113
+ jsonModel.email.opened_count = Math.min(
114
+ Math.max(
115
+ jsonModel.email.opened_count || 0,
116
+ jsonModel.count.clicks || 0
117
+ ),
118
+ jsonModel.email.email_count
119
+ );
127
120
  }
128
121
 
129
122
  return jsonModel;
@@ -157,6 +157,7 @@ const createSessionFromMagicLink = async function (req, res, next) {
157
157
  try {
158
158
  const member = await membersService.ssr.exchangeTokenForSession(req, res);
159
159
  spamPrevention.membersAuth().reset(req.ip, `${member.email}login`);
160
+ // Note: don't reset 'member_login', or that would give an easy way around user enumeration by logging in to a manually created account
160
161
  const subscriptions = member && member.subscriptions || [];
161
162
 
162
163
  const action = req.query.action;
@@ -13,7 +13,7 @@ module.exports = (event, model) => {
13
13
  ops.push(() => {
14
14
  let frame = {options: {previous: false, context: {user: true}}};
15
15
 
16
- // NOTE: below options are lost in the during event processing, a more holistic approach would be
16
+ // @NOTE: below options are lost during event processing, a more holistic approach would be
17
17
  // to pass them somehow along with the model
18
18
  if (['posts', 'pages'].includes(docName)) {
19
19
  frame.options.formats = ['mobiledoc', 'html', 'plaintext'];
@@ -20,6 +20,8 @@ module.exports = function setupAdminApp() {
20
20
  // @NOTE: when we start working on HTTP/3 optimizations the immutable headers
21
21
  // produced below should be split into separate 'Cache-Control' entry.
22
22
  // For reference see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#validation_2
23
+ // @NOTE: the maxAge config passed below are in milliseconds and the config
24
+ // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
23
25
  adminApp.use('/assets', serveStatic(
24
26
  path.join(config.get('paths').adminAssets, 'assets'), {
25
27
  maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : constants.ONE_YEAR_MS,
@@ -3,7 +3,6 @@ const api = require('../../../../api').endpoints;
3
3
  const {http} = require('@tryghost/api-framework');
4
4
  const apiMw = require('../../middleware');
5
5
  const mw = require('./middleware');
6
- const labs = require('../../../../../shared/labs');
7
6
 
8
7
  const shared = require('../../../shared');
9
8
 
@@ -310,7 +309,7 @@ module.exports = function apiRoutes() {
310
309
  router.put('/newsletters/verifications/', mw.authAdminApi, http(api.newsletters.verifyPropertyUpdate));
311
310
  router.put('/newsletters/:id', mw.authAdminApi, http(api.newsletters.edit));
312
311
 
313
- router.get('/links', labs.enabledMiddleware('emailClicks'), mw.authAdminApi, http(api.links.browse));
312
+ router.get('/links', mw.authAdminApi, http(api.links.browse));
314
313
 
315
314
  return router;
316
315
  };
@@ -48,7 +48,15 @@ module.exports = function setupMembersApp() {
48
48
  membersApp.delete('/api/session', middleware.deleteSession);
49
49
 
50
50
  // NOTE: this is wrapped in a function to ensure we always go via the getter
51
- membersApp.post('/api/send-magic-link', bodyParser.json(), shared.middleware.brute.membersAuth, (req, res, next) => membersService.api.middleware.sendMagicLink(req, res, next));
51
+ membersApp.post(
52
+ '/api/send-magic-link',
53
+ bodyParser.json(),
54
+ // Prevent brute forcing email addresses (user enumeration)
55
+ shared.middleware.brute.membersAuthEnumeration,
56
+ // Prevent brute forcing passwords for the same email address
57
+ shared.middleware.brute.membersAuth,
58
+ (req, res, next) => membersService.api.middleware.sendMagicLink(req, res, next)
59
+ );
52
60
  membersApp.post('/api/create-stripe-checkout-session', (req, res, next) => membersService.api.middleware.createCheckoutSession(req, res, next));
53
61
  membersApp.post('/api/create-stripe-update-session', (req, res, next) => membersService.api.middleware.createCheckoutSetupSession(req, res, next));
54
62
  membersApp.put('/api/subscriptions/:id', (req, res, next) => membersService.api.middleware.updateSubscription(req, res, next));
@@ -5,7 +5,7 @@ const errors = require('@tryghost/errors');
5
5
  const config = require('../../../../../shared/config');
6
6
  const tpl = require('@tryghost/tpl');
7
7
  const logging = require('@tryghost/logging');
8
- const spam = config.get('spam') || {};
8
+ let spam = config.get('spam') || {};
9
9
 
10
10
  const messages = {
11
11
  forgottenPasswordEmail: {
@@ -22,13 +22,13 @@ const messages = {
22
22
  },
23
23
  tooManyAttempts: 'Too many attempts.'
24
24
  };
25
-
26
- const spamPrivateBlock = spam.private_block || {};
27
- const spamGlobalBlock = spam.global_block || {};
28
- const spamGlobalReset = spam.global_reset || {};
29
- const spamUserReset = spam.user_reset || {};
30
- const spamUserLogin = spam.user_login || {};
31
- const spamContentApiKey = spam.content_api_key || {};
25
+ let spamPrivateBlock = spam.private_block || {};
26
+ let spamGlobalBlock = spam.global_block || {};
27
+ let spamGlobalReset = spam.global_reset || {};
28
+ let spamUserReset = spam.user_reset || {};
29
+ let spamUserLogin = spam.user_login || {};
30
+ let spamMemberLogin = spam.member_login || {};
31
+ let spamContentApiKey = spam.content_api_key || {};
32
32
 
33
33
  let store;
34
34
  let memoryStore;
@@ -37,6 +37,7 @@ let globalResetInstance;
37
37
  let globalBlockInstance;
38
38
  let userLoginInstance;
39
39
  let membersAuthInstance;
40
+ let membersAuthEnumerationInstance;
40
41
  let userResetInstance;
41
42
  let contentApiKeyInstance;
42
43
 
@@ -152,6 +153,39 @@ const membersAuth = () => {
152
153
  return membersAuthInstance;
153
154
  };
154
155
 
156
+ /**
157
+ * This one should have higher limits because it checks across all email addresses
158
+ */
159
+ const membersAuthEnumeration = () => {
160
+ const ExpressBrute = require('express-brute');
161
+ const BruteKnex = require('brute-knex');
162
+ const db = require('../../../../data/db');
163
+
164
+ store = store || new BruteKnex({
165
+ tablename: 'brute',
166
+ createTable: false,
167
+ knex: db.knex
168
+ });
169
+
170
+ if (!membersAuthEnumerationInstance) {
171
+ membersAuthEnumerationInstance = new ExpressBrute(store,
172
+ extend({
173
+ attachResetToRequest: true,
174
+ failCallback(req, res, next, nextValidRequestDate) {
175
+ return next(new errors.TooManyRequestsError({
176
+ message: `Too many different sign-in attempts try again in ${moment(nextValidRequestDate).fromNow(true)}`,
177
+ context: tpl(messages.tooManySigninAttempts.context),
178
+ help: tpl(messages.tooManySigninAttempts.context)
179
+ }));
180
+ },
181
+ handleStoreError: handleStoreError
182
+ }, pick(spamMemberLogin, spamConfigKeys))
183
+ );
184
+ }
185
+
186
+ return membersAuthEnumerationInstance;
187
+ };
188
+
155
189
  // Stops login attempts for a user+IP pair with an increasing time period starting from 10 minutes
156
190
  // and rising to a week in a fibonnaci sequence
157
191
  // The user+IP count is reset when on successful login
@@ -281,7 +315,29 @@ module.exports = {
281
315
  globalReset: globalReset,
282
316
  userLogin: userLogin,
283
317
  membersAuth: membersAuth,
318
+ membersAuthEnumeration: membersAuthEnumeration,
284
319
  userReset: userReset,
285
320
  privateBlog: privateBlog,
286
- contentApiKey: contentApiKey
321
+ contentApiKey: contentApiKey,
322
+ reset: () => {
323
+ store = undefined;
324
+ memoryStore = undefined;
325
+ privateBlogInstance = undefined;
326
+ globalResetInstance = undefined;
327
+ globalBlockInstance = undefined;
328
+ userLoginInstance = undefined;
329
+ membersAuthInstance = undefined;
330
+ membersAuthEnumerationInstance = undefined;
331
+ userResetInstance = undefined;
332
+ contentApiKeyInstance = undefined;
333
+
334
+ spam = config.get('spam') || {};
335
+ spamPrivateBlock = spam.private_block || {};
336
+ spamGlobalBlock = spam.global_block || {};
337
+ spamGlobalReset = spam.global_reset || {};
338
+ spamUserReset = spam.user_reset || {};
339
+ spamUserLogin = spam.user_login || {};
340
+ spamMemberLogin = spam.member_login || {};
341
+ spamContentApiKey = spam.content_api_key || {};
342
+ }
287
343
  };
@@ -84,6 +84,7 @@ module.exports = {
84
84
  },
85
85
 
86
86
  /**
87
+ * Block too many password guesses for the same email address
87
88
  */
88
89
  membersAuth(req, res, next) {
89
90
  return spamPrevention.membersAuth().getMiddleware({
@@ -96,5 +97,12 @@ module.exports = {
96
97
  return _next();
97
98
  }
98
99
  })(req, res, next);
100
+ },
101
+
102
+ /**
103
+ * Blocks user enumeration
104
+ */
105
+ membersAuthEnumeration(req, res, next) {
106
+ return spamPrevention.membersAuthEnumeration().prevent(req, res, next);
99
107
  }
100
108
  };
@@ -99,6 +99,12 @@
99
99
  "maxWait": 86400000,
100
100
  "lifetime": 3600,
101
101
  "freeRetries": 99
102
+ },
103
+ "member_login": {
104
+ "minWait": 600000,
105
+ "maxWait": 43200000,
106
+ "lifetime": 43200,
107
+ "freeRetries": 8
102
108
  }
103
109
  },
104
110
  "caching": {
@@ -152,7 +158,7 @@
152
158
  },
153
159
  "portal": {
154
160
  "url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
155
- "version": "2.13"
161
+ "version": "2.14"
156
162
  },
157
163
  "sodoSearch": {
158
164
  "url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
@@ -162,7 +168,7 @@
162
168
  "comments": {
163
169
  "url": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/comments-ui.min.js",
164
170
  "styles": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/main.css",
165
- "version": "0.10.1"
171
+ "version": "0.10"
166
172
  },
167
173
  "editor": {
168
174
  "url": "https://unpkg.com/@tryghost/koenig-lexical@~{version}/dist/koenig-lexical.umd.js",
@@ -19,8 +19,7 @@ const GA_FEATURES = [
19
19
  'freeTrial',
20
20
  'compExpiring',
21
21
  'searchHelper',
22
- 'emailAlerts',
23
- 'emailClicks'
22
+ 'emailAlerts'
24
23
  ];
25
24
 
26
25
  // NOTE: this allowlist is meant to be used to filter out any unexpected
@@ -34,7 +33,8 @@ const ALPHA_FEATURES = [
34
33
  'urlCache',
35
34
  'beforeAfterCard',
36
35
  'sourceAttribution',
37
- 'lexicalEditor'
36
+ 'lexicalEditor',
37
+ 'exploreApp'
38
38
  ];
39
39
 
40
40
  module.exports.GA_KEYS = [...GA_FEATURES];