linagora-rse
Version:
217 lines (187 loc) • 5.61 kB
JavaScript
'use strict';
var emailAddresses = require('email-addresses');
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var trim = require('trim');
var ObjectId = mongoose.Schema.Types.ObjectId;
var Mixed = mongoose.Schema.Types.Mixed;
const { validateActionState, validateUserAction } = require('../../../../core/user/states');
function validateEmail(email) {
return emailAddresses.parseOneAddress(email) !== null;
}
function validateEmails(emails) {
if (!emails || !emails.length) {
return true;
}
var valid = true;
emails.forEach(function(email) {
if (!validateEmail(email)) {
valid = false;
}
});
return valid;
}
function hasEmail(accounts) {
return accounts.some(function(account) {
return account.emails && account.emails.some(function(email) {
return !!email;
});
});
}
function validateAccounts(accounts) {
return accounts && accounts.length;
}
var UserAccountSchema = new mongoose.Schema({
_id: false,
type: { type: String, enum: ['email', 'oauth'] },
hosted: { type: Boolean, default: false },
emails: { type: [String], unique: true, partialFilterExpression: { $type: 'array' }, validate: validateEmails },
preferredEmailIndex: { type: Number, default: 0 },
timestamps: {
creation: { type: Date, default: Date.now }
},
data: { type: Mixed }
});
UserAccountSchema.pre('validate', function(next) {
if (this.emails.length && (this.preferredEmailIndex < 0 || this.preferredEmailIndex >= this.emails.length)) {
return next(new Error('The preferredEmailIndex field must be a valid index of the emails array.'));
}
next();
});
UserAccountSchema.pre('save', function(next) {
if (this.emails.length === 0) {
this.emails = undefined;
}
next();
});
var MemberOfDomainSchema = new mongoose.Schema({
domain_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Domain', required: true },
joined_at: { type: Date, default: Date.now },
status: { type: String, lowercase: true, trim: true }
}, { _id: false });
const UserStateSchema = new mongoose.Schema({
name: { type: String, validate: validateUserAction },
value: { type: String, validate: validateActionState }
}, { _id: false });
var UserSchema = new mongoose.Schema({
firstname: { type: String, trim: true },
lastname: { type: String, trim: true },
password: { type: String },
job_title: { type: String, trim: true },
service: { type: String, trim: true },
building_location: { type: String, trim: true },
office_location: { type: String, trim: true },
main_phone: { type: String, trim: true },
description: { type: String, trim: true },
timestamps: {
creation: { type: Date, default: Date.now }
},
domains: { type: [MemberOfDomainSchema] },
login: {
failures: {
type: [Date]
},
success: { type: Date }
},
states: [UserStateSchema],
schemaVersion: { type: Number, default: 2 },
avatars: [ObjectId],
currentAvatar: ObjectId,
accounts: { type: [UserAccountSchema], required: true, validate: validateAccounts }
});
UserSchema.virtual('preferredEmail').get(function() {
return this.accounts
.filter(function(account) {
return account.type === 'email';
})
.slice() // Because sort mutates the array
.sort(function(a, b) {
return b.hosted - a.hosted;
})
.reduce(function(foundPreferredEmail, account) {
return foundPreferredEmail || account.emails[account.preferredEmailIndex];
}, null);
});
UserSchema.virtual('preferredDomainId').get(function() {
return this.domains.length ? this.domains[0].domain_id : '';
});
UserSchema.virtual('emails').get(function() {
var emails = [];
this.accounts.forEach(function(account) {
account.emails && account.emails.forEach(function(email) {
emails.push(email);
});
});
return emails;
});
UserSchema.pre('save', function(next) {
var self = this;
var SALT_FACTOR = 5;
self.accounts.forEach(function(account) {
account.emails = account.emails && account.emails.map(function(email) {
return trim(email).toLowerCase();
});
});
if (!hasEmail(self.accounts)) {
return next(new Error('User must have at least one email'));
}
if (!self.isModified('password')) {
return next();
}
bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
if (err) {
return next(err);
}
bcrypt.hash(self.password, salt, null, function(err, hash) {
if (err) {
return next(err);
}
self.password = hash;
next();
});
});
});
UserSchema.methods = {
comparePassword: function(candidatePassword, cb) {
if (!candidatePassword) {
return cb(new Error('Can not compare with null password'));
}
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
},
loginFailure: function(cb) {
this.login.failures.push(new Date());
this.save(cb);
},
loginSuccess: function(cb) {
this.login.success = new Date();
this.login.failures = [];
this.save(cb);
},
resetLoginFailure: function(cb) {
this.login.failures = [];
this.save(cb);
}
};
UserSchema.statics = {
/**
* Load a user from one of its email
*
* @param {String} email
* @param {Function} cb - as fn(err, user) where user is not null if found
*/
loadFromEmail: function(email, cb) {
this.findOne({
accounts: {
$elemMatch: {
emails: trim(email).toLowerCase()
}
}
}, cb);
}
};
module.exports = mongoose.model('User', UserSchema);