Skip to content
This repository was archived by the owner on Aug 30, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/controllers/errors.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ exports.getErrorMessage = function(err) {
if (err.errors[errName].message) message = err.errors[errName].message;
}
}

if(err.message && message.length === 0) {
return err.message;
}
return message;
};
81 changes: 10 additions & 71 deletions app/controllers/users/users.authentication.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,78 +97,17 @@ exports.oauthCallback = function(strategy) {
};
};

/**
* Helper function to save or update a OAuth user profile
*/
exports.saveOAuthUserProfile = function(req, providerUserProfile, done) {
if (!req.user) {
// Define a search query fields
var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField;
var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField;

// Define main provider search query
var mainProviderSearchQuery = {};
mainProviderSearchQuery.provider = providerUserProfile.provider;
mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];

// Define additional provider search query
var additionalProviderSearchQuery = {};
additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];

// Define a search query to find existing user with current provider profile
var searchQuery = {
$or: [mainProviderSearchQuery, additionalProviderSearchQuery]
};

User.findOne(searchQuery, function(err, user) {
if (err) {
return done(err);
} else {
if (!user) {
var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : '');

User.findUniqueUsername(possibleUsername, null, function(availableUsername) {
user = new User({
firstName: providerUserProfile.firstName,
lastName: providerUserProfile.lastName,
username: availableUsername,
displayName: providerUserProfile.displayName,
email: providerUserProfile.email,
provider: providerUserProfile.provider,
providerData: providerUserProfile.providerData
});

// And save the user
user.save(function(err) {
return done(err, user);
});
});
} else {
return done(err, user);
}
}
});
} else {
// User is already logged in, join the provider data to the existing user
var user = req.user;

// Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured
if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) {
// Add the provider data to the additional provider data field
if (!user.additionalProvidersData) user.additionalProvidersData = {};
user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData;

// Then tell mongoose that we've updated the additionalProvidersData field
user.markModified('additionalProvidersData');

// And save the user
user.save(function(err) {
return done(err, user, '/#!/settings/accounts');
});
} else {
return done(new Error('User is already connected using this provider'), user);
}
exports.saveOAuthUserProfile = function(err, user, isNew, done) {
if(err) {
return done(err);
}
if(!user) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing.

return done(new Error('No user returned by Strategy'));
}
if(isNew) {
return done(null, user);
}
done(null, user, '/#!/settings/accounts');
};

/**
Expand Down
13 changes: 12 additions & 1 deletion app/controllers/users/users.profile.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ exports.update = function(req, res) {
user.updated = Date.now();
user.displayName = user.firstName + ' ' + user.lastName;

user.save(function(err) {
user.identities.forEach(function(auth){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing after ).

auth.accessToken = auth.accessToken || null;
auth.refreshToken = auth.refreshToken || null;
auth.providerData = auth.providerData || null;
});

if(req.body.username) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing.

user.removeOtherIdentity('username', req.body.username);
user.setIdentity('username', req.body.username);
}

user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
Expand Down
222 changes: 185 additions & 37 deletions app/models/user.server.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
crypto = require('crypto');
crypto = require('crypto'),
UserModel,
eachAsync = require( 'each-async' );

/**
* A Validation function for local strategy properties
Expand Down Expand Up @@ -41,19 +43,13 @@ var UserSchema = new Schema({
type: String,
trim: true
},
email: {
type: String,
trim: true,
default: '',
validate: [validateLocalStrategyProperty, 'Please fill in your email'],
match: [/.+\@.+\..+/, 'Please fill a valid email address']
},
username: {
type: String,
unique: 'Username already exists',
required: 'Please fill in a username',
trim: true
},
identities : [ {
provider : { type : String },
id : { type : String },
accessToken : { type : String },
refreshToken : { type : String },
providerData : { type : Object }
} ],
password: {
type: String,
default: '',
Expand All @@ -62,12 +58,6 @@ var UserSchema = new Schema({
salt: {
type: String
},
provider: {
type: String,
required: 'Provider is required'
},
providerData: {},
additionalProvidersData: {},
roles: {
type: [{
type: String,
Expand All @@ -91,17 +81,51 @@ var UserSchema = new Schema({
}
});

UserSchema.virtual('email').get(function () {
return this.getIdentity(UserModel.EMAIL);
} ).set(function(email) {
this.setIdentity(UserModel.EMAIL, email);
});

UserSchema.virtual('username').get(function () {
return this.getIdentity(UserModel.USERNAME);
} ).set(function(username) {
this.setIdentity(UserModel.USERNAME, username);
});

/**
* Hook a pre save method to hash the password
*/
UserSchema.pre('save', function(next) {
if (this.password && this.password.length > 6) {
this.salt = crypto.randomBytes(16).toString('base64');
this.password = this.hashPassword(this.password);
}

next();
});
UserSchema.pre( 'save', function ( next ) {
if ( this.password && this.password.length > 6 ) {
this.salt = new Buffer( crypto.randomBytes( 16 ).toString( 'base64' ), 'base64' );
this.password = this.hashPassword( this.password );
}
if ( !this.identities || this.identities.length === 0 ) {
return next();
}
var _this = this;
eachAsync( _this.identities, function ( identity, index, done ) {
UserModel.findByProvider( identity.provider, identity.id, function ( err, user ) {
if ( err ) {
return next( err );
}
if ( user === null ) {
return done();
}
if ( !_this._id || _this._id.toString() !== user._id.toString() ) {
return next( new Error( identity.provider + ' : ' + identity.id + ' is assinged to other user' ) );
}
return done();
} );
}, function ( error ) {
if ( error ) {
return next( error );
}
next();
} );
} );

