diff --git a/src/api/users.js b/src/api/users.js index 1b7863b50b..aeea9c8203 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -1,8 +1,12 @@ 'use strict'; const user = require('../user'); +const groups = require('../groups'); const meta = require('../meta'); +const flags = require('../flags'); const privileges = require('../privileges'); +const notifications = require('../notifications'); +const plugins = require('../plugins'); const events = require('../events'); const usersAPI = module.exports; @@ -60,3 +64,91 @@ usersAPI.update = async function (caller, data) { await log('username-change', { oldUsername: oldUserData.username, newUsername: userData.username }); } }; + +usersAPI.delete = async function (caller, data) { + processDeletion(data.uid, caller); +}; + +usersAPI.deleteMany = async function (caller, data) { + console.log(data.uids); + if (await canDeleteUids(data.uids)) { + await Promise.all(data.uids.map(uid => processDeletion(uid, caller))); + } +}; + +usersAPI.changePassword = async function (caller, data) { + await user.changePassword(caller.uid, Object.assign(data, { ip: caller.ip })); + await events.log({ + type: 'password-change', + uid: caller.uid, + targetUid: data.uid, + ip: caller.ip, + }); +}; + +usersAPI.follow = async function (caller, data) { + await user.follow(caller.uid, data.uid); + plugins.fireHook('action:user.follow', { + fromUid: caller.uid, + toUid: data.uid, + }); + + const userData = await user.getUserFields(caller.uid, ['username', 'userslug']); + const notifObj = await notifications.create({ + type: 'follow', + bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', + nid: 'follow:' + data.uid + ':uid:' + caller.uid, + from: caller.uid, + path: '/uid/' + data.uid + '/followers', + mergeId: 'notifications:user_started_following_you', + }); + if (!notifObj) { + return; + } + notifObj.user = userData; + await notifications.push(notifObj, [data.uid]); +}; + +usersAPI.unfollow = async function (caller, data) { + await user.unfollow(caller.uid, data.uid); + plugins.fireHook('action:user.unfollow', { + fromUid: caller.uid, + toUid: data.uid, + }); +}; + +async function processDeletion(uid, caller) { + const isTargetAdmin = await user.isAdministrator(uid); + const isSelf = parseInt(uid, 10) === caller.uid; + const isAdmin = await user.isAdministrator(caller.uid); + + if (!isSelf && !isAdmin) { + throw new Error('[[error:no-privileges]]'); + } else if (!isSelf && isTargetAdmin) { + throw new Error('[[error:cant-delete-other-admins]]'); + } + + // TODO: clear user tokens for this uid + await flags.resolveFlag('user', uid, caller.uid); + const userData = await user.delete(caller.uid, uid); + await events.log({ + type: 'user-delete', + uid: caller.uid, + targetUid: uid, + ip: caller.ip, + username: userData.username, + email: userData.email, + }); +} + +async function canDeleteUids(uids) { + if (!Array.isArray(uids)) { + throw new Error('[[error:invalid-data]]'); + } + const isMembers = await groups.isMembers(uids, 'administrators'); + if (isMembers.includes(true)) { + throw new Error('[[error:cant-delete-other-admins]]'); + } + + return true; +} diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index 7102f42a33..c74b823497 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -2,10 +2,8 @@ const api = require('../../api'); const user = require('../../user'); -const groups = require('../../groups'); const plugins = require('../../plugins'); const privileges = require('../../privileges'); -const notifications = require('../../notifications'); const flags = require('../../flags'); const meta = require('../../meta'); const events = require('../../events'); @@ -24,101 +22,32 @@ Users.create = async (req, res) => { }; Users.update = async (req, res) => { - const userObj = await api.users.update(req, { ...req.body, ...req.params }); + const userObj = await api.users.update(req, { ...req.body, uid: req.params.uid }); helpers.formatApiResponse(200, res, userObj); }; Users.delete = async (req, res) => { - processDeletion(req.params.uid, req, res); + await api.users.delete(req, req.params); helpers.formatApiResponse(200, res); }; Users.deleteMany = async (req, res) => { - if (await canDeleteUids(req.body.uids, res)) { - await Promise.all(req.body.uids.map(uid => processDeletion(uid, req, res))); - helpers.formatApiResponse(200, res); - } + await api.users.deleteMany(req, req.body); + helpers.formatApiResponse(200, res); }; -async function canDeleteUids(uids, res) { - if (!Array.isArray(uids)) { - helpers.formatApiResponse(400, res, new Error('[[error:invalid-data]]')); - return false; - } - const isMembers = await groups.isMembers(uids, 'administrators'); - if (isMembers.includes(true)) { - helpers.formatApiResponse(403, res, new Error('[[error:cant-delete-other-admins]]')); - return false; - } - - return true; -} - -async function processDeletion(uid, req, res) { - const isTargetAdmin = await user.isAdministrator(uid); - if (!res.locals.privileges.isSelf && !res.locals.privileges.isAdmin) { - return helpers.formatApiResponse(403, res); - } else if (!res.locals.privileges.isSelf && isTargetAdmin) { - return helpers.formatApiResponse(403, res, new Error('[[error:cant-delete-other-admins]]')); - } - - // TODO: clear user tokens for this uid - await flags.resolveFlag('user', uid, req.user.uid); - const userData = await user.delete(req.user.uid, uid); - await events.log({ - type: 'user-delete', - uid: req.user.uid, - targetUid: uid, - ip: req.ip, - username: userData.username, - email: userData.email, - }); -} - Users.changePassword = async (req, res) => { - req.body.uid = req.params.uid; - await user.changePassword(req.user.uid, Object.assign(req.body, { ip: req.ip })); - await events.log({ - type: 'password-change', - uid: req.user.uid, - targetUid: req.params.uid, - ip: req.ip, - }); - + await api.users.changePassword(req, { ...req.body, uid: req.params.uid }); helpers.formatApiResponse(200, res); }; Users.follow = async (req, res) => { - await user.follow(req.user.uid, req.params.uid); - plugins.fireHook('action:user.follow', { - fromUid: req.user.uid, - toUid: req.params.uid, - }); - - const userData = await user.getUserFields(req.user.uid, ['username', 'userslug']); - const notifObj = await notifications.create({ - type: 'follow', - bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', - nid: 'follow:' + req.params.uid + ':uid:' + req.user.uid, - from: req.user.uid, - path: '/uid/' + req.params.uid + '/followers', - mergeId: 'notifications:user_started_following_you', - }); - if (!notifObj) { - return; - } - notifObj.user = userData; - await notifications.push(notifObj, [req.params.uid]); - + await api.users.follow(req, req.params); helpers.formatApiResponse(200, res); }; Users.unfollow = async (req, res) => { - await user.unfollow(req.user.uid, req.params.uid); - plugins.fireHook('action:user.unfollow', { - fromUid: req.user.uid, - toUid: req.params.uid, - }); + await api.users.unfollow(req, req.params); helpers.formatApiResponse(200, res); }; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 44a8de4075..266e739db6 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -140,11 +140,7 @@ User.deleteUsersContent = async function (socket, uids) { User.deleteUsersAndContent = async function (socket, uids) { sockets.warnDeprecated(socket, 'DELETE /api/v3/users or DELETE /api/v3/users/:uid'); - - await canDeleteUids(uids); - deleteUsers(socket, uids, async function (uid) { - return await user.delete(socket.uid, uid); - }); + await api.users.deleteMany(socket, { uids }); }; async function canDeleteUids(uids) { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 721d869467..9080e34256 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -5,9 +5,9 @@ const async = require('async'); const util = require('util'); const sleep = util.promisify(setTimeout); +const api = require('../api'); const user = require('../user'); const topics = require('../topics'); -const notifications = require('../notifications'); const messaging = require('../messaging'); const plugins = require('../plugins'); const meta = require('../meta'); @@ -159,45 +159,14 @@ SocketUser.isFollowing = async function (socket, data) { SocketUser.follow = async function (socket, data) { sockets.warnDeprecated(socket, 'POST /api/v3/users/follow'); - - if (!socket.uid || !data) { - throw new Error('[[error:invalid-data]]'); - } - - await toggleFollow('follow', socket.uid, data.uid); - const userData = await user.getUserFields(socket.uid, ['username', 'userslug']); - const notifObj = await notifications.create({ - type: 'follow', - bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', - nid: 'follow:' + data.uid + ':uid:' + socket.uid, - from: socket.uid, - path: '/uid/' + data.uid + '/followers', - mergeId: 'notifications:user_started_following_you', - }); - if (!notifObj) { - return; - } - notifObj.user = userData; - await notifications.push(notifObj, [data.uid]); + await api.users.follow(socket, data); }; SocketUser.unfollow = async function (socket, data) { sockets.warnDeprecated(socket, 'DELETE /api/v3/users/unfollow'); - - if (!socket.uid || !data) { - throw new Error('[[error:invalid-data]]'); - } - await toggleFollow('unfollow', socket.uid, data.uid); + await api.users.unfollow(socket, data); }; -async function toggleFollow(method, uid, theiruid) { - await user[method](uid, theiruid); - plugins.fireHook('action:user.' + method, { - fromUid: uid, - toUid: theiruid, - }); -} - SocketUser.saveSettings = async function (socket, data) { if (!socket.uid || !data || !data.settings) { throw new Error('[[error:invalid-data]]'); diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 0345faf215..463d509e71 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -78,21 +78,7 @@ module.exports = function (SocketUser) { SocketUser.changePassword = async function (socket, data) { sockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/password'); - - if (!socket.uid) { - throw new Error('[[error:invalid-uid]]'); - } - - if (!data || !data.uid) { - throw new Error('[[error:invalid-data]]'); - } - await user.changePassword(socket.uid, Object.assign(data, { ip: socket.ip })); - await events.log({ - type: 'password-change', - uid: socket.uid, - targetUid: data.uid, - ip: socket.ip, - }); + await api.users.changePassword(socket, data); }; SocketUser.updateProfile = async function (socket, data) {