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' ) ,
moment = require ( 'moment' ) ;
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-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 : ''
} ,
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-04-19 19:20:46 +08:00
seeded : {
type : Number ,
default : 0
} ,
leeched : {
type : Number ,
default : 0
} ,
finished : {
type : Number ,
default : 0
} ,
2015-07-25 16:53:11 -04:00
updated : {
type : Date
} ,
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
2017-05-03 18:20:31 +08:00
/ * *
* overwrite toJSON
* /
UserSchema . methods . toJSON = function ( options ) {
var document = this . toObject ( options ) ;
2017-05-12 16:38:02 +08:00
document . isVip = false ;
2017-05-03 18:20:31 +08:00
if ( ! document . vip _start _at || ! document . vip _end _at ) {
2017-05-12 16:38:02 +08:00
document . isVip = false ;
2017-05-03 18:20:31 +08:00
} else if ( moment ( Date . now ( ) ) > moment ( document . vip _end _at ) ) {
2017-05-12 16:38:02 +08:00
document . isVip = false ;
2017-05-03 18:20:31 +08:00
} else {
2017-05-12 16:38:02 +08:00
document . isVip = true ;
2017-05-03 18:20:31 +08:00
}
2017-05-12 16:38:02 +08:00
document . isOper = ( document . roles [ 0 ] === 'oper' || document . roles [ 0 ] === 'admin' ) ;
document . isAdmin = ( document . roles [ 0 ] === 'admin' ) ;
2017-05-03 18:20:31 +08:00
return document ;
} ;
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 ) {
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
2015-09-02 23:21:24 -04:00
next ( ) ;
} ) ;
2017-05-03 18:20:31 +08:00
/ * *
* Hook a post save method to set the ratio
* /
UserSchema . post ( 'save' , function ( doc ) {
if ( doc . uploaded === 0 || doc . downloaded === 0 ) {
doc . ratio = 0 ;
} else {
doc . ratio = Math . round ( ( doc . uploaded / doc . downloaded ) * 100 ) / 100 ;
}
} ) ;
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
/ * *
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 ) ;
}
} ) ;
} ;
2015-02-07 12:31:07 -08:00
mongoose . model ( 'User' , UserSchema ) ;