Skip to content

Commit

Permalink
🔒 Blocked spammy email domains in member signups (#22027)
Browse files Browse the repository at this point in the history
ref https://linear.app/ghost/issue/ONC-721
ref https://app.incident.io/ghost/incidents/132

- added a blocklist at the email domain level for free member signups
- for example, if `blocked-domain.com` is blocked,
`thomas@blocked-domain.com` cannot sign up as free member
- the blocklist is configurable: `"spam.blocked_email_domains":
["blocked-domain.com"]`
  • Loading branch information
sagzy authored Jan 20, 2025
1 parent e1f5ff1 commit 970741c
Show file tree
Hide file tree
Showing 71 changed files with 196 additions and 8 deletions.
1 change: 1 addition & 0 deletions apps/portal/src/utils/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function chooseBestErrorMessage(error, alreadyTranslatedDefaultMessage, t
t('Too many different sign-in attempts, try again in {{number}} days');
t('Failed to send magic link email');
t('This site only accepts paid members.');
t('This email domain is not accepted, try again with a different email address');
}
};

Expand Down
4 changes: 3 additions & 1 deletion ghost/core/core/server/services/members/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const memberAttributionService = require('../member-attribution');
const emailSuppressionList = require('../email-suppression-list');
const {t} = require('../i18n');
const sentry = require('../../../shared/sentry');
const sharedConfig = require('../../../shared/config');

const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
const MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE = 10 * 60 * 1000;
Expand Down Expand Up @@ -238,7 +239,8 @@ function createApiInstance(config) {
emailSuppressionList,
settingsCache,
sentry,
settingsHelpers
settingsHelpers,
config: sharedConfig
});

return membersApiInstance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const debug = require('@tryghost/debug')('services:newsletters');
const tpl = require('@tryghost/tpl');
const errors = require('@tryghost/errors');
const sentry = require('../../../shared/sentry');
const config = require('../../../shared/config');

