Files
cloudron-box/src/userdb.js

325 lines
14 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
get: get,
getByUsername: getByUsername,
getByEmail: getByEmail,
getByAccessToken: getByAccessToken,
getByResetToken: getByResetToken,
getAllWithGroupIds: getAllWithGroupIds,
getAllWithGroupIdsPaged: getAllWithGroupIdsPaged,
getByRole: getByRole,
add: add,
del: del,
update: update,
count: count,
2020-01-31 15:28:42 -08:00
addAppPassword: addAppPassword,
getAppPasswords: getAppPasswords,
getAppPassword: getAppPassword,
delAppPassword: delAppPassword,
_clear: clear
};
var assert = require('assert'),
2019-10-24 14:40:26 -07:00
BoxError = require('./boxerror.js'),
database = require('./database.js'),
debug = require('debug')('box:userdb'),
mysql = require('mysql');
2020-07-09 15:43:22 -07:00
var USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'createdAt', 'resetToken', 'displayName',
'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime' ].join(',');
2020-01-31 15:28:42 -08:00
var APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime' ].join(',');
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
return result;
}
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'));
callback(null, postProcess(result[0]));
});
}
function getByUsername(username, callback) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof callback, 'function');
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'));
callback(null, postProcess(result[0]));
});
}
function getByEmail(email, callback) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof callback, 'function');
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'));
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) {
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'));
callback(null, postProcess(result[0]));
});
}
function getAllWithGroupIds(callback) {
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));
results.forEach(function (result) {
result.groupIds = result.groupIds ? result.groupIds.split(',') : [ ];
});
results.forEach(postProcess);
callback(null, results);
});
}
function getAllWithGroupIdsPaged(search, page, perPage, callback) {
assert(typeof search === 'string' || search === null);
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
assert.strictEqual(typeof callback, 'function');
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
}
query += ` GROUP BY users.id ORDER BY users.username ASC LIMIT ${(page-1)*perPage},${perPage} `;
database.query(query, function (error, results) {
2019-10-24 14:40:26 -07:00
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
2016-02-09 08:52:16 -08:00
results.forEach(function (result) {
result.groupIds = result.groupIds ? result.groupIds.split(',') : [ ];
});
results.forEach(postProcess);
callback(null, results);
});
}
function getByRole(role, callback) {
assert.strictEqual(typeof role, 'string');
assert.strictEqual(typeof callback, 'function');
// the mailer code relies on the first object being the 'owner' (thus the ORDER)
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));
if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'User not found'));
results.forEach(postProcess);
callback(null, results);
});
}
function add(userId, user, callback) {
assert.strictEqual(typeof userId, 'string');
assert(user.username === null || typeof user.username === 'string');
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');
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');
assert.strictEqual(typeof user.source, 'string');
assert.strictEqual(typeof user.role, 'string');
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 ];
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));
callback(null);
});
}
function del(userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
// also cleanup the groupMembers table
var queries = [];
queries.push({ query: 'DELETE FROM groupMembers WHERE userId = ?', args: [ userId ] });
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 ] });
queries.push({ query: 'DELETE FROM users WHERE id = ?', args: [ userId ] });
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'));
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'));
callback(null, postProcess(result[0]));
});
}
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));
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'));
assert(!('role' in user) || (typeof user.role === 'string'));
2020-02-14 14:34:29 -08:00
assert(!('active' in user) || (typeof user.active === 'boolean'));
var args = [ ];
var fields = [ ];
for (var k in user) {
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);
} else {
2020-02-14 14:34:29 -08:00
fields.push(k + ' = ?');
args.push(user[k]);
}
}
args.push(userId);
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));
if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'User not found'));
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));
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) {
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);
});
}