const Authentication = require('../modules/authentication'); const Mongoose = require('mongoose'); const Reset = require('./reset'); const UserSchema = new Mongoose.Schema({ username: { type: String, required: true, unique: true }, password: { type: String }, name: { first: { type: String, required: true }, last: { type: String, required: true } }, email: { type: String, required: true, unique: true }, can: [{ type: String, enum: ['add','edit','delete','manage','super','update','view'], default: ['view'] }], forceReset: { type: Boolean, default: true }, updated_at: { type: Date, default: Date.now } }); UserSchema.pre('findOneAndUpdate', function (next) { var self = this; this.update({}, { $set: { updated_at: Date.now() } }); if (this._update.$set.settings && this._update.$set.settings.length) { } if (this._update.$set.password && this._update.$set.confirmPassword && (this._update.$set.password == this._update.$set.confirmPassword)) { delete this._update.$set.confirmPassword; if (this._update.$set.currentPassword) { confirmPassword(this._conditions.userName, this._update.$set.currentPassword, (err, valid) => { if (err || !valid) { err = new Error({ success: false, message: 'There was an error validating the current password.', err: (err || null) }); next(err); } if (valid) { delete this._update.$set.currentPassword; hashPassword(this._update.$set.password, (err, hashedPassword) => { self.update({}, { $set: { password: hashedPassword } }); self.update({}, { $set: { forceReset: false } }); next(); }); } }); } else { hashPassword(this._update.$set.password, (err, hashedPassword) => { self.update({}, { $set: { password: hashedPassword } }); self.update({}, { $set: { forceReset: false } }); next(); }); } } else if (this._update.$set.password && this._update.$set.confirmPassword && (this._update.$set.password != this._update.$set.confirmPassword)) { let err = new Error({ success: false, message: 'There was an error saving the updated password.'}); next(err); } else if (!this._update.$set.password && !this._update.$set.confirmPassword) { next(); } }); UserSchema.post('save', function (err, res, next) { if (err.name === 'MongoError' && err.code === 11000) { next(new Error('There was a duplicate key error')); } else if (err) { next(err); } else { next(); } }); const UserModel = Mongoose.model('users', UserSchema); var logger = require('../modules/logger'); function hashPassword (password, callback) { callback = callback || false; Authentication.hashPassword(password, (err, password) => { if (err !== null) { err = new Error({ success: false, message: 'There was an error hashing the updated password.', err: err }); logger.error('[updateUser:hashPassword] ', err); if (callback) { callback(err, null); } else { return false; } } if (password) { var result = password.toString('hex'); if (callback) { callback(null, result); } else { return result; } } }); } function confirmPassword (username, passwordToValidate, callback) { callback = callback || false; UserModel.findById({ userName: username }, (err, user) => { if (err !== null) { err = new Error({ success: false, message: 'There was an error locating the user.', err: (err || null) }); logger.error('[updateUser:confirmPassword] ', err); if (callback) { callback(err, null); } else { return false; } } if (user) { Authentication.verifyPassword(passwordToValidate, Buffer.from(storedUser.password, 'hex'), (err, valid) => { if (err !== null || !valid) { err = new Error({ success: false, message: (!err && !valid ? 'The current password was incorrect.' : 'There was an error attempting to validate the password.'), err: (err || null) }); logger.error('[updateUser:confirmPassword] ', { err: err, valid: valid }); if (callback) { callback(err, null); } else { return false; } } if (valid) { if (callback) { callback(null, valid); } else { return true; } } }); } }); } module.exports = { adminUpdatePassword: (e, username, password) => { const promise = new Promise((resolve, reject) => { UserModel.findOneAndUpdate({ userName: username }, { $set: password }, { new: true }, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { if (e) { e.emit('adminUpdatePassword', null, result); } else { return result; } }) .catch((err) => { if (e) { e.emit('adminUpdatePassword', err, null); } else { return err; } }); }, authenticateUser: (e, login, headers) => { const promise = new Promise((resolve, reject) => { var loginObject, user; UserModel .findOne({ userName: login.userName }, 'username name email password can forceReset') .exec((err, result) => { if (err) { loginObject = { status: 200, authorized: false, err: { id: '005', code: '[UMAU005]', string: 'There was an error authenticating the user.' } } logger.debug('[UserModel::authenticateUser] Error finding user', { err: err, username: login.userName }); resolve(loginObject); } if (result) { user = result; if (user && !user.permission.disabled) { if (user.forceReset) { loginObject = { status: 200, authorized: false, err: { id: '003', code: '[UMAU003]', string: 'A password reset has been mandated. Please check your email for a password reset link or request a new one from the login screen.' } }; resolve(loginObject); } else { try { Authentication.verifyPassword(login.password, Buffer.from(user.password, 'hex'), (err, valid) => { if (err) { logger.debug('[UserModel::authenticateUser] Error validating password', { err: err, user: user }); reject(err); } loginObject = { status: 200, authorized: valid }; if (valid) { loginObject.user = { _id: user._id, username: user.username, name: user.name, email: user.email, can: user.can }; loginObject.timestamp = Date.now(); logger.debug('[UserModel::authenticateUser] User Validated', { user: user, loginObject: loginObject }); resolve(loginObject); } else { loginObject.err = { id: '002', code: '[UMAU002]', string: 'The user id or password you entered was invalid.' }; logger.debug('[UserModel::authenticateUser] Invalid Password', { user: user, loginObject: loginObject }); resolve(loginObject); } }); } catch (err) { loginObject = { status: 200, authorized: false, err: { id: '004', code: '[UMAU004]', string: 'There was an error authenticating the user, please contact an administrator.' } }; logger.error('[UserModel::authenticateUser] Error verifying password', { err: err, user: user }); resolve(loginObject); } } } else if (user && user.permission.disabled) { loginObject = { status: 200, authorized: false, err: { id: '000', code: '[UMAU000]', string: 'The user is not authorized, please contact an administrator.' } }; logger.debug('[UserModel::authenticateUser] The user is disabled', { err: err, user: user }); resolve(loginObject); } else { loginObject = { status: 200, authorized: false, err: { id: '001', code: '[UMAU001]', string: 'The user id or password you entered was invalid.' } }; logger.debug('[UserModel::authenticateUser] The user does not exist', { err: err, user: user }); resolve(loginObject); } } }); }); promise.then((result) => { e.emit('authenticateUser', null, result); }) .catch((err) => { e.emit('authenticateUser', err, null); }); }, createUser: (e, user) => { const promise = new Promise((resolve, reject) => { var userInstance = new UserModel(user); userInstance.save((err, result) => { logger.debug('createUser', { err: err, result: result, user: userInstance }); if (err) { reject(err); } if (result) { Reset.sendNewUser(null, result); resolve(result); } }); }); promise.then((result) => { e.emit('createUser', null, result); }) .catch((err) => { e.emit('createUser', err, null); }); }, deleteUser: (e, id) => { const promise = new Promise((resolve, reject) => { UserModel.remove({ _id: id }, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { e.emit('deleteUser', null, result); }) .catch((err) => { e.emit('deleteUser', err, null); }); }, findUser: (query, callback) => { UserModel.findOne(query, (err, result) => { if (err) { callback(err, null); } if (result) { callback(null, result); } }); }, forcePasswordReset: (e, id) => { const promise = new Promise((resolve, reject) => { UserModel.findByIdAndUpdate(id, { $set: { forceReset: true } }, { new: true }, (err, result) => { if (err) { reject(err); } if (result) { let resetPromise = new Promise((resetResolve, resetReject) => { Reset.forceReset(null, result, (result) => { if (result.success) { resetResolve({ success: true, message: 'Force password reset initiated on the user.', result: result }); } else { resetResolve({ success: false, message: 'There was an error initiating the forced password reset.', result: result }); } }); }); resetPromise.then((result) => { resolve(result); }) .catch((err) => { reject(err); }); } }); }); promise.then((result) => { e.emit('forcePasswordReset', null, result); }) .catch((err) => { e.emit('forcePasswordReset', err, null); }); }, getUsers: (e, query, restricted = true) => { query = query || { find: {}, options: { sort: { 'name.last': 1, 'name.first': 1 }, limit: 0, skip: 0 }}; const promise = new Promise((resolve, reject) => { var projection = 'userName name' + (restricted ? '' : ' title email permission avatar settings forceReset'); var query = UserModel.find(query.find, projection, query.options); if (!restricted) { query.populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails') } query.exec((err, results) => { if (err) { reject(err); } if (results) { resolve(results); } }); }); promise.then((results) => { if (e) { e.emit('getUsers', null, results); } else { return results; } }) .catch((err) => { if (e) { e.emit('getUsers', err, null); } else { return err; } }); }, getUser: (e, id, restricted = true) => { const promise = new Promise((resolve, reject) => { var projection = 'userName name' + (restricted ? '' : ' title email permission avatar settings forceReset'); var query = UserModel .findById(id) .projection(projection); if (!restricted) { query.populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails'); } query.exec((err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { if (e) { e.emit('getUser', null, result); } else { return result; } }) .catch((err) => { if (e) { e.emit('getUser', err, null); } else { return err; } }); }, isUserNameUnique: (e, username) => { const promise = new Promise((resolve, reject) => { UserModel.findOne({ userName: username }, (err, result) => { if (err) { reject(err); } if (result) { resolve({ unique: false, length: true }); } else { resolve({ unique: true, length: true }); } }); }); promise.then((result) => { e.emit('isUserNameUnique', null, result); }) .catch((err) => { e.emit('isUserNameUnique', err, null); }); }, updateUser: (e, id, user) => { const promise = new Promise((resolve, reject) => { UserModel .findByIdAndUpdate(id, { $set: user }, { new: true }) .populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails') .exec((err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { e.emit('updateUser', null, result); }) .catch((err) => { e.emit('updateUser', err, null); }); }, updatePassword: (e, id, token, data) => { const promise = new Promise((resolve, reject) => { Reset.checkReset(null, id, token, (err, validatedId) => { if (data.userId === validatedId) { UserModel.findByIdAndUpdate(data.userId, { $set: { password: data.password, confirmPassword: data.confirmPassword, forceReset: false } }, { new: true }, (err, updated) => { if (err) { if (err.success === false) { resolve(err); } else { reject(err); } } if (updated) { Reset.markUsed(null, id); resolve({ success: true, updated: updated }); } }); } else { resolve({ success: false, message: 'The password reset link is not valid. Please request a new link.'}); } }); }); promise.then((result) => { e.emit('updatePassword', null, result); }) .catch((err) => { e.emit('updatePassword', err, null); }); } };