The problem
Need to mask sensitive information like emails, phone numbers, and credit cards
The solution
class DataMasker {
constructor(options = {}) {
this.options = {
maskChar: '*',
visibleChars: {
email: 3,
phone: 4,
ssn: 4,
creditCard: 4
},
preserveLength: true,
...options
};
}
maskEmail(email, visibleChars = this.options.visibleChars.email) {
if (!this.isValidEmail(email)) return email;
const [local, domain] = email.split('@');
const maskedLocal = this.maskString(local, visibleChars);
return `${maskedLocal}@${domain}`;
}
maskPhone(phone, visibleChars = this.options.visibleChars.phone) {
const digits = phone.replace(/\D/g, '');
if (digits.length < 10) return phone;
const lastDigits = digits.slice(-visibleChars);
const maskedDigits = this.maskString(
digits.slice(0, -visibleChars),
0,
this.options.preserveLength
);
return `${maskedDigits}${lastDigits}`;
}
maskSSN(ssn, visibleChars = this.options.visibleChars.ssn) {
const digits = ssn.replace(/\D/g, '');
if (digits.length !== 9) return ssn;
const lastDigits = digits.slice(-visibleChars);
const maskedDigits = this.maskString(
digits.slice(0, -visibleChars),
0,
this.options.preserveLength
);
return `${maskedDigits}-${lastDigits}`;
}
maskCreditCard(cardNumber, visibleChars = this.options.visibleChars.creditCard) {
const digits = cardNumber.replace(/\D/g, '');
if (digits.length < 13 || digits.length > 19) return cardNumber;
const lastDigits = digits.slice(-visibleChars);
const maskedDigits = this.maskString(
digits.slice(0, -visibleChars),
0,
this.options.preserveLength
);
// Format with spaces like XXXX XXXX XXXX 1234
const masked = `${maskedDigits}${lastDigits}`;
return masked.replace(/(.{4})/g, '$1 ').trim();
}
maskName(name, visibleChars = 2) {
const parts = name.trim().split(/\s+/);
return parts.map(part => {
if (part.length <= visibleChars) {
return this.maskString(part, 0);
}
return this.maskString(part, visibleChars);
}).join(' ');
}
maskString(str, visibleChars = 0, preserveLength = this.options.preserveLength) {
if (!str || str.length === 0) return str;
if (visibleChars >= str.length) {
return str;
}
const visiblePart = str.slice(0, visibleChars);
const maskedLength = preserveLength ? str.length - visibleChars : 4;
const maskedPart = this.options.maskChar.repeat(maskedLength);
return `${visiblePart}${maskedPart}`;
}
maskObject(obj, maskRules = {}) {
const masked = { ...obj };
for (const [key, rule] of Object.entries(maskRules)) {
if (masked[key] !== undefined && masked[key] !== null) {
if (typeof rule === 'string') {
// Simple field type
masked[key] = this.maskByType(masked[key], rule);
} else if (typeof rule === 'function') {
// Custom mask function
masked[key] = rule(masked[key]);
} else if (typeof rule === 'object') {
// Nested object with rules
if (typeof masked[key] === 'object' && !Array.isArray(masked[key])) {
masked[key] = this.maskObject(masked[key], rule);
}
}
}
}
return masked;
}
maskByType(value, type) {
switch (type.toLowerCase()) {
case 'email':
return this.maskEmail(String(value));
case 'phone':
return this.maskPhone(String(value));
case 'ssn':
return this.maskSSN(String(value));
case 'creditcard':
return this.maskCreditCard(String(value));
case 'name':
return this.maskName(String(value));
default:
return this.maskString(String(value));
}
}
isValidEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
setMaskChar(char) {
this.options.maskChar = char;
return this;
}
setVisibleChars(config) {
this.options.visibleChars = { ...this.options.visibleChars, ...config };
return this;
}
}Put it to work
const masker = new DataMasker({
maskChar: '*',
visibleChars: {
email: 3,
phone: 4,
ssn: 4,
creditCard: 4
}
});
// Mask individual fields
console.log(masker.maskEmail('[email protected]')); // "joh*****@example.com"
console.log(masker.maskPhone('+1 (555) 123-4567')); // "*********4567"
console.log(masker.maskSSN('123-45-6789')); // "*****6789"
console.log(masker.maskCreditCard('4111 1111 1111 1111')); // "**** **** **** 1111"
console.log(masker.maskName('John Doe')); // "Jo** Do*"
// Mask entire object
const userData = {
id: 123,
email: '[email protected]',
phone: '555-123-4567',
ssn: '123-45-6789',
creditCard: '4111111111111111',
address: {
street: '123 Main St',
city: 'Anytown'
}
};
const maskRules = {
email: 'email',
phone: 'phone',
ssn: 'ssn',
creditCard: 'creditCard',
address: {
street: (value) => masker.maskString(value, 5)
}
};
const maskedData = masker.maskObject(userData, maskRules);
console.log(maskedData);
// {
// id: 123,
// email: 'joh*****@example.com',
// phone: '*******4567',
// ssn: '*****6789',
// creditCard: '**** **** **** 1111',
// address: { street: '123 Ma*****', city: 'Anytown' }
// }
// Custom configuration
masker.setMaskChar('#');
masker.setVisibleChars({ email: 5, phone: 6 });
console.log(masker.maskEmail('[email protected]')); // "alice#####@example.com"