2013-12-25 16:36:33 +02:00
'use strict' ;
2013-05-22 17:03:50 +03:00
/ * *
2016-01-07 22:18:36 +01:00
* Module dependencies
2013-05-22 17:03:50 +03:00
* /
2013-08-16 15:23:09 -04:00
var mongoose = require ( 'mongoose' ) ,
2016-09-07 19:16:11 -07:00
path = require ( 'path' ) ,
config = require ( path . resolve ( './config/config' ) ) ,
2015-07-25 16:53:11 -04:00
Schema = mongoose . Schema ,
crypto = require ( 'crypto' ) ,
2015-09-02 23:21:24 -04:00
validator = require ( 'validator' ) ,
2015-09-17 19:20:58 -07:00
generatePassword = require ( 'generate-password' ) ,
2017-05-03 18:20:31 +08:00
owasp = require ( 'owasp-password-strength-test' ) ,
2017-08-22 13:35:29 +08:00
moment = require ( 'moment' ) ,
2017-08-13 16:29:47 -07:00
chalk = require ( 'chalk' ) ;
2013-05-22 17:03:50 +03:00
2016-09-12 11:53:22 -07:00
owasp . config ( config . shared . owasp ) ;
2016-09-07 19:16:11 -07:00
2014-03-26 01:11:24 +02:00
/ * *
* A Validation function for local strategy properties
* /
2015-07-25 16:53:11 -04:00
var validateLocalStrategyProperty = function ( property ) {
return ( ( this . provider !== 'local' && ! this . updated ) || property . length ) ;
2014-03-26 01:11:24 +02:00
} ;
2015-07-17 09:27:16 +02:00
/ * *
* A Validation function for local strategy email
* /
2015-07-25 16:53:11 -04:00
var validateLocalStrategyEmail = function ( email ) {
2017-04-16 17:46:15 +08:00
return ( ( this . provider !== 'local' && ! this . updated ) || validator . isEmail ( email , { require _tld : false } ) ) ;
2014-03-26 01:11:24 +02:00
} ;
2016-10-19 23:40:26 -04:00
/ * *
* A Validation function for username
* - at least 3 characters
* - only a - z0 - 9_ - .
* - contain at least one alphanumeric character
* - not in list of illegal usernames
* - no consecutive dots : "." ok , ".." nope
* - not begin or end with "."
* /
2017-04-16 17:46:15 +08:00
var validateUsername = function ( username ) {
2016-10-19 23:40:26 -04:00
var usernameRegex = /^(?=[\w.-]+$)(?!.*[._-]{2})(?!\.)(?!.*\.$).{3,34}$/ ;
return (
this . provider !== 'local' ||
( username && usernameRegex . test ( username ) && config . illegalUsernames . indexOf ( username ) < 0 )
) ;
} ;
2013-05-22 17:03:50 +03:00
/ * *
* User Schema
* /
var UserSchema = new Schema ( {
2015-07-25 16:53:11 -04:00
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 ,
2017-03-25 16:05:26 +08:00
//index: {
// unique: true,
// sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
//},
unique : 'email already exists' ,
required : 'Please fill in a email address' ,
2015-08-31 14:58:40 +03:00
lowercase : true ,
trim : true ,
2015-07-25 16:53:11 -04:00
default : '' ,
validate : [ validateLocalStrategyEmail , 'Please fill a valid email address' ]
} ,
username : {
type : String ,
unique : 'Username already exists' ,
required : 'Please fill in a username' ,
2016-10-19 23:40:26 -04:00
validate : [ validateUsername , 'Please enter a valid username: 3+ characters long, non restricted word, characters "_-.", no consecutive dots, does not begin or end with dots, letters a-z and numbers 0-9.' ] ,
2015-08-31 14:58:40 +03:00
lowercase : true ,
2015-07-25 16:53:11 -04:00
trim : true
} ,
password : {
type : String ,
2015-09-02 23:21:24 -04:00
default : ''
2015-07-25 16:53:11 -04:00
} ,
2017-04-08 04:01:45 +08:00
passkey : {
type : String ,
default : ''
} ,
2015-07-25 16:53:11 -04:00
salt : {
type : String
} ,
profileImageURL : {
type : String ,
2015-08-04 16:57:32 -05:00
default : 'modules/users/client/img/profile/default.png'
2015-07-25 16:53:11 -04:00
} ,
provider : {
type : String ,
required : 'Provider is required'
} ,
providerData : { } ,
additionalProvidersData : { } ,
roles : {
type : [ {
type : String ,
2017-04-18 15:32:25 +08:00
enum : config . meanTorrentConfig . userRoles
2015-07-25 16:53:11 -04:00
} ] ,
2017-04-18 15:32:25 +08:00
default : [ 'user' ] ,
2015-08-28 17:41:13 -07:00
required : 'Please provide at least one role'
2015-07-25 16:53:11 -04:00
} ,
2017-09-14 15:18:14 +08:00
isOper : {
type : Boolean ,
default : false
} ,
isAdmin : {
type : Boolean ,
default : false
} ,
2017-04-15 15:48:07 +08:00
status : {
type : String ,
2017-04-18 15:32:25 +08:00
default : 'normal'
} ,
vip _start _at : {
type : Date ,
default : ''
} ,
vip _end _at : {
type : Date ,
default : ''
} ,
2017-09-14 15:18:14 +08:00
isVip : {
type : Boolean ,
default : false
} ,
2017-04-18 15:32:25 +08:00
score : {
type : Number ,
default : 0
2017-04-15 15:48:07 +08:00
} ,
2017-03-25 16:05:26 +08:00
uploaded : {
type : Number ,
default : 0
} ,
downloaded : {
type : Number ,
default : 0
} ,
2017-05-03 18:20:31 +08:00
ratio : {
type : Number ,
default : 0
} ,
2017-09-12 10:53:42 +08:00
uptotal : {
type : Number ,
default : 0
} ,
2017-04-19 19:20:46 +08:00
seeded : {
type : Number ,
default : 0
} ,
leeched : {
type : Number ,
default : 0
} ,
finished : {
type : Number ,
default : 0
} ,
2017-09-07 16:44:13 +08:00
hnr _warning : {
type : Number ,
default : 0
} ,
2017-07-16 14:44:22 +08:00
topics : {
type : Number ,
default : 0
} ,
replies : {
type : Number ,
default : 0
} ,
2015-07-25 16:53:11 -04:00
updated : {
type : Date
} ,
2017-05-26 10:49:17 +08:00
last _signed : {
type : Date
} ,
signed _ip : [ String ] ,
leeched _ip : [ String ] ,
client _agent : [ String ] ,
2017-09-13 14:54:58 +08:00
invited _by : {
type : Schema . Types . ObjectId ,
ref : 'User'
} ,
2015-07-25 16:53:11 -04:00
created : {
type : Date ,
default : Date . now
} ,
/* For reset password */
resetPasswordToken : {
type : String
} ,
resetPasswordExpires : {
type : Date
2017-04-19 19:20:46 +08:00
}
2013-08-16 15:23:09 -04:00
} ) ;
2013-05-22 17:03:50 +03:00
/ * *
2014-03-26 01:11:24 +02:00
* Hook a pre save method to hash the password
2013-05-22 17:03:50 +03:00
* /
2015-07-25 16:53:11 -04:00
UserSchema . pre ( 'save' , function ( next ) {
2017-05-22 14:09:32 +08:00
var user = this ;
2015-09-02 23:21:24 -04:00
if ( this . password && this . isModified ( 'password' ) ) {
2015-07-25 16:53:11 -04:00
this . salt = crypto . randomBytes ( 16 ) . toString ( 'base64' ) ;
this . password = this . hashPassword ( this . password ) ;
}
2014-03-26 01:11:24 +02:00
2017-06-29 18:15:00 +08:00
countRatio ( this ) ;
2017-09-14 15:18:14 +08:00
updateVipFlag ( this ) ;
updateOperAdminFlag ( this ) ;
2017-05-22 14:09:32 +08:00
2017-05-20 23:24:02 +08:00
this . constructor . count ( function ( err , count ) {
if ( err ) {
return next ( err ) ;
}
2017-05-22 14:09:32 +08:00
2017-05-20 23:24:02 +08:00
if ( count === 0 ) {
2017-05-22 14:09:32 +08:00
user . roles = [ 'admin' ] ;
2017-05-20 23:24:02 +08:00
}
2015-09-02 23:21:24 -04:00
2017-05-22 14:09:32 +08:00
next ( ) ;
} ) ;
2017-05-03 18:20:31 +08:00
} ) ;
2017-06-29 18:15:00 +08:00
/ * *
* Hook a pre save method to hash the password
* /
UserSchema . pre ( 'update' , function ( next ) {
countRatio ( this ) ;
2017-09-14 15:18:14 +08:00
updateVipFlag ( this ) ;
updateOperAdminFlag ( this ) ;
2017-06-29 18:15:00 +08:00
next ( ) ;
} ) ;
2017-09-14 15:18:14 +08:00
/ * *
* countRatio
* @ param user
* /
2017-06-29 18:15:00 +08:00
function countRatio ( user ) {
if ( user . uploaded > 0 && user . downloaded === 0 ) {
user . ratio = - 1 ;
} else if ( user . uploaded === 0 || user . downloaded === 0 ) {
user . ratio = 0 ;
} else {
user . ratio = Math . round ( ( user . uploaded / user . downloaded ) * 100 ) / 100 ;
}
}
2017-09-14 15:18:14 +08:00
/ * *
* updateVipFlag
* @ param user
* /
function updateVipFlag ( user ) {
user . isVip = false ;
if ( ! user . vip _start _at || ! user . vip _end _at ) {
user . isVip = false ;
} else if ( moment ( Date . now ( ) ) > moment ( user . vip _end _at ) ) {
user . isVip = false ;
} else {
user . isVip = true ;
}
}
/ * *
* updateOperAdminFlag
* @ param user
* /
function updateOperAdminFlag ( user ) {
user . isOper = false ;
user . isAdmin = false ;
if ( user . roles ) {
user . isOper = ( user . roles [ 0 ] === 'oper' || user . roles [ 0 ] === 'admin' ) ;
user . isAdmin = ( user . roles [ 0 ] === 'admin' ) ;
}
}
2015-09-02 23:21:24 -04:00
/ * *
* Hook a pre validate method to test the local password
* /
UserSchema . pre ( 'validate' , function ( next ) {
2015-10-05 16:41:14 -07:00
if ( this . provider === 'local' && this . password && this . isModified ( 'password' ) ) {
2015-09-02 23:21:24 -04:00
var result = owasp . test ( this . password ) ;
if ( result . errors . length ) {
var error = result . errors . join ( ' ' ) ;
this . invalidate ( 'password' , error ) ;
}
}
2015-07-25 16:53:11 -04:00
next ( ) ;
2013-08-17 01:06:17 +03:00
} ) ;
2013-05-22 17:03:50 +03:00
/ * *
2014-03-26 01:11:24 +02:00
* Create instance method for hashing a password
2013-05-22 17:03:50 +03:00
* /
2015-07-25 16:53:11 -04:00
UserSchema . methods . hashPassword = function ( password ) {
if ( this . salt && password ) {
2016-08-26 10:13:51 +03:00
return crypto . pbkdf2Sync ( password , new Buffer ( this . salt , 'base64' ) , 10000 , 64 , 'SHA1' ) . toString ( 'base64' ) ;
2015-07-25 16:53:11 -04:00
} else {
return password ;
}
2013-08-16 15:23:09 -04:00
} ;
2013-05-22 17:03:50 +03:00
2017-05-26 10:49:17 +08:00
/ * *
* update user last signed time
* /
UserSchema . methods . updateSignedTime = function ( ) {
2017-05-27 17:15:50 +08:00
this . update ( {
$set : { last _signed : Date . now ( ) }
} ) . exec ( ) ;
2017-05-26 10:49:17 +08:00
} ;
/ * *
* update user last signed ip
* @ param ip
* /
UserSchema . methods . addSignedIp = function ( ip ) {
2017-05-26 19:11:12 +08:00
this . update ( {
$addToSet : { signed _ip : ip }
} ) . exec ( ) ;
2017-05-26 10:49:17 +08:00
} ;
/ * *
* update user last leeched ip
* @ param ip
* /
UserSchema . methods . addLeechedIp = function ( ip ) {
2017-05-26 19:11:12 +08:00
this . update ( {
$addToSet : { leeched _ip : ip }
} ) . exec ( ) ;
2017-05-26 10:49:17 +08:00
} ;
/ * *
* update user last client _agent
* @ param ip
* /
UserSchema . methods . addClientAgent = function ( ca ) {
2017-05-26 19:11:12 +08:00
this . update ( {
$addToSet : { client _agent : ca }
} ) . exec ( ) ;
2017-05-26 10:49:17 +08:00
} ;
2013-05-22 17:03:50 +03:00
/ * *
2014-03-26 01:11:24 +02:00
* Create instance method for authenticating user
2013-05-22 17:03:50 +03:00
* /
2015-07-25 16:53:11 -04:00
UserSchema . methods . authenticate = function ( password ) {
return this . password === this . hashPassword ( password ) ;
2014-03-26 01:11:24 +02:00
} ;
2013-05-22 17:03:50 +03:00
2017-04-16 17:46:15 +08:00
/ * *
* create randomString
* @ param length
* @ param chars
* @ returns { string }
* /
UserSchema . methods . randomString = function ( length , chars ) {
if ( ! chars ) {
throw new Error ( 'Argument \'chars\' is undefined' ) ;
}
var charsLength = chars . length ;
if ( charsLength > 256 ) {
throw new Error ( 'Argument \'chars\' should not have more than 256 characters'
+ ', otherwise unpredictability will be broken' ) ;
}
var randomBytes = crypto . randomBytes ( length ) ;
var result = new Array ( length ) ;
var cursor = 0 ;
for ( var i = 0 ; i < length ; i ++ ) {
cursor += randomBytes [ i ] ;
result [ i ] = chars [ cursor % charsLength ] ;
}
return result . join ( '' ) ;
} ;
/ * *
* create randomAsciiString
* @ param length
* @ param chars
* @ returns { string }
* /
UserSchema . methods . randomAsciiString = function ( length ) {
return this . randomString ( length , 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ) ;
} ;
2013-05-22 17:03:50 +03:00
/ * *
2014-03-26 01:11:24 +02:00
* Find possible not used username
2013-05-22 17:03:50 +03:00
* /
2015-07-25 16:53:11 -04:00
UserSchema . statics . findUniqueUsername = function ( username , suffix , callback ) {
var _this = this ;
2015-08-31 14:58:40 +03:00
var possibleUsername = username . toLowerCase ( ) + ( suffix || '' ) ;
2013-08-17 01:06:17 +03:00
2015-07-25 16:53:11 -04:00
_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 ) ;
}
} ) ;
2013-08-16 15:23:09 -04:00
} ;
2013-05-22 17:03:50 +03:00
2015-09-17 19:20:58 -07:00
/ * *
2017-04-16 17:46:15 +08:00
* 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 .
* /
2015-09-17 19:20:58 -07:00
UserSchema . statics . generateRandomPassphrase = function ( ) {
return new Promise ( function ( resolve , reject ) {
var password = '' ;
2015-09-22 04:02:31 -07:00
var repeatingCharacters = new RegExp ( '(.)\\1{2,}' , 'g' ) ;
2015-09-17 19:20:58 -07:00
2016-01-07 22:18:36 +01:00
// 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
2015-09-22 04:02:31 -07:00
while ( password . length < 20 || repeatingCharacters . test ( password ) ) {
2015-09-17 19:20:58 -07:00
// 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 ,
2015-12-10 20:31:51 +01:00
excludeSimilarCharacters : true
2015-09-17 19:20:58 -07:00
} ) ;
2016-01-07 22:18:36 +01:00
// check if we need to remove any repeating characters
2015-09-22 04:02:31 -07:00
password = password . replace ( repeatingCharacters , '' ) ;
2015-09-17 19:20:58 -07:00
}
// 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 ) ;
}
} ) ;
} ;
2017-08-13 16:29:47 -07:00
UserSchema . statics . seed = seed ;
2015-02-07 12:31:07 -08:00
mongoose . model ( 'User' , UserSchema ) ;
2017-08-13 16:29:47 -07:00
/ * *
2017-09-14 15:18:14 +08:00
* Seeds the User collection with document ( User )
* and provided options .
* /
2017-08-13 16:29:47 -07:00
function seed ( doc , options ) {
var User = mongoose . model ( 'User' ) ;
return new Promise ( function ( resolve , reject ) {
skipDocument ( )
. then ( add )
. then ( function ( response ) {
return resolve ( response ) ;
} )
. catch ( function ( err ) {
return reject ( err ) ;
} ) ;
function skipDocument ( ) {
return new Promise ( function ( resolve , reject ) {
User
. findOne ( {
username : doc . username
} )
. exec ( function ( err , existing ) {
if ( err ) {
return reject ( err ) ;
}
if ( ! existing ) {
return resolve ( false ) ;
}
if ( existing && ! options . overwrite ) {
return resolve ( true ) ;
}
// Remove User (overwrite)
existing . remove ( function ( err ) {
if ( err ) {
return reject ( err ) ;
}
return resolve ( false ) ;
} ) ;
} ) ;
} ) ;
}
function add ( skip ) {
return new Promise ( function ( resolve , reject ) {
if ( skip ) {
return resolve ( {
message : chalk . yellow ( 'Database Seeding: User\t\t' + doc . username + ' skipped' )
} ) ;
}
User . generateRandomPassphrase ( )
. then ( function ( passphrase ) {
var user = new User ( doc ) ;
user . provider = 'local' ;
user . displayName = user . firstName + ' ' + user . lastName ;
user . password = passphrase ;
user . save ( function ( err ) {
if ( err ) {
return reject ( err ) ;
}
return resolve ( {
message : 'Database Seeding: User\t\t' + user . username + ' added with password set to ' + passphrase
} ) ;
} ) ;
} )
. catch ( function ( err ) {
return reject ( err ) ;
} ) ;
} ) ;
}
} ) ;
}