2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
get : get ,
getByUsername : getByUsername ,
getByEmail : getByEmail ,
getByAccessToken : getByAccessToken ,
getByResetToken : getByResetToken ,
2016-02-09 09:37:12 -08:00
getAllWithGroupIds : getAllWithGroupIds ,
2019-01-14 16:08:55 +01:00
getAllWithGroupIdsPaged : getAllWithGroupIdsPaged ,
2020-02-21 12:17:06 -08:00
getByRole : getByRole ,
2015-07-20 00:09:47 -07:00
add : add ,
del : del ,
update : update ,
count : count ,
2020-01-31 15:28:42 -08:00
addAppPassword : addAppPassword ,
getAppPasswords : getAppPasswords ,
getAppPassword : getAppPassword ,
delAppPassword : delAppPassword ,
2015-07-20 00:09:47 -07:00
_clear : clear
} ;
var assert = require ( 'assert' ) ,
2019-10-24 14:40:26 -07:00
BoxError = require ( './boxerror.js' ) ,
2015-07-20 00:09:47 -07:00
database = require ( './database.js' ) ,
debug = require ( 'debug' ) ( 'box:userdb' ) ,
2020-02-21 12:17:06 -08:00
mysql = require ( 'mysql' ) ;
2015-07-20 00:09:47 -07:00
2020-07-09 15:43:22 -07:00
var USERS _FIELDS = [ 'id' , 'username' , 'email' , 'fallbackEmail' , 'password' , 'salt' , 'createdAt' , 'resetToken' , 'displayName' ,
2020-03-30 16:47:18 -07:00
'twoFactorAuthenticationEnabled' , 'twoFactorAuthenticationSecret' , 'active' , 'source' , 'role' , 'resetTokenCreationTime' ] . join ( ',' ) ;
2015-07-20 00:09:47 -07:00
2020-01-31 15:28:42 -08:00
var APP _PASSWORD _FIELDS = [ 'id' , 'name' , 'userId' , 'identifier' , 'hashedPassword' , 'creationTime' ] . join ( ',' ) ;
2016-04-05 10:54:09 +02:00
function postProcess ( result ) {
assert . strictEqual ( typeof result , 'object' ) ;
2018-04-25 16:40:17 +02:00
result . twoFactorAuthenticationEnabled = ! ! result . twoFactorAuthenticationEnabled ;
2019-08-08 05:45:56 -07:00
result . active = ! ! result . active ;
2018-04-25 16:40:17 +02:00
2016-04-05 10:54:09 +02:00
return result ;
}
2015-07-20 00:09:47 -07:00
function get ( userId , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users WHERE id = ?' , [ userId ] , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-10-24 20:48:38 -07:00
if ( result . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:54:09 +02:00
callback ( null , postProcess ( result [ 0 ] ) ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
function getByUsername ( username , callback ) {
assert . strictEqual ( typeof username , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2016-04-14 16:25:46 +02:00
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users WHERE username = ?' , [ username ] , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-10-24 20:48:38 -07:00
if ( result . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2016-04-01 21:43:48 +02:00
2016-04-05 10:54:09 +02:00
callback ( null , postProcess ( result [ 0 ] ) ) ;
2016-04-01 21:43:48 +02:00
} ) ;
2015-07-20 00:09:47 -07:00
}
function getByEmail ( email , callback ) {
assert . strictEqual ( typeof email , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2016-04-14 16:25:46 +02:00
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users WHERE email = ?' , [ email ] , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-10-24 20:48:38 -07:00
if ( result . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:54:09 +02:00
callback ( null , postProcess ( result [ 0 ] ) ) ;
2016-01-13 12:28:38 -08:00
} ) ;
}
2020-02-04 17:05:08 +01:00
function getByResetToken ( resetToken , callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof resetToken , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-02-04 17:05:08 +01:00
// empty reset tokens means it does not exist
if ( ! resetToken ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users WHERE resetToken=?' , [ resetToken ] , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-10-24 20:48:38 -07:00
if ( result . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:54:09 +02:00
callback ( null , postProcess ( result [ 0 ] ) ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2016-02-09 09:37:12 -08:00
function getAllWithGroupIds ( callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2016-02-09 08:52:16 -08:00
database . query ( 'SELECT ' + USERS _FIELDS + ',GROUP_CONCAT(groupMembers.groupId) AS groupIds ' +
' FROM users LEFT OUTER JOIN groupMembers ON users.id = groupMembers.userId ' +
2016-04-06 09:08:59 -07:00
' GROUP BY users.id ORDER BY users.username' , function ( error , results ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-01-14 16:08:55 +01:00
results . forEach ( function ( result ) {
result . groupIds = result . groupIds ? result . groupIds . split ( ',' ) : [ ] ;
} ) ;
results . forEach ( postProcess ) ;
callback ( null , results ) ;
} ) ;
}
2019-01-15 17:21:40 +01:00
function getAllWithGroupIdsPaged ( search , page , perPage , callback ) {
assert ( typeof search === 'string' || search === null ) ;
2019-01-14 16:08:55 +01:00
assert . strictEqual ( typeof page , 'number' ) ;
assert . strictEqual ( typeof perPage , 'number' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-15 17:21:40 +01:00
var query = ` SELECT ${ USERS _FIELDS } ,GROUP_CONCAT(groupMembers.groupId) AS groupIds FROM users LEFT OUTER JOIN groupMembers ON users.id = groupMembers.userId ` ;
2019-08-13 15:16:17 +02:00
if ( search ) {
query += ' WHERE ' ;
2019-08-13 15:23:22 +02:00
query += '(LOWER(users.username) LIKE ' + mysql . escape ( ` % ${ search . toLowerCase ( ) } % ` ) + ')' ;
2019-08-13 15:16:17 +02:00
query += ' OR ' ;
2019-08-13 15:23:22 +02:00
query += '(LOWER(users.email) LIKE ' + mysql . escape ( ` % ${ search . toLowerCase ( ) } % ` ) + ')' ;
2019-08-13 15:16:17 +02:00
query += ' OR ' ;
2019-08-13 15:23:22 +02:00
query += '(LOWER(users.displayName) LIKE ' + mysql . escape ( ` % ${ search . toLowerCase ( ) } % ` ) + ')' ;
2019-08-13 15:16:17 +02:00
}
2019-01-15 17:21:40 +01:00
query += ` GROUP BY users.id ORDER BY users.username ASC LIMIT ${ ( page - 1 ) * perPage } , ${ perPage } ` ;
2019-01-14 16:08:55 +01:00
database . query ( query , function ( error , results ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
2016-02-09 08:52:16 -08:00
results . forEach ( function ( result ) {
result . groupIds = result . groupIds ? result . groupIds . split ( ',' ) : [ ] ;
} ) ;
2016-04-05 10:54:09 +02:00
results . forEach ( postProcess ) ;
2015-07-20 00:09:47 -07:00
callback ( null , results ) ;
} ) ;
}
2020-02-21 12:17:06 -08:00
function getByRole ( role , callback ) {
assert . strictEqual ( typeof role , 'string' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-02 14:35:16 -07:00
// the mailer code relies on the first object being the 'owner' (thus the ORDER)
2020-02-21 12:17:06 -08:00
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users WHERE role=? ORDER BY createdAt' , [ role ] , function ( error , results ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2020-02-21 12:17:06 -08:00
if ( results . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:54:09 +02:00
results . forEach ( postProcess ) ;
2015-07-20 00:09:47 -07:00
callback ( null , results ) ;
} ) ;
}
function add ( userId , user , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
2017-02-02 00:23:11 -08:00
assert ( user . username === null || typeof user . username === 'string' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof user . password , 'string' ) ;
assert . strictEqual ( typeof user . email , 'string' ) ;
2018-01-21 14:25:39 +01:00
assert . strictEqual ( typeof user . fallbackEmail , 'string' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof user . salt , 'string' ) ;
assert . strictEqual ( typeof user . createdAt , 'string' ) ;
assert . strictEqual ( typeof user . resetToken , 'string' ) ;
2016-01-19 12:39:54 +01:00
assert . strictEqual ( typeof user . displayName , 'string' ) ;
2019-08-29 22:15:48 +02:00
assert . strictEqual ( typeof user . source , 'string' ) ;
2020-02-21 12:17:06 -08:00
assert . strictEqual ( typeof user . role , 'string' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-07-09 15:43:22 -07:00
const query = 'INSERT INTO users (id, username, password, email, fallbackEmail, salt, createdAt, resetToken, displayName, source, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ;
const args = [ userId , user . username , user . password , user . email , user . fallbackEmail , user . salt , user . createdAt , user . resetToken , user . displayName , user . source , user . role ] ;
2017-02-14 10:42:32 -08:00
2018-01-26 17:56:07 +01:00
database . query ( query , args , function ( error ) {
2019-10-24 14:40:26 -07:00
if ( error && error . code === 'ER_DUP_ENTRY' && error . sqlMessage . indexOf ( 'users_email' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'email already exists' ) ) ;
if ( error && error . code === 'ER_DUP_ENTRY' && error . sqlMessage . indexOf ( 'users_username' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'username already exists' ) ) ;
if ( error && error . code === 'ER_DUP_ENTRY' && error . sqlMessage . indexOf ( 'PRIMARY' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'id already exists' ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
callback ( null ) ;
} ) ;
}
function del ( userId , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2016-02-11 12:02:35 +01:00
// also cleanup the groupMembers table
var queries = [ ] ;
2016-02-11 12:07:43 +01:00
queries . push ( { query : 'DELETE FROM groupMembers WHERE userId = ?' , args : [ userId ] } ) ;
2018-08-27 13:58:02 -07:00
queries . push ( { query : 'DELETE FROM tokens WHERE identifier = ?' , args : [ userId ] } ) ;
2020-01-31 15:28:42 -08:00
queries . push ( { query : 'DELETE FROM appPasswords WHERE userId = ?' , args : [ userId ] } ) ;
2016-02-11 12:02:35 +01:00
queries . push ( { query : 'DELETE FROM users WHERE id = ?' , args : [ userId ] } ) ;
2016-02-11 12:07:43 +01:00
database . transaction ( queries , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error && error . code === 'ER_NO_REFERENCED_ROW_2' ) return callback ( new BoxError ( BoxError . NOT _FOUND , error ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2020-01-31 15:28:42 -08:00
if ( result [ 3 ] . affectedRows !== 1 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
callback ( error ) ;
} ) ;
}
function getByAccessToken ( accessToken , callback ) {
assert . strictEqual ( typeof accessToken , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'getByAccessToken: ' + accessToken ) ;
database . query ( 'SELECT ' + USERS _FIELDS + ' FROM users, tokens WHERE tokens.accessToken = ?' , [ accessToken ] , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2019-10-24 20:48:38 -07:00
if ( result . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:54:09 +02:00
callback ( null , postProcess ( result [ 0 ] ) ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
function clear ( callback ) {
database . query ( 'DELETE FROM users' , function ( error ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
callback ( error ) ;
} ) ;
}
function update ( userId , user , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof user , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-02-14 14:34:29 -08:00
assert ( ! ( 'username' in user ) || ( user . username === null || typeof user . username === 'string' ) ) ;
assert ( ! ( 'email' in user ) || ( typeof user . email === 'string' ) ) ;
assert ( ! ( 'fallbackEmail' in user ) || ( typeof user . fallbackEmail === 'string' ) ) ;
assert ( ! ( 'twoFactorAuthenticationEnabled' in user ) || ( typeof user . twoFactorAuthenticationEnabled === 'boolean' ) ) ;
2020-02-21 12:17:06 -08:00
assert ( ! ( 'role' in user ) || ( typeof user . role === 'string' ) ) ;
2020-02-14 14:34:29 -08:00
assert ( ! ( 'active' in user ) || ( typeof user . active === 'boolean' ) ) ;
2015-07-20 00:09:47 -07:00
var args = [ ] ;
var fields = [ ] ;
for ( var k in user ) {
2020-02-21 12:17:06 -08:00
if ( k === 'twoFactorAuthenticationEnabled' || k === 'active' ) {
2020-02-14 14:34:29 -08:00
fields . push ( k + ' = ?' ) ;
2018-07-26 15:35:41 -07:00
args . push ( user [ k ] ? 1 : 0 ) ;
2016-04-05 10:54:09 +02:00
} else {
2020-02-14 14:34:29 -08:00
fields . push ( k + ' = ?' ) ;
2016-04-05 10:54:09 +02:00
args . push ( user [ k ] ) ;
}
2015-07-20 00:09:47 -07:00
}
args . push ( userId ) ;
2020-02-13 20:45:00 -08:00
database . query ( 'UPDATE users SET ' + fields . join ( ', ' ) + ' WHERE id = ?' , args , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error && error . code === 'ER_DUP_ENTRY' && error . sqlMessage . indexOf ( 'users_email' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'email already exists' ) ) ;
if ( error && error . code === 'ER_DUP_ENTRY' && error . sqlMessage . indexOf ( 'users_username' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'username already exists' ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2020-02-13 20:45:00 -08:00
if ( result . affectedRows !== 1 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'User not found' ) ) ;
2015-07-20 00:09:47 -07:00
return callback ( null ) ;
} ) ;
}
function count ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT COUNT(*) AS total FROM users' , function ( error , result ) {
2019-10-24 14:40:26 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
return callback ( null , result [ 0 ] . total ) ;
} ) ;
}
2020-01-31 15:28:42 -08:00
function getAppPasswords ( userId , callback ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APP _PASSWORD _FIELDS + ' FROM appPasswords WHERE userId = ?' , [ userId ] , function ( error , results ) {
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
callback ( null , results ) ;
} ) ;
}
function getAppPassword ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APP _PASSWORD _FIELDS + ' FROM appPasswords WHERE id = ?' , [ id ] , function ( error , results ) {
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
callback ( null , results [ 0 ] ) ;
} ) ;
}
function addAppPassword ( id , appPassword , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof appPassword , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
const query = 'INSERT INTO appPasswords (id, userId, identifier, name, hashedPassword) VALUES (?, ?, ?, ?, ?)' ;
const args = [ id , appPassword . userId , appPassword . identifier , appPassword . name , appPassword . hashedPassword ] ;
database . query ( query , args , function ( error ) {
2020-05-30 12:57:25 -07:00
if ( error && error . code === 'ER_DUP_ENTRY' && error . message . indexOf ( 'appPasswords_name_userId_identifier' ) !== - 1 ) return callback ( new BoxError ( BoxError . ALREADY _EXISTS , 'name/app combination already exists' ) ) ;
2020-01-31 15:28:42 -08:00
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
function delAppPassword ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'DELETE FROM appPasswords WHERE id = ?' , [ id ] , function ( error , result ) {
if ( error ) return callback ( new BoxError ( BoxError . DATABASE _ERROR , error ) ) ;
if ( result . affectedRows !== 1 ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'password not found' ) ) ;
return callback ( null ) ;
} ) ;
}