103 lines
3.5 KiB
JavaScript
103 lines
3.5 KiB
JavaScript
// ============================================================
|
|
// validator.js — Input validation and sanitization
|
|
// ============================================================
|
|
// Always validate user input before storing it in the database.
|
|
// This protects against bad data and injection attacks.
|
|
// ============================================================
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Load simple leaked passwords dictionary into memory
|
|
let leakedPasswords = new Set();
|
|
try {
|
|
const filePath = path.join(__dirname, '..', 'leaked_passwords.txt');
|
|
const data = fs.readFileSync(filePath, 'utf8');
|
|
data.split(/\r?\n/).forEach(line => {
|
|
const pwd = line.trim();
|
|
if (pwd) leakedPasswords.add(pwd);
|
|
});
|
|
} catch (err) {
|
|
console.warn('Could not load leaked_passwords.txt. Leaked password checking is disabled.');
|
|
}
|
|
|
|
// Check if email format is valid (e.g. user@example.com)
|
|
function validateEmail(email) {
|
|
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return regex.test(email);
|
|
}
|
|
|
|
// Username: only letters, numbers, underscores. Length 3-30.
|
|
function validateUsername(username) {
|
|
const regex = /^[a-zA-Z0-9_]{3,30}$/;
|
|
return regex.test(username);
|
|
}
|
|
|
|
// Check if password exists in our leaked passwords file
|
|
function isLeakedPassword(password) {
|
|
return leakedPasswords.has(password);
|
|
}
|
|
|
|
// Password must be at least 8 characters and contain:
|
|
// - at least 1 uppercase letter
|
|
// - at least 1 lowercase letter
|
|
// - at least 1 number
|
|
// - at least 1 special character
|
|
function validatePassword(password) {
|
|
if (typeof password !== 'string' || password.length < 8) return false;
|
|
if (!/[A-Z]/.test(password)) return false; // uppercase
|
|
if (!/[a-z]/.test(password)) return false; // lowercase
|
|
if (!/[0-9]/.test(password)) return false; // digit
|
|
if (!/[^a-zA-Z0-9]/.test(password)) return false; // special char
|
|
return true;
|
|
}
|
|
|
|
// Returns a user-friendly message about what's missing from the password
|
|
function getPasswordErrors(password) {
|
|
const errors = [];
|
|
if (typeof password !== 'string') return ['Password is required.'];
|
|
if (password.length < 8) errors.push('at least 8 characters');
|
|
if (!/[A-Z]/.test(password)) errors.push('an uppercase letter');
|
|
if (!/[a-z]/.test(password)) errors.push('a lowercase letter');
|
|
if (!/[0-9]/.test(password)) errors.push('a number');
|
|
if (!/[^a-zA-Z0-9]/.test(password)) errors.push('a special character');
|
|
return errors;
|
|
}
|
|
|
|
// Sanitize input to prevent XSS (cross-site scripting)
|
|
// This escapes HTML special characters like < > & " '
|
|
function sanitize(input) {
|
|
if (typeof input !== 'string') return input;
|
|
return input
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
// Password strength checker — returns 'weak', 'medium', or 'strong'
|
|
// Used on the frontend to show a live strength indicator
|
|
function checkPasswordStrength(password) {
|
|
let score = 0;
|
|
if (password.length >= 8) score++; // basic length
|
|
if (password.length >= 12) score++; // longer = better
|
|
if (/[A-Z]/.test(password)) score++; // has uppercase
|
|
if (/[0-9]/.test(password)) score++; // has number
|
|
if (/[^a-zA-Z0-9]/.test(password)) score++; // has special char
|
|
|
|
if (score <= 2) return 'weak';
|
|
if (score <= 3) return 'medium';
|
|
return 'strong';
|
|
}
|
|
|
|
module.exports = {
|
|
validateEmail,
|
|
validateUsername,
|
|
validatePassword,
|
|
getPasswordErrors,
|
|
isLeakedPassword,
|
|
sanitize,
|
|
checkPasswordStrength,
|
|
};
|