Add CORS and move registration policies to middleware
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
const { isDisposableEmail } = require('fakeout');
|
||||
|
||||
const LIMITS = {
|
||||
name: 100,
|
||||
username: 50,
|
||||
email: 254,
|
||||
password: 128,
|
||||
};
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 12;
|
||||
const COMMON_PASSWORDS = new Set([
|
||||
'123456789', 'password', 'password123', 'qwerty',
|
||||
'qwertyuiop', 'letmein', 'welcome', 'admin',
|
||||
'iloveyou', 'monkey', 'dragon', 'abc123',
|
||||
]);
|
||||
|
||||
function cleanText(value, { trim = true } = {}) {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const cleaned = value.replace(/[\u0000-\u001F\u007F]/g, '');
|
||||
return trim ? cleaned.trim() : cleaned;
|
||||
}
|
||||
|
||||
function cleanPersonInput(input) {
|
||||
return {
|
||||
name: cleanText(input.name),
|
||||
username: cleanText(input.username),
|
||||
email: cleanText(input.email).toLowerCase(),
|
||||
// Password whitespace is preserved, but control characters are removed.
|
||||
password: cleanText(input.password, { trim: false }),
|
||||
};
|
||||
}
|
||||
|
||||
function validateCommonFields(values, { requireName = true } = {}) {
|
||||
const fields = requireName
|
||||
? ['name', 'username', 'email', 'password']
|
||||
: ['email', 'password'];
|
||||
|
||||
for (const field of fields) {
|
||||
if (!values[field] || values[field].length > LIMITS[field]) {
|
||||
throw new Error(`${field} must be between 1 and ${LIMITS[field]} characters`);
|
||||
}
|
||||
}
|
||||
|
||||
if (/\s/.test(values.username)) {
|
||||
throw new Error('username must not contain spaces');
|
||||
}
|
||||
|
||||
if (/\s/.test(values.email)) {
|
||||
throw new Error('email must not contain spaces');
|
||||
}
|
||||
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
|
||||
throw new Error('email must be a valid email address');
|
||||
}
|
||||
}
|
||||
|
||||
function validateRegistrationInput(values) {
|
||||
validateCommonFields(values);
|
||||
|
||||
if (values.password.length < MIN_PASSWORD_LENGTH) {
|
||||
throw new Error(`password must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
||||
}
|
||||
|
||||
const passwordLower = values.password.toLowerCase();
|
||||
const normalizedPassword = passwordLower.replace(/[^a-z0-9]/g, '');
|
||||
const emailLocalPart = values.email.split('@')[0];
|
||||
if (
|
||||
COMMON_PASSWORDS.has(normalizedPassword) ||
|
||||
passwordLower.includes(values.username.toLowerCase()) ||
|
||||
passwordLower.includes(emailLocalPart)
|
||||
) {
|
||||
throw new Error('password is too common or contains account details');
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(values.password) || !/[A-Z]/.test(values.password)) {
|
||||
throw new Error('password must contain lowercase and uppercase letters');
|
||||
}
|
||||
|
||||
if (!/[0-9]/.test(values.password)) {
|
||||
throw new Error('password must contain a number');
|
||||
}
|
||||
|
||||
if (!/[^a-zA-Z0-9\s]/.test(values.password)) {
|
||||
throw new Error('password must contain a special character');
|
||||
}
|
||||
|
||||
if (/(.)\1\1/.test(values.password) || /(0123|1234|2345|3456|4567|5678|6789|abcd|bcde|cdef)/i.test(values.password)) {
|
||||
throw new Error('password must not contain obvious repeated or sequential characters');
|
||||
}
|
||||
|
||||
if (isDisposableEmail(values.email)) {
|
||||
throw new Error('temporary email addresses are not allowed');
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizePersonInput(req, res, next) {
|
||||
try {
|
||||
const values = cleanPersonInput(req.body || {});
|
||||
validateRegistrationInput(values);
|
||||
req.person = values;
|
||||
next();
|
||||
}
|
||||
catch (err) {
|
||||
res.status(400).send(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeLoginInput(req, res, next) {
|
||||
try {
|
||||
const values = cleanPersonInput(req.body || {});
|
||||
validateCommonFields(values, { requireName: false });
|
||||
req.loginInput = { email: values.email, password: values.password };
|
||||
next();
|
||||
}
|
||||
catch (err) {
|
||||
res.status(400).send(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { sanitizePersonInput, sanitizeLoginInput };
|
||||
Reference in New Issue
Block a user