diff --git a/src/constants.js b/src/constants.js index 02785c48b..803759361 100644 --- a/src/constants.js +++ b/src/constants.js @@ -49,6 +49,10 @@ exports = module.exports = { AUTOUPDATE_PATTERN_NEVER: 'never', + AVATAR_NONE: '', + AVATAR_GRAVATAR: 'gravatar', + AVATAR_CUSTOM: 'custom', // this is not used here just for reference. The field will contain a byte buffer instead of the type string + SECRET_PLACEHOLDER: String.fromCharCode(0x25CF).repeat(8), // also used in dashboard client.js CLOUDRON: CLOUDRON, diff --git a/src/routes/profile.js b/src/routes/profile.js index 497b8bfac..10c617ae7 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -6,7 +6,6 @@ exports = module.exports = { update, getAvatar, setAvatar, - clearAvatar, changePassword, setTwoFactorAuthenticationSecret, enableTwoFactorAuthentication, @@ -16,6 +15,7 @@ exports = module.exports = { const assert = require('assert'), auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), + constants = require('../constants.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, safe = require('safetydance'), @@ -75,10 +75,14 @@ function update(req, res, next) { async function setAvatar(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - if (!req.files.avatar) return next(new HttpError(400, 'avatar is missing')); + let avatar = req.body.avatar; - const avatar = safe.fs.readFileSync(req.files.avatar.path); - if (!avatar) return next(BoxError.toHttpError(new BoxError(BoxError.FS_ERROR, safe.error.message))); + if (req.files && req.files.avatar) { + avatar = safe.fs.readFileSync(req.files.avatar.path); + if (!avatar) return next(BoxError.toHttpError(new BoxError(BoxError.FS_ERROR, safe.error.message))); + } else if (avatar !== constants.AVATAR_GRAVATAR && avatar !== constants.AVATAR_NONE) { + return next(new HttpError(400, `avatar must be a file, ${constants.AVATAR_GRAVATAR} or ${constants.AVATAR_NONE}`)); + } const [error] = await safe(users.setAvatar(req.user.id, avatar)); if (error) return next(BoxError.toHttpError(error)); @@ -86,21 +90,11 @@ async function setAvatar(req, res, next) { next(new HttpSuccess(202, {})); } -async function clearAvatar(req, res, next) { - assert.strictEqual(typeof req.user, 'object'); - - const [error] = await safe(users.setAvatar(req.user.id, null)); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(202, {})); -} - async function getAvatar(req, res, next) { assert.strictEqual(typeof req.params.identifier, 'string'); const [error, avatar] = await safe(users.getAvatar(req.params.identifier)); if (error) return next(BoxError.toHttpError(error)); - if (!avatar) return next(new HttpError(404, 'User not found')); res.send(avatar); } diff --git a/src/server.js b/src/server.js index 56c74b7c4..fdc2e3be7 100644 --- a/src/server.js +++ b/src/server.js @@ -148,8 +148,7 @@ function initializeExpressSync() { router.get ('/api/v1/profile', token, routes.profile.get); router.post('/api/v1/profile', json, token, routes.profile.authorize, routes.profile.update); router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatar); // this is not scoped so it can used directly in img tag - router.post('/api/v1/profile/avatar', json, token, multipart, routes.profile.setAvatar); // avatar is not exposed in LDAP. so it's personal and not locked - router.del ('/api/v1/profile/avatar', token, routes.profile.clearAvatar); + router.post('/api/v1/profile/avatar', json, token, (req, res, next) => { return typeof req.body.avatar === 'string' ? next() : multipart(req, res, next); }, routes.profile.setAvatar); // avatar is not exposed in LDAP. so it's personal and not locked router.post('/api/v1/profile/password', json, token, routes.users.verifyPassword, routes.profile.changePassword); router.post('/api/v1/profile/twofactorauthentication_secret', json, token, routes.profile.setTwoFactorAuthenticationSecret); router.post('/api/v1/profile/twofactorauthentication_enable', json, token, routes.profile.enableTwoFactorAuthentication); diff --git a/src/users.js b/src/users.js index 628cec228..4ebce4b3f 100644 --- a/src/users.js +++ b/src/users.js @@ -192,7 +192,8 @@ function create(username, password, email, displayName, options, auditSource, ca resetToken: '', displayName: displayName, source: source, - role: role + role: role, + avatar: constants.AVATAR_NONE }; userdb.add(user.id, user, function (error) { @@ -782,25 +783,26 @@ function compareRoles(role1, role2) { async function getAvatarUrl(user) { assert.strictEqual(typeof user, 'object'); - const result = await database.query('SELECT avatar FROM users WHERE id = ?', [ user.id ]); - if (result.length === 0) return null; - if (result[0].avatar) return `${settings.dashboardOrigin()}/api/v1/profile/avatar/${user.id}`; + const result = await getAvatar(user.id); + const fallbackUrl = `${settings.dashboardOrigin()}/img/avatar-default-symbolic.svg`; - const emailHash = require('crypto').createHash('md5').update(user.email).digest('hex'); - return `https://www.gravatar.com/avatar/${emailHash}.jpg`; + if (result.toString() === constants.AVATAR_NONE) return fallbackUrl; + else if (result.toString() === constants.AVATAR_GRAVATAR) return `https://www.gravatar.com/avatar/${require('crypto').createHash('md5').update(user.email).digest('hex')}.jpg`; + else if (result) return `${settings.dashboardOrigin()}/api/v1/profile/avatar/${user.id}`; + else return fallbackUrl; } async function getAvatar(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query('SELECT avatar FROM users WHERE id = ?', [ id ]); - if (result.length === 0) return null; + if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'User not found'); return result[0].avatar; } async function setAvatar(id, avatar) { assert.strictEqual(typeof id, 'string'); - assert(avatar === null || Buffer.isBuffer(avatar)); + assert(avatar === constants.AVATAR_NONE || avatar === constants.AVATAR_GRAVATAR || Buffer.isBuffer(avatar)); const result = await database.query('UPDATE users SET avatar=? WHERE id = ?', [ avatar, id ]); if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'User not found');