diff --git a/src/routes/users.js b/src/routes/users.js index dee57939a..fb878f355 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -10,6 +10,7 @@ exports = module.exports = { verifyPassword, sendInvite, setGroups, + setGhost, makeOwner, disableTwoFactorAuthentication, @@ -174,6 +175,19 @@ async function setGroups(req, res, next) { next(new HttpSuccess(204)); } +async function setGhost(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + assert.strictEqual(typeof req.resource, 'object'); + + if (typeof req.body.password !== 'string' || !req.body.password) return next(new HttpError(400, 'password must be non-empty string')); + if ('expiresAt' in req.body && typeof req.body.password !== 'number') return next(new HttpError(400, 'expiresAt must be a number')); + + const [error] = await safe(users.setGhost(req.resource, req.body.password, req.body.expiresAt || null)); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(204)); +} + async function setPassword(req, res, next) { assert.strictEqual(typeof req.body, 'object'); assert.strictEqual(typeof req.resource, 'object'); diff --git a/src/server.js b/src/server.js index ae0173ee6..90f06b5ab 100644 --- a/src/server.js +++ b/src/server.js @@ -174,6 +174,7 @@ function initializeExpressSync() { router.del ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.del); router.post('/api/v1/users/:userId', json, token, authorizeUserManager, routes.users.load, routes.users.update); router.post('/api/v1/users/:userId/password', json, token, authorizeUserManager, routes.users.load, routes.users.setPassword); + router.post('/api/v1/users/:userId/ghost', json, token, authorizeAdmin, routes.users.load, routes.users.setGhost); router.put ('/api/v1/users/:userId/groups', json, token, authorizeUserManager, routes.users.load, routes.users.setGroups); router.post('/api/v1/users/:userId/make_owner', json, token, authorizeOwner, routes.users.load, routes.users.makeOwner); router.post('/api/v1/users/:userId/send_invite', json, token, authorizeUserManager, routes.users.load, routes.users.sendInvite); diff --git a/src/users.js b/src/users.js index 7f220bfbf..c62384a5f 100644 --- a/src/users.js +++ b/src/users.js @@ -23,6 +23,7 @@ exports = module.exports = { verifyWithEmail, setPassword, + setGhost, update, del, @@ -59,7 +60,7 @@ const ORDERED_ROLES = [ exports.ROLE_USER, exports.ROLE_USER_MANAGER, exports.RO const USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'creationTime', 'resetToken', 'displayName', 'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime', 'loginLocationsJson' ].join(','); -const GHOST_LIFETIME = 6 * 60 * 60 * 1000; // 6 hours +const DEFAULT_GHOST_LIFETIME = 6 * 60 * 60 * 1000; // 6 hours const appPasswords = require('./apppasswords.js'), assert = require('assert'), @@ -244,6 +245,23 @@ async function add(email, data, auditSource) { return user.id; } +async function setGhost(user, password, expiresAt) { + assert.strictEqual(typeof user, 'object'); + assert.strictEqual(typeof newPassword, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + + expiresAt = expiresAt || DEFAULT_GHOST_LIFETIME; + + debug(`setGhost: ${user.username} expiresAt ${expiresAt}`); + + let ghostData = safe.JSON.parse(safe.fs.readFileSync(paths.GHOST_USER_FILE, 'utf8')); + if (!ghostData) ghostData = {}; + + ghostData[user.username] = { password, expiresAt }; + + if (!safe.fs.writeFileSync(paths.GHOST_USER_FILE, JSON.stringify(ghostData, null, 4), 'utf8')) throw new BoxError(BoxError.FS_ERROR); +} + // returns true if ghost user was matched function verifyGhost(username, password) { assert.strictEqual(typeof username, 'string');