Mask Email Addresses for Logs

Mask the local part of email addresses while preserving the domain, with short-address handling and an option to hide subdomains.

JavascriptSecurity Boundaries

Code

snippet
export function maskEmail(email, options = {}) {
  if (typeof email !== "string") {
    throw new TypeError("email must be a string");
  }
  const { maskDomain = false } = options;
  const address = email.trim();
  if (address.length === 0 || address.length > 254) {
    throw new Error("invalid email length");
  }
  if (/\s/.test(address)) {
    throw new Error("invalid email address");
  }

  const atIndex = address.indexOf("@");
  if (
    atIndex <= 0 ||
    atIndex !== address.lastIndexOf("@") ||
    atIndex === address.length - 1
  ) {
    throw new Error("invalid email address");
  }

  const local = address.slice(0, atIndex);
  const domain = address.slice(atIndex + 1);
  if (
    !domain.includes(".") ||
    domain.startsWith(".") ||
    domain.endsWith(".") ||
    domain.includes("..")
  ) {
    throw new Error("invalid email domain");
  }

  let maskedLocal;
  if (local.length === 1) {
    maskedLocal = "*";
  } else if (local.length === 2) {
    maskedLocal = `${local[0]}*`;
  } else {
    maskedLocal = `${local[0]}***${local[local.length - 1]}`;
  }

  let maskedDomain = domain;
  if (maskDomain) {
    const labels = domain.split(".");
    maskedDomain = labels
      .map((label, index) =>
        index === labels.length - 1 ? label : "*".repeat(Math.max(1, label.length)),
      )
      .join(".");
  }

  return `${maskedLocal}@${maskedDomain}`;
}

console.log(maskEmail("[email protected]"));
console.log(maskEmail("[email protected]"));
console.log(maskEmail("[email protected]"));
console.log(maskEmail("[email protected]", { maskDomain: true }));

try {
  maskEmail("not-an-email");
} catch (error) {
  console.log(error.message);
}