diff --git a/migrations/20210517141957-users-rename-createdAt-to-creationTime.js b/migrations/20210517141957-users-rename-createdAt-to-creationTime.js new file mode 100644 index 000000000..0499506c0 --- /dev/null +++ b/migrations/20210517141957-users-rename-createdAt-to-creationTime.js @@ -0,0 +1,33 @@ +'use strict'; + +const async = require('async'); + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE users ADD COLUMN creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', function (error) { + if (error) return callback(error); + + db.runSql('ALTER TABLE users ADD INDEX creationTime_index (creationTime)', function (error) { + if (error) return callback(error); + + db.all('SELECT id, createdAt FROM users', function (error, results) { + if (error) return callback(error); + + async.eachSeries(results, function (r, iteratorDone) { + const creationTime = new Date(r.createdAt); + db.runSql('UPDATE users SET creationTime=? WHERE id=?', [ creationTime, r.id ], iteratorDone); + }, function (error) { + if (error) return callback(error); + + db.runSql('ALTER TABLE users DROP COLUMN createdAt', callback); + }); + }); + }); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE users DROP COLUMN creationTime', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index 95ef488ff..0c096355e 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS users( email VARCHAR(254) NOT NULL UNIQUE, password VARCHAR(1024) NOT NULL, salt VARCHAR(512) NOT NULL, - createdAt VARCHAR(512) NOT NULL, + creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, displayName VARCHAR(512) DEFAULT "", fallbackEmail VARCHAR(512) DEFAULT "", @@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS users( avatar MEDIUMBLOB, locationJson TEXT, // { locations: [{ ip, userAgent, city, country, ts }] } + INDEX creationTime_index (creationTime), PRIMARY KEY(id)); CREATE TABLE IF NOT EXISTS userGroups( diff --git a/src/userdb.js b/src/userdb.js index 40b54d4e1..8872198ea 100644 --- a/src/userdb.js +++ b/src/userdb.js @@ -24,14 +24,14 @@ exports = module.exports = { _clear: clear }; -var assert = require('assert'), +const assert = require('assert'), BoxError = require('./boxerror.js'), database = require('./database.js'), mysql = require('mysql'), safe = require('safetydance'); // the avatar field is special and not added here to reduce response sizes -const USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'createdAt', 'resetToken', 'displayName', +const USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'creationTime', 'resetToken', 'displayName', 'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime', 'loginLocationsJson' ].join(','); const APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime' ].join(','); @@ -155,7 +155,7 @@ function getByRole(role, callback) { 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) { + database.query('SELECT ' + USERS_FIELDS + ' FROM users WHERE role=? ORDER BY creationTime', [ role ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'User not found')); @@ -172,15 +172,14 @@ function add(userId, user, callback) { assert.strictEqual(typeof user.email, 'string'); assert.strictEqual(typeof user.fallbackEmail, 'string'); assert.strictEqual(typeof user.salt, 'string'); - assert.strictEqual(typeof user.createdAt, 'string'); assert.strictEqual(typeof user.resetToken, 'string'); assert.strictEqual(typeof user.displayName, 'string'); assert.strictEqual(typeof user.source, 'string'); assert.strictEqual(typeof user.role, 'string'); assert.strictEqual(typeof callback, 'function'); - 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 ]; + const query = 'INSERT INTO users (id, username, password, email, fallbackEmail, salt, resetToken, displayName, source, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + const args = [ userId, user.username, user.password, user.email, user.fallbackEmail, user.salt, user.resetToken, user.displayName, user.source, user.role ]; database.query(query, args, function (error) { if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('users_email') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'email already exists')); diff --git a/src/users.js b/src/users.js index 73a8c5323..997cbea55 100644 --- a/src/users.js +++ b/src/users.js @@ -154,7 +154,7 @@ function create(username, password, email, displayName, options, auditSource, ca const source = options.source || ''; // empty is local user const role = options.role || exports.ROLE_USER; - var error; + let error; if (username !== null) { username = username.toLowerCase(); @@ -185,15 +185,13 @@ function create(username, password, email, displayName, options, auditSource, ca crypto.pbkdf2(password, salt, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) { if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error)); - var now = (new Date()).toISOString(); - var user = { + const user = { id: 'uid-' + uuid.v4(), username: username, email: email, fallbackEmail: email, password: Buffer.from(derivedKey, 'binary').toString('hex'), salt: salt.toString('hex'), - createdAt: now, resetToken: '', displayName: displayName, source: source,