From 35c3d0bb4f1c31d45f86c2c6d4f2c30ace8e5ecb Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Mon, 3 Mar 2025 18:27:52 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20Block=20temporary=20inb?= =?UTF-8?q?oxes=20(#1602)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/public/locales/en/app.json | 1 + .../components/register-name-form.tsx | 8 +++ .../src/auth/helpers/temp-email-domains.ts | 65 +++++++++++++++++++ apps/web/src/trpc/routers/auth.ts | 13 +++- 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/auth/helpers/temp-email-domains.ts diff --git a/apps/web/public/locales/en/app.json b/apps/web/public/locales/en/app.json index ca4c77a81..141763112 100644 --- a/apps/web/public/locales/en/app.json +++ b/apps/web/public/locales/en/app.json @@ -30,6 +30,7 @@ "editVotes": "Edit votes", "email": "Email", "emailNotAllowed": "This email is not allowed.", + "temporaryEmailNotAllowed": "Temporary or disposable email addresses are not allowed.", "emailPlaceholder": "jessie.smith@example.com", "exportToCsv": "Export to CSV", "guest": "Guest", diff --git a/apps/web/src/app/[locale]/(auth)/register/components/register-name-form.tsx b/apps/web/src/app/[locale]/(auth)/register/components/register-name-form.tsx index 8ffbe61ca..d6aeeaddb 100644 --- a/apps/web/src/app/[locale]/(auth)/register/components/register-name-form.tsx +++ b/apps/web/src/app/[locale]/(auth)/register/components/register-name-form.tsx @@ -51,6 +51,14 @@ export function RegisterNameForm() { message: t("emailNotAllowed"), }); break; + case "temporaryEmailNotAllowed": + form.setError("email", { + message: t("temporaryEmailNotAllowed", { + defaultValue: + "Temporary or disposable email addresses are not allowed.", + }), + }); + break; case "userAlreadyExists": form.setError("email", { message: t("userAlreadyExists"), diff --git a/apps/web/src/auth/helpers/temp-email-domains.ts b/apps/web/src/auth/helpers/temp-email-domains.ts new file mode 100644 index 000000000..d175b5048 --- /dev/null +++ b/apps/web/src/auth/helpers/temp-email-domains.ts @@ -0,0 +1,65 @@ +// List of common temporary/disposable email domains +export const temporaryEmailDomains = [ + "10minutemail.com", + "temp-mail.org", + "fakeinbox.com", + "tempinbox.com", + "mailinator.com", + "guerrillamail.com", + "guerrillamail.net", + "guerrillamail.org", + "sharklasers.com", + "grr.la", + "yopmail.com", + "yopmail.fr", + "yopmail.net", + "cool.fr.nf", + "jetable.org", + "nospam.ze.tc", + "nomail.xl.cx", + "mega.zik.dj", + "speed.1s.fr", + "courriel.fr.nf", + "moncourrier.fr.nf", + "monemail.fr.nf", + "monmail.fr.nf", + "tempr.email", + "discard.email", + "discardmail.com", + "throwawaymail.com", + "trashmail.com", + "mailnesia.com", + "mailnull.com", + "maildrop.cc", + "getairmail.com", + "getnada.com", + "emailondeck.com", + "emailfake.com", + "mohmal.com", + "tempmail.ninja", + "temp-mail.io", + "disposable-email.com", + "tempmailaddress.com", + "tempail.com", + "tempemail.co", + "tempmail.plus", + "burnermail.io", + "spamgourmet.com", + "mytemp.email", + "incognitomail.com", + "mintemail.com", + "tempmailo.com", + "temporary-mail.net", + "mailto.plus", + "ethereal.mail", +]; + +/** + * Checks if an email domain is a known temporary/disposable email service + */ +export const isTemporaryEmail = (email: string): boolean => { + const domain = email.split("@")[1]?.toLowerCase(); + if (!domain) return false; + + return temporaryEmailDomains.includes(domain); +}; diff --git a/apps/web/src/trpc/routers/auth.ts b/apps/web/src/trpc/routers/auth.ts index e66472153..1c5f7d55a 100644 --- a/apps/web/src/trpc/routers/auth.ts +++ b/apps/web/src/trpc/routers/auth.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { isEmailBlocked } from "@/auth/helpers/is-email-blocked"; import { mergeGuestsIntoUser } from "@/auth/helpers/merge-user"; +import { isTemporaryEmail } from "@/auth/helpers/temp-email-domains"; import { getEmailClient } from "@/utils/emails"; import { createToken, decryptToken } from "@/utils/session"; @@ -42,12 +43,22 @@ export const auth = router({ ctx, }): Promise< | { ok: true; token: string } - | { ok: false; reason: "userAlreadyExists" | "emailNotAllowed" } + | { + ok: false; + reason: + | "userAlreadyExists" + | "emailNotAllowed" + | "temporaryEmailNotAllowed"; + } > => { if (isEmailBlocked?.(input.email)) { return { ok: false, reason: "emailNotAllowed" }; } + if (isTemporaryEmail(input.email)) { + return { ok: false, reason: "temporaryEmailNotAllowed" }; + } + const user = await prisma.user.findUnique({ select: { id: true,