2013-06-25 15:43:15 +04:00
var User ,
Users ,
2013-09-24 14:46:30 +04:00
_ = require ( 'underscore' ) ,
uuid = require ( 'node-uuid' ) ,
when = require ( 'when' ) ,
errors = require ( '../errorHandling' ) ,
nodefn = require ( 'when/node/function' ) ,
2013-10-23 17:00:28 +04:00
bcrypt = require ( 'bcryptjs' ) ,
2013-09-24 14:46:30 +04:00
Posts = require ( './post' ) . Posts ,
2013-09-23 02:20:08 +04:00
ghostBookshelf = require ( './base' ) ,
2013-09-24 14:46:30 +04:00
Role = require ( './role' ) . Role ,
2013-11-11 23:55:22 +04:00
Permission = require ( './permission' ) . Permission ,
http = require ( 'http' ) ,
crypto = require ( 'crypto' ) ;
2013-06-25 15:43:15 +04:00
2013-08-20 22:52:44 +04:00
function validatePasswordLength ( password ) {
try {
2013-09-23 02:20:08 +04:00
ghostBookshelf . validator . check ( password , "Your must be at least 8 characters long." ) . len ( 8 ) ;
2013-08-20 22:52:44 +04:00
} catch ( error ) {
return when . reject ( error ) ;
}
return when . resolve ( ) ;
}
2013-09-23 02:20:08 +04:00
User = ghostBookshelf . Model . extend ( {
2013-06-25 15:43:15 +04:00
tableName : 'users' ,
2013-08-25 14:49:31 +04:00
permittedAttributes : [
2013-09-14 23:01:46 +04:00
'id' , 'uuid' , 'name' , 'slug' , 'password' , 'email' , 'image' , 'cover' , 'bio' , 'website' , 'location' ,
2013-11-03 21:13:19 +04:00
'accessibility' , 'status' , 'language' , 'meta_title' , 'meta_description' , 'last_login' , 'created_at' ,
'created_by' , 'updated_at' , 'updated_by'
2013-08-25 14:49:31 +04:00
] ,
2013-08-20 22:52:44 +04:00
validate : function ( ) {
2013-09-23 02:20:08 +04:00
ghostBookshelf . validator . check ( this . get ( 'email' ) , "Please enter a valid email address. That one looks a bit dodgy." ) . isEmail ( ) ;
ghostBookshelf . validator . check ( this . get ( 'bio' ) , "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters." ) . len ( 0 , 200 ) ;
2013-09-14 23:01:46 +04:00
if ( this . get ( 'website' ) && this . get ( 'website' ) . length > 0 ) {
2013-09-23 02:20:08 +04:00
ghostBookshelf . validator . check ( this . get ( 'website' ) , "Looks like your website is not actually a website. Try again?" ) . isUrl ( ) ;
2013-08-20 22:52:44 +04:00
}
2013-10-17 15:22:24 +04:00
ghostBookshelf . validator . check ( this . get ( 'location' ) , 'This seems a little too long! Please try and keep your location under 150 characters.' ) . len ( 0 , 150 ) ;
2013-08-20 22:52:44 +04:00
return true ;
} ,
2013-09-14 23:01:46 +04:00
creating : function ( ) {
var self = this ;
2013-08-25 14:49:31 +04:00
2013-09-23 02:20:08 +04:00
ghostBookshelf . Model . prototype . creating . call ( this ) ;
2013-09-14 23:01:46 +04:00
if ( ! this . get ( 'slug' ) ) {
// Generating a slug requires a db call to look for conflicting slugs
return this . generateSlug ( User , this . get ( 'name' ) )
. then ( function ( slug ) {
self . set ( { slug : slug } ) ;
} ) ;
}
2013-08-25 14:49:31 +04:00
} ,
2013-10-07 21:02:57 +04:00
saving : function ( ) {
2013-10-09 22:11:29 +04:00
this . set ( 'name' , this . sanitize ( 'name' ) ) ;
this . set ( 'email' , this . sanitize ( 'email' ) ) ;
this . set ( 'location' , this . sanitize ( 'location' ) ) ;
this . set ( 'website' , this . sanitize ( 'website' ) ) ;
this . set ( 'bio' , this . sanitize ( 'bio' ) ) ;
2013-10-07 21:02:57 +04:00
2013-09-23 02:20:08 +04:00
return ghostBookshelf . Model . prototype . saving . apply ( this , arguments ) ;
2013-10-07 21:02:57 +04:00
} ,
2013-06-25 15:43:15 +04:00
posts : function ( ) {
return this . hasMany ( Posts , 'created_by' ) ;
} ,
roles : function ( ) {
return this . belongsToMany ( Role ) ;
} ,
permissions : function ( ) {
return this . belongsToMany ( Permission ) ;
}
} , {
/ * *
* Naive user add
* @ param _user
*
* Hashes the password provided before saving to the database .
* /
add : function ( _user ) {
2013-08-16 03:22:08 +04:00
var self = this ,
// Clone the _user so we don't expose the hashed password unnecessarily
userData = _ . extend ( { } , _user ) ;
2013-06-01 18:47:41 +04:00
/ * *
2013-06-25 15:43:15 +04:00
* This only allows one user to be added to the database , otherwise fails .
* @ param { object } user
* @ author javorszky
2013-06-01 18:47:41 +04:00
* /
2013-08-20 22:52:44 +04:00
return validatePasswordLength ( userData . password ) . then ( function ( ) {
return self . forge ( ) . fetch ( ) ;
} ) . then ( function ( user ) {
2013-08-16 03:22:08 +04:00
// Check if user exists
2013-06-25 15:43:15 +04:00
if ( user ) {
return when . reject ( new Error ( 'A user is already registered. Only one user for now!' ) ) ;
}
2013-08-20 22:52:44 +04:00
} ) . then ( function ( ) {
2013-10-23 17:00:28 +04:00
// Generate a new salt
return nodefn . call ( bcrypt . genSalt ) ;
} ) . then ( function ( salt ) {
2013-08-16 03:22:08 +04:00
// Hash the provided password with bcrypt
2013-10-23 17:00:28 +04:00
return nodefn . call ( bcrypt . hash , _user . password , salt ) ;
2013-08-16 03:22:08 +04:00
} ) . then ( function ( hash ) {
// Assign the hashed password
userData . password = hash ;
2013-11-11 23:55:22 +04:00
// LookupGravatar
return self . gravatarLookup ( userData ) ;
} ) . then ( function ( userData ) {
2013-08-16 03:22:08 +04:00
// Save the user with the hashed password
2013-09-23 02:20:08 +04:00
return ghostBookshelf . Model . add . call ( self , userData ) ;
2013-08-16 03:22:08 +04:00
} ) . then ( function ( addedUser ) {
// Assign the userData to our created user so we can pass it back
userData = addedUser ;
// Add this user to the admin role (assumes admin = role_id: 1)
2013-09-13 18:06:17 +04:00
return userData . roles ( ) . attach ( 1 ) ;
2013-08-16 03:22:08 +04:00
} ) . then ( function ( addedUserRole ) {
2013-10-31 22:02:34 +04:00
/*jslint unparam:true*/
2013-08-16 03:22:08 +04:00
// Return the added user as expected
2013-09-01 02:20:12 +04:00
2013-08-16 03:22:08 +04:00
return when . resolve ( userData ) ;
2013-08-19 01:50:42 +04:00
} ) ;
2013-06-01 18:47:41 +04:00
2013-06-25 15:43:15 +04:00
/ * *
* Temporarily replacing the function below with another one that checks
* whether there ' s anyone registered at all . This is due to # 138
* @ author javorszky
* /
2013-06-01 18:47:41 +04:00
2013-09-14 23:01:46 +04:00
// return this.forge({email: userData.email}).fetch().then(function (user) {
2013-08-09 05:22:49 +04:00
// if (user !== null) {
// return when.reject(new Error('A user with that email address already exists.'));
// }
// return nodefn.call(bcrypt.hash, _user.password, null, null).then(function (hash) {
// userData.password = hash;
2013-09-23 02:20:08 +04:00
// ghostBookshelf.Model.add.call(UserRole, userRoles);
// return ghostBookshelf.Model.add.call(User, userData);
2013-08-09 05:22:49 +04:00
// }, errors.logAndThrowError);
// }, errors.logAndThrowError);
2013-06-25 15:43:15 +04:00
} ,
2013-08-06 23:27:56 +04:00
// Finds the user by email, and checks the password
2013-06-25 15:43:15 +04:00
check : function ( _userdata ) {
return this . forge ( {
2013-09-14 23:01:46 +04:00
email : _userdata . email
2013-06-25 15:43:15 +04:00
} ) . fetch ( { require : true } ) . then ( function ( user ) {
return nodefn . call ( bcrypt . compare , _userdata . pw , user . get ( 'password' ) ) . then ( function ( matched ) {
if ( ! matched ) {
2013-08-20 22:52:44 +04:00
return when . reject ( new Error ( 'Your password is incorrect' ) ) ;
2013-06-25 15:43:15 +04:00
}
return user ;
2013-06-17 01:36:28 +04:00
} , errors . logAndThrowError ) ;
2013-08-09 05:22:49 +04:00
} , function ( error ) {
2013-10-31 22:02:34 +04:00
/*jslint unparam:true*/
2013-08-19 01:50:42 +04:00
return when . reject ( new Error ( 'There is no user with that email address.' ) ) ;
2013-08-09 05:22:49 +04:00
} ) ;
2013-06-25 15:43:15 +04:00
} ,
2013-08-06 03:49:06 +04:00
/ * *
* Naive change password method
* @ param { object } _userdata email , old pw , new pw , new pw2
*
* /
changePassword : function ( _userdata ) {
2013-08-20 22:52:44 +04:00
var self = this ,
userid = _userdata . currentUser ,
2013-08-06 03:49:06 +04:00
oldPassword = _userdata . oldpw ,
newPassword = _userdata . newpw ,
2013-09-01 02:20:12 +04:00
ne2Password = _userdata . ne2pw ,
user = null ;
2013-08-06 03:49:06 +04:00
if ( newPassword !== ne2Password ) {
2013-08-20 22:52:44 +04:00
return when . reject ( new Error ( 'Your new passwords do not match' ) ) ;
2013-08-06 03:49:06 +04:00
}
2013-08-20 22:52:44 +04:00
return validatePasswordLength ( newPassword ) . then ( function ( ) {
return self . forge ( { id : userid } ) . fetch ( { require : true } ) ;
2013-09-01 02:20:12 +04:00
} ) . then ( function ( _user ) {
user = _user ;
return nodefn . call ( bcrypt . compare , oldPassword , user . get ( 'password' ) ) ;
} ) . then ( function ( matched ) {
if ( ! matched ) {
return when . reject ( new Error ( 'Your password is incorrect' ) ) ;
}
2013-10-23 17:00:28 +04:00
return nodefn . call ( bcrypt . genSalt ) ;
} ) . then ( function ( salt ) {
return nodefn . call ( bcrypt . hash , newPassword , salt ) ;
2013-09-01 02:20:12 +04:00
} ) . then ( function ( hash ) {
user . save ( { password : hash } ) ;
return user ;
2013-08-06 03:49:06 +04:00
} ) ;
2013-09-01 02:20:12 +04:00
} ,
forgottenPassword : function ( email ) {
var newPassword = Math . random ( ) . toString ( 36 ) . slice ( 2 , 12 ) , // This is magick
user = null ;
2013-08-20 22:52:44 +04:00
2013-09-14 23:01:46 +04:00
return this . forge ( { email : email } ) . fetch ( { require : true } ) . then ( function ( _user ) {
2013-09-01 02:20:12 +04:00
user = _user ;
2013-10-23 17:00:28 +04:00
return nodefn . call ( bcrypt . genSalt ) ;
} ) . then ( function ( salt ) {
return nodefn . call ( bcrypt . hash , newPassword , salt ) ;
2013-09-01 02:20:12 +04:00
} ) . then ( function ( hash ) {
user . save ( { password : hash } ) ;
return { user : user , newPassword : newPassword } ;
} , function ( error ) {
2013-10-31 22:02:34 +04:00
/*jslint unparam:true*/
2013-09-01 02:20:12 +04:00
return when . reject ( new Error ( 'There is no user by that email address. Check again.' ) ) ;
} ) ;
2013-08-06 03:49:06 +04:00
} ,
2013-06-25 15:43:15 +04:00
effectivePermissions : function ( id ) {
return this . read ( { id : id } , { withRelated : [ 'permissions' , 'roles.permissions' ] } )
. then ( function ( foundUser ) {
var seenPerms = { } ,
rolePerms = _ . map ( foundUser . related ( 'roles' ) . models , function ( role ) {
return role . related ( 'permissions' ) . models ;
} ) ,
allPerms = [ ] ;
rolePerms . push ( foundUser . related ( 'permissions' ) . models ) ;
_ . each ( rolePerms , function ( rolePermGroup ) {
_ . each ( rolePermGroup , function ( perm ) {
var key = perm . get ( 'action_type' ) + '-' + perm . get ( 'object_type' ) + '-' + perm . get ( 'object_id' ) ;
// Only add perms once
if ( seenPerms [ key ] ) {
return ;
}
allPerms . push ( perm ) ;
seenPerms [ key ] = true ;
2013-06-05 07:47:11 +04:00
} ) ;
2013-06-25 15:43:15 +04:00
} ) ;
2013-06-05 07:47:11 +04:00
2013-06-25 15:43:15 +04:00
return when . resolve ( allPerms ) ;
} , errors . logAndThrowError ) ;
2013-11-11 23:55:22 +04:00
} ,
gravatarLookup : function ( userData ) {
var gravatarUrl = 'http://www.gravatar.com/avatar/' +
crypto . createHash ( 'md5' ) . update ( userData . email . toLowerCase ( ) . trim ( ) ) . digest ( 'hex' ) +
"?d=404" ,
checkPromise = when . defer ( ) ;
http . get ( gravatarUrl , function ( res ) {
if ( res . statusCode !== 404 ) {
userData . image = gravatarUrl ;
}
checkPromise . resolve ( userData ) ;
} ) . on ( 'error' , function ( ) {
//Error making request just continue.
checkPromise . resolve ( userData ) ;
} ) ;
return checkPromise . promise ;
2013-06-25 15:43:15 +04:00
}
2013-06-01 18:47:41 +04:00
2013-06-25 15:43:15 +04:00
} ) ;
2013-06-01 18:47:41 +04:00
2013-09-23 02:20:08 +04:00
Users = ghostBookshelf . Collection . extend ( {
2013-06-25 15:43:15 +04:00
model : User
} ) ;
2013-06-01 18:47:41 +04:00
2013-06-25 15:43:15 +04:00
module . exports = {
User : User ,
Users : Users
2013-08-06 03:49:06 +04:00
} ;