mirror of
https://github.com/taobataoma/meanTorrent.git
synced 2026-01-18 13:22:20 +01:00
Fixes an issue with an empty/missing/null Email coming from GitHub's OAuth call response. Also, introduces the `sparse` index option on the User model's Email field. This will ensure that we can have multiple User documents without the Email field. Adds a server-side User model test for the sparse index setting on the email field. Confirms that User documents without the email field are not indexed, illustrating the sparse option on the schema's email field works properly. Added the dropdb task to the Gulp test:client & test:server tasks, to ensure we have a clean database & that any indexes are rebuilt; this will ensure any Schema changes (in this case the email index is rebuilt using the sparse index option) are reflected when the database is started again. Added a UPGRADE.md for tracking important upgrade information for our user's to be aware of, when we introduce potentially breaking changes. Included an explanation of the Sparse index being added, and how to apply it to an existing MEANJS application's database. Adds a script for dropping the `email` field's index from the User collection. Related #1145
210 lines
5.3 KiB
JavaScript
210 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
var mongoose = require('mongoose'),
|
|
Schema = mongoose.Schema,
|
|
crypto = require('crypto'),
|
|
validator = require('validator'),
|
|
generatePassword = require('generate-password'),
|
|
owasp = require('owasp-password-strength-test');
|
|
|
|
/**
|
|
* A Validation function for local strategy properties
|
|
*/
|
|
var validateLocalStrategyProperty = function (property) {
|
|
return ((this.provider !== 'local' && !this.updated) || property.length);
|
|
};
|
|
|
|
/**
|
|
* A Validation function for local strategy email
|
|
*/
|
|
var validateLocalStrategyEmail = function (email) {
|
|
return ((this.provider !== 'local' && !this.updated) || validator.isEmail(email, { require_tld: false }));
|
|
};
|
|
|
|
/**
|
|
* User Schema
|
|
*/
|
|
var UserSchema = new Schema({
|
|
firstName: {
|
|
type: String,
|
|
trim: true,
|
|
default: '',
|
|
validate: [validateLocalStrategyProperty, 'Please fill in your first name']
|
|
},
|
|
lastName: {
|
|
type: String,
|
|
trim: true,
|
|
default: '',
|
|
validate: [validateLocalStrategyProperty, 'Please fill in your last name']
|
|
},
|
|
displayName: {
|
|
type: String,
|
|
trim: true
|
|
},
|
|
email: {
|
|
type: String,
|
|
index: {
|
|
unique: true,
|
|
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
|
|
},
|
|
lowercase: true,
|
|
trim: true,
|
|
default: '',
|
|
validate: [validateLocalStrategyEmail, 'Please fill a valid email address']
|
|
},
|
|
username: {
|
|
type: String,
|
|
unique: 'Username already exists',
|
|
required: 'Please fill in a username',
|
|
lowercase: true,
|
|
trim: true
|
|
},
|
|
password: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
salt: {
|
|
type: String
|
|
},
|
|
profileImageURL: {
|
|
type: String,
|
|
default: 'modules/users/client/img/profile/default.png'
|
|
},
|
|
provider: {
|
|
type: String,
|
|
required: 'Provider is required'
|
|
},
|
|
providerData: {},
|
|
additionalProvidersData: {},
|
|
roles: {
|
|
type: [{
|
|
type: String,
|
|
enum: ['user', 'admin']
|
|
}],
|
|
default: ['user'],
|
|
required: 'Please provide at least one role'
|
|
},
|
|
updated: {
|
|
type: Date
|
|
},
|
|
created: {
|
|
type: Date,
|
|
default: Date.now
|
|
},
|
|
/* For reset password */
|
|
resetPasswordToken: {
|
|
type: String
|
|
},
|
|
resetPasswordExpires: {
|
|
type: Date
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Hook a pre save method to hash the password
|
|
*/
|
|
UserSchema.pre('save', function (next) {
|
|
if (this.password && this.isModified('password')) {
|
|
this.salt = crypto.randomBytes(16).toString('base64');
|
|
this.password = this.hashPassword(this.password);
|
|
}
|
|
|
|
next();
|
|
});
|
|
|
|
/**
|
|
* Hook a pre validate method to test the local password
|
|
*/
|
|
UserSchema.pre('validate', function (next) {
|
|
if (this.provider === 'local' && this.password && this.isModified('password')) {
|
|
var result = owasp.test(this.password);
|
|
if (result.errors.length) {
|
|
var error = result.errors.join(' ');
|
|
this.invalidate('password', error);
|
|
}
|
|
}
|
|
|
|
next();
|
|
});
|
|
|
|
/**
|
|
* Create instance method for hashing a password
|
|
*/
|
|
UserSchema.methods.hashPassword = function (password) {
|
|
if (this.salt && password) {
|
|
return crypto.pbkdf2Sync(password, new Buffer(this.salt, 'base64'), 10000, 64).toString('base64');
|
|
} else {
|
|
return password;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create instance method for authenticating user
|
|
*/
|
|
UserSchema.methods.authenticate = function (password) {
|
|
return this.password === this.hashPassword(password);
|
|
};
|
|
|
|
/**
|
|
* Find possible not used username
|
|
*/
|
|
UserSchema.statics.findUniqueUsername = function (username, suffix, callback) {
|
|
var _this = this;
|
|
var possibleUsername = username.toLowerCase() + (suffix || '');
|
|
|
|
_this.findOne({
|
|
username: possibleUsername
|
|
}, function (err, user) {
|
|
if (!err) {
|
|
if (!user) {
|
|
callback(possibleUsername);
|
|
} else {
|
|
return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
|
|
}
|
|
} else {
|
|
callback(null);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Generates a random passphrase that passes the owasp test
|
|
* Returns a promise that resolves with the generated passphrase, or rejects with an error if something goes wrong.
|
|
* NOTE: Passphrases are only tested against the required owasp strength tests, and not the optional tests.
|
|
*/
|
|
UserSchema.statics.generateRandomPassphrase = function () {
|
|
return new Promise(function (resolve, reject) {
|
|
var password = '';
|
|
var repeatingCharacters = new RegExp('(.)\\1{2,}', 'g');
|
|
|
|
// iterate until the we have a valid passphrase
|
|
// NOTE: Should rarely iterate more than once, but we need this to ensure no repeating characters are present
|
|
while (password.length < 20 || repeatingCharacters.test(password)) {
|
|
// build the random password
|
|
password = generatePassword.generate({
|
|
length: Math.floor(Math.random() * (20)) + 20, // randomize length between 20 and 40 characters
|
|
numbers: true,
|
|
symbols: false,
|
|
uppercase: true,
|
|
excludeSimilarCharacters: true
|
|
});
|
|
|
|
// check if we need to remove any repeating characters
|
|
password = password.replace(repeatingCharacters, '');
|
|
}
|
|
|
|
// Send the rejection back if the passphrase fails to pass the strength test
|
|
if (owasp.test(password).errors.length) {
|
|
reject(new Error('An unexpected problem occured while generating the random passphrase'));
|
|
} else {
|
|
// resolve with the validated passphrase
|
|
resolve(password);
|
|
}
|
|
});
|
|
};
|
|
|
|
mongoose.model('User', UserSchema);
|