/**
* Create instance method for hashing a password
Expand All @@ -121,26 +145,150 @@ UserSchema.methods.authenticate = function(password) {
return this.password === this.hashPassword(password);
};

/**
* Create instance method for authenticating user
*/
UserSchema.methods.setIdentity = function(provider, id, accessToken, refreshToken, providerData) {
var found = false;
var auth = {
provider: provider,
id : id
};
if(!this.identities) {
this.identities = [];
}
this.identities.forEach(function(identity) {
if(identity.provider === provider && identity.id === id) {
auth = identity;
found = true;
}
});
auth.accessToken = accessToken || null;
auth.refreshToken = refreshToken || null;
auth.providerData = providerData || null;
if(!found) {
this.identities.push(auth);
}
};

UserSchema.methods.getIdentity = function(provider, id) {
var auth = null;
if ( this.identities ) {
this.identities.forEach( function ( identity ) {
if ( identity.provider === provider && (identity.id === id || id === undefined || id === null )) {
auth = identity;
}
} );
}
return auth;
};

UserSchema.methods.removeIdentity = function ( provider, id ) {
if ( this.identities ) {
var c = this.identities.length - 1;
while ( c >= 0 ) {
if ( this.identities[ c ] && this.identities[ c ].provider === provider && this.identities[ c ] === id ) {
this.identities = this.identities.splice( c, 1 );
}
c--;
}
}
};


UserSchema.methods.removeOtherIdentity = function ( provider, id ) {
if ( this.identities ) {
this.identities.forEach(function(auth,idx) {
if ( auth.provider === provider && auth.id !== id ) {
this.identities.splice( idx, 1 );
}
});
}
};



/**
* Find possible not used username
*/
UserSchema.statics.findByProvider = function ( provider, id, callback ) {
var _this = this;

var providerQuery = provider;

if(Array.isArray(provider)) {
providerQuery = {
'$in' : provider
};
}

_this.findOne( {
identities : {
$elemMatch : {
provider : providerQuery,
id : id
}
}
}, function ( err, user ) {
if ( !err ) {
return callback( null, user );
} else {
callback( null );
}
} );
};

/**
* Find possible not used username
*/
UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
var _this = this;
var possibleUsername = username + (suffix || '');

_this.findOne({
username: possibleUsername
}, function(err, user) {
if (!err) {
if (!user) {
callback(possibleUsername);
_this.findByProvider( UserModel.USERNAME, possibleUsername, function ( err, user ) {
if ( !err ) {
if ( !user ) {
callback( possibleUsername );
} else {
return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
return _this.findUniqueUsername( username, (suffix || 0) + 1, callback );
}
} else {
callback(null);
callback( null );
}
});
} );
};

mongoose.model('User', UserSchema);
UserSchema.statics.oAuthHandle = function ( currentUser, provider, id, accessToken, refreshToken, providerData, userData, callback ) {
if ( !currentUser ) {
UserModel.findByProvider( provider, id, function ( err, user ) {
if ( err ) {
return callback( err );
}
if ( !user ) {
var possibleUsername = userData.username || ((userData.email) ? userData.email.split( '@' )[ 0 ] : '');
return UserModel.findUniqueUsername( possibleUsername, null, function ( availableUsername ) {
user = new UserModel( userData );
user.setIdentity( provider, id, accessToken, refreshToken, providerData );
user.save( function ( err ) {
return callback( err, user, true );
} );
} );
}
return callback( new Error( 'You shoun\'t go to that line, ever' ), user, false );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it means if execution reaches this line?

} );

} else {
currentUser.setIdentity( provider, id, accessToken, refreshToken, providerData );
return currentUser.save( function ( err ) {
return callback( err, currentUser, '/#!/settings/accounts' );
} );
}
};




UserSchema.statics.USERNAME = 'username';
UserSchema.statics.EMAIL = 'email';

UserModel = mongoose.model('User', UserSchema);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep it consistent as User.

3 changes: 2 additions & 1 deletion app/routes/users.server.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ module.exports = function(app) {
// Setting up the users profile api
app.route('/users/me').get(users.me);
app.route('/users').put(users.update);
app.route('/users/accounts').delete(users.removeOAuthProvider);
app.route( '/users/:userId' ).put( users.update );
app.route('/users/accounts').delete(users.removeOAuthProvider);

// Setting up the users password api
app.route('/users/password').post(users.changePassword);
Expand Down
Loading