229 lines
7.0 KiB
JavaScript
229 lines
7.0 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const User = require('./user');
|
|
const Config = require('./config').get;
|
|
const Log = require('./logger').log;
|
|
const { getAddressedToInfo } = require('./mail_util');
|
|
const Message = require('./message');
|
|
const { Errors, ErrorReasons } = require('./enig_error'); // note: Only use ValidationFailed in this module!
|
|
|
|
// deps
|
|
const fs = require('graceful-fs');
|
|
|
|
exports.validateNonEmpty = validateNonEmpty;
|
|
exports.validateMessageSubject = validateMessageSubject;
|
|
exports.validateUserNameAvail = validateUserNameAvail;
|
|
exports.validateUserNameExists = validateUserNameExists;
|
|
exports.validateUserNameOrRealNameExists = validateUserNameOrRealNameExists;
|
|
exports.validateGeneralMailAddressedTo = validateGeneralMailAddressedTo;
|
|
exports.validateEmailAvail = validateEmailAvail;
|
|
exports.validateBirthdate = validateBirthdate;
|
|
exports.validatePasswordSpec = validatePasswordSpec;
|
|
|
|
function validateNonEmpty(data, cb) {
|
|
return cb(
|
|
data && data.length > 0
|
|
? null
|
|
: Errors.ValidationFailed('Field cannot be empty', ErrorReasons.ValueTooShort)
|
|
);
|
|
}
|
|
|
|
function validateMessageSubject(data, cb) {
|
|
return cb(
|
|
data && data.length > 1
|
|
? null
|
|
: Errors.ValidationFailed('Subject too short', ErrorReasons.ValueTooShort)
|
|
);
|
|
}
|
|
|
|
function validateUserNameAvail(data, cb) {
|
|
const config = Config();
|
|
if (!data || data.length < config.users.usernameMin) {
|
|
cb(Errors.ValidationFailed('Username too short', ErrorReasons.ValueTooShort));
|
|
} else if (data.length > config.users.usernameMax) {
|
|
// generally should be unreached due to view restraints
|
|
return cb(
|
|
Errors.ValidationFailed('Username too long', ErrorReasons.ValueTooLong)
|
|
);
|
|
} else {
|
|
const usernameRegExp = new RegExp(config.users.usernamePattern);
|
|
const invalidNames = config.users.newUserNames + config.users.badUserNames;
|
|
|
|
if (!usernameRegExp.test(data)) {
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
'Username contains invalid characters',
|
|
ErrorReasons.ValueInvalid
|
|
)
|
|
);
|
|
} else if (invalidNames.indexOf(data.toLowerCase()) > -1) {
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
'Username is blacklisted',
|
|
ErrorReasons.NotAllowed
|
|
)
|
|
);
|
|
} else if (/^[0-9]+$/.test(data)) {
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
'Username cannot be a number',
|
|
ErrorReasons.ValueInvalid
|
|
)
|
|
);
|
|
} else {
|
|
// a new user name cannot be an existing user name or an existing real name
|
|
User.getUserIdAndNameByLookup(data, function userIdAndName(err) {
|
|
if (!err) {
|
|
// err is null if we succeeded -- meaning this user exists already
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
'Username unavailable',
|
|
ErrorReasons.NotAvailable
|
|
)
|
|
);
|
|
}
|
|
|
|
return cb(null);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateUserNameExists(data, cb) {
|
|
if (0 === data.length) {
|
|
return cb(
|
|
Errors.ValidationFailed('Invalid username', ErrorReasons.ValueTooShort)
|
|
);
|
|
}
|
|
|
|
User.getUserIdAndName(data, err => {
|
|
return cb(
|
|
err
|
|
? Errors.ValidationFailed(
|
|
'Failed to find username',
|
|
err.reasonCode || ErrorReasons.DoesNotExist
|
|
)
|
|
: null
|
|
);
|
|
});
|
|
}
|
|
|
|
function validateUserNameOrRealNameExists(data, cb) {
|
|
if (0 === data.length) {
|
|
return cb(
|
|
Errors.ValidationFailed('Invalid username', ErrorReasons.ValueTooShort)
|
|
);
|
|
}
|
|
|
|
User.getUserIdAndNameByLookup(data, err => {
|
|
return cb(
|
|
err
|
|
? Errors.ValidationFailed(
|
|
'Failed to find user',
|
|
err.reasonCode || ErrorReasons.DoesNotExist
|
|
)
|
|
: null
|
|
);
|
|
});
|
|
}
|
|
|
|
function validateGeneralMailAddressedTo(data, cb) {
|
|
//
|
|
// Allow any supported addressing:
|
|
// - Local username or real name
|
|
// - Supported remote flavors such as FTN, email, ...
|
|
//
|
|
const addressedToInfo = getAddressedToInfo(data);
|
|
if (Message.AddressFlavor.Local !== addressedToInfo.flavor) {
|
|
return cb(null);
|
|
}
|
|
|
|
return validateUserNameOrRealNameExists(data, cb);
|
|
}
|
|
|
|
function validateEmailAvail(data, cb) {
|
|
//
|
|
// This particular method allows empty data - e.g. no email entered
|
|
//
|
|
if (!data || 0 === data.length) {
|
|
return cb(null);
|
|
}
|
|
|
|
//
|
|
// Otherwise, it must be a valid email. We'll be pretty lose here, like
|
|
// the HTML5 spec.
|
|
//
|
|
// See http://stackoverflow.com/questions/7786058/find-the-regex-used-by-html5-forms-for-validation
|
|
//
|
|
const emailRegExp = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
|
|
if (!emailRegExp.test(data)) {
|
|
return cb(
|
|
Errors.ValidationFailed('Invalid email address', ErrorReasons.ValueInvalid)
|
|
);
|
|
}
|
|
|
|
User.getUserIdsWithProperty(
|
|
'email_address',
|
|
data,
|
|
function userIdsWithEmail(err, uids) {
|
|
if (err) {
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
err.message,
|
|
err.reasonCode || ErrorReasons.DoesNotExist
|
|
)
|
|
);
|
|
} else if (uids.length > 0) {
|
|
return cb(
|
|
Errors.ValidationFailed(
|
|
'Email address not unique',
|
|
ErrorReasons.NotAvailable
|
|
)
|
|
);
|
|
}
|
|
|
|
return cb(null);
|
|
}
|
|
);
|
|
}
|
|
|
|
function validateBirthdate(data, cb) {
|
|
// :TODO: check for dates in the future, or > reasonable values
|
|
return cb(
|
|
isNaN(Date.parse(data))
|
|
? Errors.ValidationFailed('Invalid birthdate', ErrorReasons.ValueInvalid)
|
|
: null
|
|
);
|
|
}
|
|
|
|
function validatePasswordSpec(data, cb) {
|
|
const config = Config();
|
|
if (!data || data.length < config.users.passwordMin) {
|
|
return cb(
|
|
Errors.ValidationFailed('Password too short', ErrorReasons.ValueTooShort)
|
|
);
|
|
}
|
|
|
|
// check badpass, if avail
|
|
fs.readFile(config.users.badPassFile, 'utf8', (err, passwords) => {
|
|
if (err) {
|
|
Log.warn(
|
|
{ error: err.message, path: config.users.badPassFile },
|
|
'Cannot read bad pass file'
|
|
);
|
|
return cb(null);
|
|
}
|
|
|
|
passwords = passwords.toString().split(/\r\n|\n/g);
|
|
if (passwords.includes(data)) {
|
|
return cb(
|
|
Errors.ValidationFailed('Password is too common', ErrorReasons.NotAllowed)
|
|
);
|
|
}
|
|
|
|
return cb(null);
|
|
});
|
|
}
|