maglev
Version:
Preconfigured NodeJS framework
585 lines (469 loc) • 15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.name = undefined;
exports.createSchema = createSchema;
var _jsonwebtoken = require('jsonwebtoken');
var _jsonwebtoken2 = _interopRequireDefault(_jsonwebtoken);
var _bcryptjs = require('bcryptjs');
var _bcryptjs2 = _interopRequireDefault(_bcryptjs);
var _provider = require('./provider');
var _mongoosePermalink = require('mongoose-permalink');
var _mongoosePermalink2 = _interopRequireDefault(_mongoosePermalink);
var _mongooseHrbac = require('mongoose-hrbac');
var _mongooseHrbac2 = _interopRequireDefault(_mongooseHrbac);
var _mongooseJsonSchema = require('mongoose-json-schema');
var _mongooseJsonSchema2 = _interopRequireDefault(_mongooseJsonSchema);
var _okay = require('okay');
var _okay2 = _interopRequireDefault(_okay);
var _async = require('async');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var name = exports.name = 'User';
// max of 5 attempts, resulting in a 2 hour lock
var SALT_WORK_FACTOR = 10;
// const MAX_LOGIN_ATTEMPTS = 5;
// const LOCK_TIME = 2 * 60 * 60 * 1000;
function toPrivateJSON() {
var data = this.toJSON({ virtuals: true });
data.id = data._id;
delete data._id;
delete data.__v;
return data;
}
function getDisplayName() {
return this.name || this.username;
}
function updateUserByFacebookProfile(user, profile, callback) {
var changed = false;
if (!user.firstName && profile.first_name) {
user.firstName = profile.first_name;
changed = true;
}
if (!user.lastName && profile.last_name) {
user.lastName = profile.last_name;
changed = true;
}
if (!user.name && profile.name) {
user.name = profile.name;
changed = true;
}
if (!user.locale && profile.locale) {
user.locale = profile.locale;
changed = true;
}
(0, _async.series)([
// try to setup email
function (cb) {
var User = user.models('User');
if (!user.email && profile.email) {
User.findOne({
email: profile.email
}, function (err, foundedUser) {
if (err) {
return cb(err);
}
if (!foundedUser) {
user.email = profile.email;
changed = true;
}
cb(null);
});
} else {
cb(null);
}
},
// save user
function (cb) {
if (!changed) {
return cb(null);
}
user.save(cb);
}], function (err) {
callback(err, user);
});
}
/**
* Create user by user profile from facebook
* @param {Object} profile Profile from facebook
* @param {Function} callback Callback with created user
*/
function createByFacebook(profile, callback) {
var _this = this;
if (!profile.id) {
return callback(new Error('Profile id is undefined'));
}
this.findByFacebookID(profile.id, (0, _okay2.default)(callback, function (user) {
if (user) {
return updateUserByFacebookProfile(user, profile, callback);
}
_this.create({
username: profile.username || null,
firstName: profile.first_name,
lastName: profile.last_name,
name: profile.name,
email: profile.email,
locale: profile.locale
}, (0, _okay2.default)(callback, function (newUser) {
newUser.addProvider('facebook', profile.id, profile, (0, _okay2.default)(callback, function () {
callback(null, newUser);
}));
}));
}));
}
/**
* Create user by user profile from twitter
* @param {Object} profile Profile from twitter
* @param {Function} callback Callback with created user
*/
function createByTwitter(profile, callback) {
var _this2 = this;
if (!profile.id) {
return callback(new Error('Profile id is undefined'));
}
this.findByTwitterID(profile.id, (0, _okay2.default)(callback, function (user) {
if (user) {
return callback(null, user);
}
_this2.create({
username: profile.username || null,
name: profile.displayName
}, (0, _okay2.default)(callback, function (newUser) {
newUser.addProvider('twitter', profile.id, profile, (0, _okay2.default)(callback, function () {
callback(null, newUser);
}));
}));
}));
}
/**
* Generate access token for actual user
* @param {String} Secret for generating of token
* @param {[Number]} expiresInMinutes
* @param {[Array]} scope List of scopes
* @return {Object} Access token of user
*/
function generateBearerToken(tokenSecret) {
var expiresInMinutes = arguments.length <= 1 || arguments[1] === undefined ? 60 * 24 * 14 : arguments[1];
var scope = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
if (!tokenSecret) {
throw new Error('Token secret is undefined');
}
var data = {
user: this._id.toString()
};
if (scope.length) {
data.scope = scope;
}
var token = _jsonwebtoken2.default.sign(data, tokenSecret, {
expiresInMinutes: expiresInMinutes
});
return {
type: 'Bearer',
value: token
};
}
function isMe(user) {
return user && this._id.toString() === user._id.toString();
}
function findByUsername(username, strict, callback) {
if (typeof strict === 'function') {
callback = strict;
strict = true;
}
if (strict) {
return this.findOne({ username: username }, callback);
}
return this.findOne({ $or: [{ username: username }, { email: username }] }, callback);
}
/**
* Find user by his facebook ID
* @param {String} id Facebook id of user assigned in database
* @param {Function} callback
*/
function findByFacebookID(uid, callback) {
return this.findByProviderUID('facebook', uid, callback);
}
function findByTwitterID(uid, callback) {
return this.findByProviderUID('twitter', uid, callback);
}
function findByProviderUID(providerName, uid, callback) {
var Provider = this.model('Provider');
return Provider.findOne({
nameUID: (0, _provider.genNameUID)(providerName, uid)
}).populate('user').exec((0, _okay2.default)(callback, function (provider) {
if (!provider) {
return callback(null, provider);
}
return callback(null, provider.user);
}));
}
/**
* Find user by his username/email and his password
* @param {String} username Username or email of user
* @param {String} password Password of user
* @param {Function} callback
*/
function findByUsernamePassword(username, password, strict, callback) {
if (typeof strict === 'function') {
callback = strict;
strict = true;
}
return this.findByUsername(username, strict, (0, _okay2.default)(callback, function (user) {
if (!user) {
return callback(null, null);
}
user.comparePassword(password, (0, _okay2.default)(callback, function (isMatch) {
if (!isMatch) {
return callback(null, null);
}
callback(null, user);
}));
}));
}
function addProvider(providerName, providerUID, data, callback) {
var _this3 = this;
var Provider = this.model('Provider');
this.hasProvider(providerName, providerUID, (0, _okay2.default)(callback, function (has) {
if (has) {
return callback(new Error('This provider is already associated to this user'));
}
Provider.create({
user: _this3._id,
name: providerName,
uid: providerUID,
nameUID: (0, _provider.genNameUID)(providerName, providerUID),
data: JSON.stringify(data)
}, callback);
}));
}
function removeProvider(providerName, providerUID, callback) {
var Provider = this.model('Provider');
if (!providerName || !providerUID) {
return callback(new Error('Provider name or uid is undefined'));
}
Provider.remove({
user: this._id,
nameUID: (0, _provider.genNameUID)(providerName, providerUID)
}, callback);
}
function getProvider(providerName, providerUID, callback) {
if (typeof providerUID === 'function') {
callback = providerUID;
providerUID = false;
}
var Provider = this.model('Provider');
var query = {
user: this._id
};
if (!providerUID) {
query.name = providerName;
} else {
query.nameUID = (0, _provider.genNameUID)(providerName, providerUID);
}
return Provider.findOne(query, callback);
}
function hasProvider(providerName, providerUID, callback) {
if (typeof providerUID === 'function') {
callback = providerUID;
providerUID = false;
}
this.getProvider(providerName, providerUID, (0, _okay2.default)(callback, function (provider) {
callback(null, !!provider);
}));
}
/**
* Compare user entered password with stored user's password
* @param {String} candidatePassword
* @param {Function} callback
*/
function comparePassword(candidatePassword, callback) {
_bcryptjs2.default.compare(candidatePassword, this.password, function (err, isMatch) {
if (err) {
return callback(err);
}
callback(null, isMatch);
});
}
function hasPassword() {
return !!this.password;
}
function setPassword(password, callback) {
this.password = password;
return this.save(callback);
}
function hasEmail() {
return !!this.email ? true : false;
}
function setEmail(email, callback) {
this.email = email;
return this.save(callback);
}
function hasUsername() {
return !!this.username;
}
function setUsername(username, callback) {
this.username = username;
return this.save(callback);
}
/*
function incLoginAttempts(callback) {
// if we have a previous lock that has expired, restart at 1
if (this.lockUntil && this.lockUntil < Date.now()) {
return this.update({
$set: { loginAttempts: 1 },
$unset: { lockUntil: 1 }
}, callback);
}
// otherwise we're incrementing
var updates = {
$inc: {
loginAttempts: 1
}
};
// lock the account if we've reached max attempts and it's not locked already
if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) {
updates.$set = {
lockUntil: Date.now() + LOCK_TIME
};
}
return this.update(updates, callback);
}*/
/**
* Create schema for model
* @param {mongoose.Schema} Schema
* @return {mongoose.Schema} User Instance of user schema
*/
function createSchema(Schema) {
// add properties to schema
var schema = new Schema({
firstName: { type: String },
lastName: { type: String },
name: { type: String },
email: { type: String, unique: true, sparse: true },
username: { type: String, unique: true, sparse: true },
password: { type: String },
locale: { type: String },
loginAttempts: { type: Number, required: true, 'default': 0 },
lockUntil: { type: Number }
});
schema.virtual('isLocked').get(function isLocked() {
// check for a future lockUntil timestamp
return !!(this.lockUntil && this.lockUntil > Date.now());
});
schema.pre('validate', function validate(next) {
var user = this;
// update name
if ((user.isModified('firstName') || user.isModified('lastName')) && !user.isModified('name')) {
if (user.firstName && user.lastName) {
user.name = user.firstName + ' ' + user.lastName;
} else {
user.name = user.firstName || user.lastName;
}
}
next();
});
// add preprocess validation
schema.pre('save', function save(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) {
return next();
}
// hash the password using our new salt
_bcryptjs2.default.hash(user.password, SALT_WORK_FACTOR, function (err, hash) {
if (err) {
return next(err);
}
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
// add RBAC permissions
schema.plugin(_mongooseHrbac2.default, {
defaultRole: 'user'
});
// add permalink
schema.plugin(_mongoosePermalink2.default, {
sources: ['name', 'firstName', 'lastName', 'username'],
pathOptions: {
restExclude: true
}
});
schema.plugin(_mongooseJsonSchema2.default, {});
schema.methods.generateBearerToken = generateBearerToken;
// auth
schema.methods.isMe = isMe;
// password
schema.methods.hasPassword = hasPassword;
schema.methods.setPassword = setPassword;
schema.methods.comparePassword = comparePassword;
// schema.methods.incLoginAttempts = incLoginAttempts;
// email
schema.methods.hasEmail = hasEmail;
schema.methods.setEmail = setEmail;
// username
schema.methods.hasUsername = hasUsername;
schema.methods.setUsername = setUsername;
// create
schema.statics.createByFacebook = createByFacebook;
schema.statics.createByTwitter = createByTwitter;
// search
schema.statics.findByUsername = findByUsername;
schema.statics.findByUsernamePassword = findByUsernamePassword;
schema.statics.findByProviderUID = findByProviderUID;
schema.statics.findByFacebookID = findByFacebookID;
schema.statics.findByTwitterID = findByTwitterID;
// providers
schema.methods.addProvider = addProvider;
schema.methods.removeProvider = removeProvider;
schema.methods.getProvider = getProvider;
schema.methods.hasProvider = hasProvider;
schema.methods.toPrivateJSON = toPrivateJSON;
schema.methods.getDisplayName = getDisplayName;
return schema;
}
/*
UserSchema.statics.getAuthenticated = function(username, password, cb) {
this.findOne({ username: username }, function(err, user) {
if (err) {
return cb(err);
}
// make sure the user exists
if (!user) {
return cb(null, null, reasons.NOT_FOUND);
}
// check if the account is currently locked
if (user.isLocked) {
// just increment login attempts if account is already locked
return user.incLoginAttempts(function(err) {
if (err) return cb(err);
return cb(null, null, reasons.MAX_ATTEMPTS);
});
}
// test for a matching password
user.comparePassword(password, function(err, isMatch) {
if (err) return cb(err);
// check if the password was a match
if (isMatch) {
// if there's no lock or failed attempts, just return the user
if (!user.loginAttempts && !user.lockUntil) return cb(null, user);
// reset attempts and lock info
var updates = {
$set: { loginAttempts: 0 },
$unset: { lockUntil: 1 }
};
return user.update(updates, function(err) {
if (err) return cb(err);
return cb(null, user);
});
}
// password is incorrect, so increment login attempts before responding
user.incLoginAttempts(function(err) {
if (err) return cb(err);
return cb(null, null, reasons.PASSWORD_INCORRECT);
});
});
});
};
*/