const messages = {
nameAlreadyExists: 'A newsletter with the same name already exists',
Expand Down Expand Up @@ -86,7 +87,8 @@ class NewslettersService {
getText,
getHTML,
getSubject,
sentry
sentry,
config
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const logging = require('@tryghost/logging');
const MagicLink = require('@tryghost/magic-link');
const verifyEmailTemplate = require('./emails/verify-email');
const sentry = require('../../../shared/sentry');
const config = require('../../../shared/config');

const EMAIL_KEYS = ['members_support_address'];
const messages = {
Expand Down Expand Up @@ -82,7 +83,8 @@ class SettingsBREADService {
getText,
getHTML,
getSubject,
sentry
sentry,
config
});
}

Expand Down
3 changes: 2 additions & 1 deletion ghost/core/core/shared/config/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@
"maxWait": 360000,
"lifetime": 3600,
"freeRetries": 10
}
},
"blocked_email_domains": []
},
"caching": {
"frontend": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,21 @@ Object {
],
}
`;

exports[`sendMagicLink blocks signups from blocked email domains 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": null,
"details": null,
"ghostErrorCode": null,
"help": null,
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "This email domain is not accepted, try again with a different email address",
"property": null,
"type": "BadRequestError",
},
],
}
`;
20 changes: 20 additions & 0 deletions ghost/core/test/e2e-api/members/send-magic-link.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const settingsCache = require('../../../core/shared/settings-cache');
const DomainEvents = require('@tryghost/domain-events');
const {anyErrorId} = matchers;
const spamPrevention = require('../../../core/server/web/shared/middleware/api/spam-prevention');
const configUtils = require('../../utils/configUtils');

let membersAgent, membersService;

Expand All @@ -29,6 +30,7 @@ describe('sendMagicLink', function () {

afterEach(function () {
mockManager.restore();
configUtils.restore();
});

it('Errors when passed multiple emails', async function () {
Expand Down Expand Up @@ -285,4 +287,22 @@ describe('sendMagicLink', function () {
}
});
});

it('blocks signups from blocked email domains', async function () {
configUtils.set('spam:blocked_email_domains', ['blocked-domain.com']);

const email = 'this-member-does-not-exist@blocked-domain.com';
await membersAgent.post('/api/send-magic-link')
.body({
email,
emailType: 'signup'
})
.expectStatus(400)
.matchBodySnapshot({
errors: [{
id: anyErrorId,
message: 'This email domain is not accepted, try again with a different email address'
}]
});
});
});
1 change: 1 addition & 0 deletions ghost/i18n/locales/af/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Hierdie webwerf is slegs op uitnodiging, kontak die eienaar vir toegang.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/ar/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": ".حدث خطأ أثناء الاشتراك، يرجى المحاولة مرة أخرى",
"There was an error processing your payment. Please try again.": ".حدث خطأ أثناء معالجة دفعك، يرجى المحاولة مرة أخرى",
"There was an error sending the email, please try again": ".حدث خطأ أثناء إرسال البريد الاكتروني، يرجى المحاولة مرة أخرى",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "هذا الموقع للمشتركين فقط، تواصل مع ادارة الموقع للحصول على اشتراك.",
"This site is not accepting payments at the moment.": "هذا الموقع لا يقبل المدفوعات في الوقت الحالي",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/bg/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "Възникна грешка при продължаването на абонамента ви, опитайте отново.",
"There was an error processing your payment. Please try again.": "Възникна грешка при обработката на вашето плащане. Моля, опитайте отново.",
"There was an error sending the email, please try again": "Възникна грешка при изпращане на имейл, опитайте отново",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Сайтът е само с покани. Свържете се със собственика за да получите достъп.",
"This site is not accepting payments at the moment.": "В момента сайтът не приема плащания.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/bn/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "এই সাইটটি কেবল আমন্ত্রণের মাধ্যমে, প্রবেশাধিকার পেতে মালিকের সাথে যোগাযোগ করুন।",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/bs/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Ova je stranica samo na poziv, kontaktiraj vlasnika za pristup.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/ca/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Aquest llog és només per invitació, contacta amb el propietari per obtenir accés.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
"This comment has been hidden.": "Text for a comment thas was hidden",
"This comment has been removed.": "Text for a comment thas was removed",
"This email address will not be used.": "This is in the footer of signup verification emails, and comes right after 'If you did not make this request, you can simply delete this message.'",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "A message on the member login screen indicating that a site is not-open to public signups",
"This site is not accepting payments at the moment.": "An error message shown when a tips or donations link is opened but the site has donations disabled",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/cs/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "Při zpracování vaší platby došlo k chybě. Zkuste to prosím znovu.",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Tento web je pouze pro pozvané, kontaktujte provozovatele pro přístup.",
"This site is not accepting payments at the moment.": "Tento web momentálně nepřijímá platby.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/da/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "Der opstod en fejl under forlængelsen af dit abonnement, prøv venligst igen.",
"There was an error processing your payment. Please try again.": "Der opstod en fejl under behandlingen af din betaling. Prøv venligst igen.",
"There was an error sending the email, please try again": "Der opstod en fejl under afsendelse af e-mailen, prøv venligst igen.",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Denne sider kræver at du skal være inviteret. Kontakt ejeres for at få adgang.",
"This site is not accepting payments at the moment.": "Denne side accepterer ikke betalinger i øjeblikket.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/de-CH/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Der Zugang zu diesem Inhalt ist eingeschränkt. Bitte kontaktieren Sie uns, wenn Sie Zugang wünschen.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/de/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "Beim Erneuern deines Abonnements ist ein Fehler aufgetreten. Bitte versuche es erneut.",
"There was an error processing your payment. Please try again.": "Bei der Verarbeitung deiner Zahlung gab es einen Fehler. Bitte versuche es noch einmal.",
"There was an error sending the email, please try again": "Beim Versand der E-Mail ist ein Fehler aufgetreten. Bitte versuche es erneut.",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Für diese Seite benötigst du eine Einladung. Bitte kontaktiere den Inhaber.",
"This site is not accepting payments at the moment.": "Diese Website nimmt zur Zeit keine Zahlungen entgegen.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/el/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Αυτός ο ιστότοπος είναι μόνο με πρόσκληση, επικοινωνήστε με τον ιδιοκτήτη για πρόσβαση.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/en/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/eo/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Ĉi tiu retejo estas nur por invitiĝuloj, kontaktu la proprietulo por alireblo.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/es/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "Hubo un error en continuar la suscripción, inténtalo de nuevo por favor.",
"There was an error processing your payment. Please try again.": "Hubo un error procesando tu pago. Intentalo de nuevvo por favor.",
"There was an error sending the email, please try again": "Hubo un error enviando el correo electrónico, intentalo de nuevo por favor.",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Este sitio es solo por invitación, contacta al propietario para obtener acceso.",
"This site is not accepting payments at the moment.": "Este sitio no acepta pagos en este momento.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/et/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "See sait on ainult kutsetega, juurdepääsu saamiseks võtke ühendust omanikuga.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/fa/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "دسترسی به این وب\u200cسایت نیازمند دعوت\u200cنامه است، با مالک آن برای دریافت دسترسی تماس بگیرید.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/fi/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Tämä sivu on vain kutsutuille, ota yhteyttä omistajaan saadaksesi pääsyoikeuden.",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/fr/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "Une erreur s'est produite lors de la prolongation de votre abonnement, veuillez réessayer.",
"There was an error processing your payment. Please try again.": "Une erreur s'est produite lors du traitement de votre paiement. Veuillez réessayer.",
"There was an error sending the email, please try again": "Une erreur s'est produite lors de l'envoi de l'e-mail, veuillez réessayer.",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Ce site est réservé aux invités. Veuillez écrire au propriétaire pour en demander l'accès.",
"This site is not accepting payments at the moment.": "Ce site n'accepte pas les paiements pour le moment.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/gd/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "Thachair mearachd fhad 's a bhathar a' làimhseachadh a' phàighidh agad. Feuch a-rithist an ceann greis.",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "Feumar cuireadh airson an làrach-lìn seo, leig fios dhan rianaire ma tha thu ag iarraidh cothrom-inntrigidh.",
"This site is not accepting payments at the moment.": "Chan eil an làrach seo a' gabhail ri phàighidhean an-dràsta.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/he/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "שגיאה בהמשך המנוי שלכם, נסו שוב.",
"There was an error processing your payment. Please try again.": "שגיאה בעיבוד התשלום שלכם. נסו שוב.",
"There was an error sending the email, please try again": "שגיאה בשליחת המייל, נסו שוב",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "אתר זה פתוח למוזמנים בלבד, פנו לבעל האתר לגישה.",
"This site is not accepting payments at the moment.": "אתר זה לא מקבל תשלומים כרגע.",
"This site only accepts paid members.": "",
Expand Down
1 change: 1 addition & 0 deletions ghost/i18n/locales/hi/portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"There was an error continuing your subscription, please try again.": "",
"There was an error processing your payment. Please try again.": "",
"There was an error sending the email, please try again": "",
"This email domain is not accepted, try again with a different email address": "",
"This site is invite-only, contact the owner for access.": "यह साइट केवल निमंत्रण द्वारा है, पहुँच के लिए मालिक से संपर्क करें।",
"This site is not accepting payments at the moment.": "",
"This site only accepts paid members.": "",
Expand Down
Loading

0 comments on commit 970741c

Please sign in to comment.