mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-07 21:21:49 +02:00
🔒️ Use email normalization to prevent ban evasion (#1615)
This commit is contained in:
parent
f3ffe71df3
commit
3d8604a379
2 changed files with 101 additions and 10 deletions
|
@ -1,4 +1,95 @@
|
||||||
export const isEmailBlocked = (email: string) => {
|
import { prisma } from "@rallly/database";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes an email address by removing aliases and standardizing format
|
||||||
|
* based on the email provider's alias conventions.
|
||||||
|
*
|
||||||
|
* Handles:
|
||||||
|
* - Gmail: Removes dots and plus aliases (user.name+alias@gmail.com → username@gmail.com)
|
||||||
|
* - Yahoo: Removes hyphen aliases (user-alias@yahoo.com → user@yahoo.com)
|
||||||
|
* - Outlook/Hotmail/Live: Removes plus aliases
|
||||||
|
* - iCloud: Removes plus aliases
|
||||||
|
* - ProtonMail: Removes plus aliases
|
||||||
|
* - FastMail: Removes plus aliases
|
||||||
|
* - Hey: Removes plus aliases
|
||||||
|
*
|
||||||
|
* @param email The email address to normalize
|
||||||
|
* @returns The normalized email address
|
||||||
|
*/
|
||||||
|
function normalizeEmail(email: string): string {
|
||||||
|
if (!email || !email.includes("@")) {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = email.toLowerCase().split("@");
|
||||||
|
let localPart = parts[0];
|
||||||
|
const domain = parts[1];
|
||||||
|
|
||||||
|
// Handle Gmail's dot-ignoring and plus aliases
|
||||||
|
if (domain === "gmail.com" || domain === "googlemail.com") {
|
||||||
|
// Remove all dots from the local part
|
||||||
|
localPart = localPart.replace(/\./g, "");
|
||||||
|
// Remove everything after the first plus
|
||||||
|
localPart = localPart.split("+")[0];
|
||||||
|
}
|
||||||
|
// Handle Yahoo's hyphen aliases
|
||||||
|
else if (
|
||||||
|
domain === "yahoo.com" ||
|
||||||
|
domain === "ymail.com" ||
|
||||||
|
domain === "rocketmail.com"
|
||||||
|
) {
|
||||||
|
// Remove everything after the first hyphen
|
||||||
|
localPart = localPart.split("-")[0];
|
||||||
|
}
|
||||||
|
// Handle plus aliases for other common providers
|
||||||
|
else if (
|
||||||
|
[
|
||||||
|
"outlook.com",
|
||||||
|
"hotmail.com",
|
||||||
|
"live.com",
|
||||||
|
"msn.com",
|
||||||
|
"icloud.com",
|
||||||
|
"me.com",
|
||||||
|
"mac.com",
|
||||||
|
"protonmail.com",
|
||||||
|
"pm.me",
|
||||||
|
"proton.me",
|
||||||
|
"fastmail.com",
|
||||||
|
"fastmail.fm",
|
||||||
|
"hey.com",
|
||||||
|
].includes(domain)
|
||||||
|
) {
|
||||||
|
// Remove everything after the first plus
|
||||||
|
localPart = localPart.split("+")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${localPart}@${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a user is banned by their email address,
|
||||||
|
* taking into account email aliases that could be used to bypass bans.
|
||||||
|
*
|
||||||
|
* @param email The email address to check
|
||||||
|
* @returns Whether the user with this email is banned
|
||||||
|
*/
|
||||||
|
export async function isEmailBanned(email: string) {
|
||||||
|
const normalizedEmail = normalizeEmail(email);
|
||||||
|
|
||||||
|
// Check both the original and normalized emails
|
||||||
|
const bannedUser = await prisma.user.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ email, banned: true },
|
||||||
|
{ email: normalizedEmail, banned: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return bannedUser > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmailBlocked(email: string) {
|
||||||
if (process.env.ALLOWED_EMAILS) {
|
if (process.env.ALLOWED_EMAILS) {
|
||||||
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");
|
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");
|
||||||
// Check whether the email matches enough of the patterns specified in ALLOWED_EMAILS
|
// Check whether the email matches enough of the patterns specified in ALLOWED_EMAILS
|
||||||
|
@ -15,5 +106,6 @@ export const isEmailBlocked = (email: string) => {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type { Provider } from "next-auth/providers";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { CustomPrismaAdapter } from "./auth/adapters/prisma";
|
import { CustomPrismaAdapter } from "./auth/adapters/prisma";
|
||||||
import { isEmailBlocked } from "./auth/helpers/is-email-blocked";
|
import { isEmailBanned, isEmailBlocked } from "./auth/helpers/is-email-blocked";
|
||||||
import { mergeGuestsIntoUser } from "./auth/helpers/merge-user";
|
import { mergeGuestsIntoUser } from "./auth/helpers/merge-user";
|
||||||
import { getLegacySession } from "./auth/legacy/next-auth-cookie-migration";
|
import { getLegacySession } from "./auth/legacy/next-auth-cookie-migration";
|
||||||
import { EmailProvider } from "./auth/providers/email";
|
import { EmailProvider } from "./auth/providers/email";
|
||||||
|
@ -104,15 +104,14 @@ const {
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Make sure email is allowed
|
|
||||||
if (user.email) {
|
if (user.banned) {
|
||||||
const isBlocked = isEmailBlocked(user.email);
|
|
||||||
if (isBlocked) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is banned
|
// Make sure email is allowed
|
||||||
if (user.banned) {
|
if (user.email) {
|
||||||
|
if (isEmailBlocked(user.email) || (await isEmailBanned(user.email))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue