diff --git a/src/constants.js b/src/constants.js index 0be32b4bf..0ebb850aa 100644 --- a/src/constants.js +++ b/src/constants.js @@ -14,6 +14,8 @@ exports = module.exports = { ADMIN_CLIENT_ID: 'webadmin', // oauth client id ADMIN_APPID: 'admin', // admin appid (settingsdb) + GHOST_USER_FILE: '/tmp/cloudron_ghost', + DEFAULT_MEMORY_LIMIT: (256 * 1024 * 1024) // see also client.js }; diff --git a/src/test/user-test.js b/src/test/user-test.js index 64aeac4b1..741255d8c 100644 --- a/src/test/user-test.js +++ b/src/test/user-test.js @@ -7,7 +7,9 @@ var async = require('async'), database = require('../database.js'), + constants = require('../constants.js'), expect = require('expect.js'), + fs = require('fs'), groupdb = require('../groupdb.js'), groups = require('../groups.js'), mailer = require('../mailer.js'), @@ -293,6 +295,62 @@ describe('User', function () { done(); }); }); + + it('fails for ghost if not enabled', function (done) { + user.verify(userObject.id, 'foobar', function (error) { + expect(error).to.be.a(UserError); + expect(error.reason).to.equal(UserError.WRONG_PASSWORD); + done(); + }); + }); + + it('fails for ghost with wrong password', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verify(userObject.id, 'foobar', function (error) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.be.a(UserError); + expect(error.reason).to.equal(UserError.WRONG_PASSWORD); + done(); + }); + }); + + it('succeeds for ghost', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verify(userObject.id, 'testpassword', function (error, result) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.equal(null); + expect(result.id).to.equal(userObject.id); + expect(result.username).to.equal(userObject.username); + expect(result.email).to.equal(userObject.email); + expect(result.displayName).to.equal(userObject.displayName); + + done(); + }); + }); + + it('succeeds for normal user password when ghost file exists', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verify(userObject.id, PASSWORD, function (error, result) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.not.be.ok(); + expect(result).to.be.ok(); + + done(); + }); + + }); }); describe('verifyWithUsername', function () { @@ -346,6 +404,40 @@ describe('User', function () { done(); }); }); + + it('fails for ghost with wrong password', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verifyWithUsername(USERNAME, 'foobar', function (error) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.be.a(UserError); + expect(error.reason).to.equal(UserError.WRONG_PASSWORD); + done(); + }); + }); + + it('succeeds for ghost', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verifyWithUsername(USERNAME, 'testpassword', function (error, result) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.equal(null); + expect(result.id).to.equal(userObject.id); + expect(result.username).to.equal(userObject.username); + expect(result.email).to.equal(userObject.email); + expect(result.displayName).to.equal(userObject.displayName); + + done(); + }); + }); }); describe('verifyWithEmail', function () { @@ -399,6 +491,40 @@ describe('User', function () { done(); }); }); + + it('fails for ghost with wrong password', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verifyWithEmail(EMAIL, 'foobar', function (error) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.be.a(UserError); + expect(error.reason).to.equal(UserError.WRONG_PASSWORD); + done(); + }); + }); + + it('succeeds for ghost', function (done) { + var ghost = { }; + ghost[userObject.username] = 'testpassword'; + + fs.writeFileSync(constants.GHOST_USER_FILE, JSON.stringify(ghost), 'utf8'); + + user.verifyWithEmail(EMAIL, 'testpassword', function (error, result) { + fs.unlinkSync(constants.GHOST_USER_FILE); + + expect(error).to.equal(null); + expect(result.id).to.equal(userObject.id); + expect(result.username).to.equal(userObject.username); + expect(result.email).to.equal(userObject.email); + expect(result.displayName).to.equal(userObject.displayName); + + done(); + }); + }); }); describe('retrieving', function () { diff --git a/src/user.js b/src/user.js index 937321643..087b54096 100644 --- a/src/user.js +++ b/src/user.js @@ -26,6 +26,7 @@ exports = module.exports = { var assert = require('assert'), clients = require('./clients.js'), crypto = require('crypto'), + constants = require('./constants.js'), debug = require('debug')('box:user'), DatabaseError = require('./databaseerror.js'), eventlog = require('./eventlog.js'), @@ -34,6 +35,7 @@ var assert = require('assert'), hat = require('hat'), mailer = require('./mailer.js'), mailboxes = require('./mailboxes.js'), + safe = require('safetydance'), tokendb = require('./tokendb.js'), userdb = require('./userdb.js'), util = require('util'), @@ -189,6 +191,23 @@ function createUser(username, password, email, displayName, auditSource, options }); } +// returns true if ghost user was matched +function verifyGhost(username, password) { + assert.strictEqual(typeof username, 'string'); + assert.strictEqual(typeof password, 'string'); + + var ghostFile = safe.fs.readFileSync(constants.GHOST_USER_FILE, 'utf8'); + var ghostData = safe.JSON.parse(ghostFile); + if (!ghostData) return false; + + if (username in ghostData && ghostData[username] === password) { + debug('verifyGhost: matched ghost user'); + return true; + } + + return false; +} + function verify(userId, password, callback) { assert.strictEqual(typeof userId, 'string'); assert.strictEqual(typeof password, 'string'); @@ -198,6 +217,8 @@ function verify(userId, password, callback) { if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); + if (verifyGhost(user.username, password)) return callback(null, user); + var saltBinary = new Buffer(user.salt, 'hex'); crypto.pbkdf2(password, saltBinary, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, function (error, derivedKey) { if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));