mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
feat(api): account deletion routes for the Write API (#8881)
* feat(api): account deletion routes for the Write API * refactor: rewrite client-side calls to account deletion to use api * style: apply DRY
This commit is contained in:
@@ -32,6 +32,10 @@ paths:
|
|||||||
$ref: 'write/users.yaml'
|
$ref: 'write/users.yaml'
|
||||||
/users/{uid}:
|
/users/{uid}:
|
||||||
$ref: 'write/users/uid.yaml'
|
$ref: 'write/users/uid.yaml'
|
||||||
|
/users/{uid}/content:
|
||||||
|
$ref: 'write/users/uid/content.yaml'
|
||||||
|
/users/{uid}/account:
|
||||||
|
$ref: 'write/users/uid/account.yaml'
|
||||||
/users/{uid}/settings:
|
/users/{uid}/settings:
|
||||||
$ref: 'write/users/uid/settings.yaml'
|
$ref: 'write/users/uid/settings.yaml'
|
||||||
/users/{uid}/password:
|
/users/{uid}/password:
|
||||||
|
|||||||
25
public/openapi/write/users/uid/account.yaml
Normal file
25
public/openapi/write/users/uid/account.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
summary: delete a single user account (preserve content)
|
||||||
|
description: This route deletes a single user's account, but preserves the content (posts, bookmarks, etc.)
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: uid
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: uid of the user to delete
|
||||||
|
example: 7
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: user account deleted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
25
public/openapi/write/users/uid/content.yaml
Normal file
25
public/openapi/write/users/uid/content.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
summary: delete a single user account's content (preserve account)
|
||||||
|
description: This route deletes a single user's account content (posts, bookmarks, etc.) but preserves the account itself
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: uid
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: uid of the user's content to delete
|
||||||
|
example: 7
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: user account content deleted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
@@ -36,8 +36,12 @@ define('admin/manage/users', [
|
|||||||
$('.users-table [component="user/select/all"]').prop('checked', false);
|
$('.users-table [component="user/select/all"]').prop('checked', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSelected() {
|
function removeRow(uid) {
|
||||||
$('.users-table [component="user/select/single"]:checked').parents('.user-row').remove();
|
const checkboxEl = document.querySelector(`.users-table [component="user/select/single"][data-uid="${uid}"]`);
|
||||||
|
if (checkboxEl) {
|
||||||
|
const rowEl = checkboxEl.closest('.user-row');
|
||||||
|
rowEl.parentNode.removeChild(rowEl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// use onSuccess instead
|
// use onSuccess instead
|
||||||
@@ -253,71 +257,46 @@ define('admin/manage/users', [
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.delete-user').on('click', function () {
|
$('.delete-user').on('click', () => {
|
||||||
|
handleDelete('[[admin/manage/users:alerts.confirm-delete]]', '/account');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete-user-content').on('click', () => {
|
||||||
|
handleDelete('[[admin/manage/users:alerts.confirm-delete-content]]', '/content');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete-user-and-content').on('click', () => {
|
||||||
|
handleDelete('[[admin/manage/users:alerts.confirm-purge]]', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleDelete(confirmMsg, path) {
|
||||||
var uids = getSelectedUids();
|
var uids = getSelectedUids();
|
||||||
if (!uids.length) {
|
if (!uids.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bootbox.confirm('[[admin/manage/users:alerts.confirm-delete]]', function (confirm) {
|
bootbox.confirm(confirmMsg, function (confirm) {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
socket.emit('admin.user.deleteUsers', uids, function (err) {
|
Promise.all(uids.map(uid => api.del(`/users/${uid}${path}`, {})
|
||||||
if (err) {
|
.then(() => {
|
||||||
return app.alertError(err.message);
|
if (path !== '/content') {
|
||||||
|
removeRow(uid);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)).then(() => {
|
||||||
|
if (path !== '/content') {
|
||||||
|
app.alertSuccess('[[admin/manage/users:alerts.delete-success]]');
|
||||||
|
} else {
|
||||||
|
app.alertSuccess('[[admin/manage/users:alerts.delete-content-success]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
app.alertSuccess('[[admin/manage/users:alerts.delete-success]]');
|
|
||||||
removeSelected();
|
|
||||||
unselectAll();
|
unselectAll();
|
||||||
if (!$('.users-table [component="user/select/single"]').length) {
|
if (!$('.users-table [component="user/select/single"]').length) {
|
||||||
ajaxify.refresh();
|
ajaxify.refresh();
|
||||||
}
|
}
|
||||||
});
|
}).catch(app.alertError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
$('.delete-user-content').on('click', function () {
|
|
||||||
var uids = getSelectedUids();
|
|
||||||
if (!uids.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bootbox.confirm('[[admin/manage/users:alerts.confirm-delete-content]]', function (confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
socket.emit('admin.user.deleteUsersContent', uids, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.alertSuccess('[[admin/manage/users:alerts.delete-content-success]]');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.delete-user-and-content').on('click', function () {
|
|
||||||
var uids = getSelectedUids();
|
|
||||||
if (!uids.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bootbox.confirm('[[admin/manage/users:alerts.confirm-purge]]', function (confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
socket.emit('admin.user.deleteUsersAndContent', uids, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.alertSuccess('[[admin/manage/users:alerts.delete-success]]');
|
|
||||||
removeSelected();
|
|
||||||
unselectAll();
|
|
||||||
if (!$('.users-table [component="user/select/single"]').length) {
|
|
||||||
ajaxify.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleUserCreate() {
|
function handleUserCreate() {
|
||||||
$('[data-action="create"]').on('click', function () {
|
$('[data-action="create"]').on('click', function () {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define('accounts/delete', [], function () {
|
define('accounts/delete', ['api', 'bootbox'], function (api) {
|
||||||
var Delete = {};
|
var Delete = {};
|
||||||
|
|
||||||
Delete.account = function (uid, callback) {
|
Delete.account = function (uid, callback) {
|
||||||
executeAction(
|
executeAction(
|
||||||
uid,
|
uid,
|
||||||
'[[user:delete_this_account_confirm]]',
|
'[[user:delete_this_account_confirm]]',
|
||||||
'admin.user.deleteUsers',
|
'/account',
|
||||||
'[[user:account-deleted]]',
|
'[[user:account-deleted]]',
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
@@ -17,7 +17,7 @@ define('accounts/delete', [], function () {
|
|||||||
executeAction(
|
executeAction(
|
||||||
uid,
|
uid,
|
||||||
'[[user:delete_account_content_confirm]]',
|
'[[user:delete_account_content_confirm]]',
|
||||||
'admin.user.deleteUsersContent',
|
'/content',
|
||||||
'[[user:account-content-deleted]]',
|
'[[user:account-content-deleted]]',
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
@@ -27,22 +27,19 @@ define('accounts/delete', [], function () {
|
|||||||
executeAction(
|
executeAction(
|
||||||
uid,
|
uid,
|
||||||
'[[user:delete_all_confirm]]',
|
'[[user:delete_all_confirm]]',
|
||||||
'admin.user.deleteUsersAndContent',
|
'',
|
||||||
'[[user:account-deleted]]',
|
'[[user:account-deleted]]',
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function executeAction(uid, confirmText, action, successText, callback) {
|
function executeAction(uid, confirmText, path, successText, callback) {
|
||||||
bootbox.confirm(confirmText, function (confirm) {
|
bootbox.confirm(confirmText, function (confirm) {
|
||||||
if (!confirm) {
|
if (!confirm) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit(action, [uid], function (err) {
|
api.del(`/users/${uid}${path}`, {}).then(() => {
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
app.alertSuccess(successText);
|
app.alertSuccess(successText);
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
|
|||||||
@@ -81,13 +81,21 @@ usersAPI.update = async function (caller, data) {
|
|||||||
return userData;
|
return userData;
|
||||||
};
|
};
|
||||||
|
|
||||||
usersAPI.delete = async function (caller, data) {
|
usersAPI.delete = async function (caller, { uid, password }) {
|
||||||
processDeletion(data.uid, caller);
|
processDeletion({ uid: uid, method: 'delete', password, caller });
|
||||||
|
};
|
||||||
|
|
||||||
|
usersAPI.deleteContent = async function (caller, { uid, password }) {
|
||||||
|
processDeletion({ uid, method: 'deleteContent', password, caller });
|
||||||
|
};
|
||||||
|
|
||||||
|
usersAPI.deleteAccount = async function (caller, { uid, password }) {
|
||||||
|
processDeletion({ uid, method: 'deleteAccount', password, caller });
|
||||||
};
|
};
|
||||||
|
|
||||||
usersAPI.deleteMany = async function (caller, data) {
|
usersAPI.deleteMany = async function (caller, data) {
|
||||||
if (await canDeleteUids(data.uids)) {
|
if (await canDeleteUids(data.uids)) {
|
||||||
await Promise.all(data.uids.map(uid => processDeletion(uid, caller)));
|
await Promise.all(data.uids.map(uid => processDeletion({ uid, method: 'delete', caller })));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,22 +237,56 @@ async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processDeletion(uid, caller) {
|
async function processDeletion({ uid, method, password, caller }) {
|
||||||
const isTargetAdmin = await user.isAdministrator(uid);
|
const isTargetAdmin = await user.isAdministrator(uid);
|
||||||
const isSelf = parseInt(uid, 10) === caller.uid;
|
const isSelf = parseInt(uid, 10) === caller.uid;
|
||||||
const isAdmin = await user.isAdministrator(caller.uid);
|
const isAdmin = await user.isAdministrator(caller.uid);
|
||||||
|
|
||||||
if (!isSelf && !isAdmin) {
|
if (meta.config.allowAccountDelete !== 1) {
|
||||||
throw new Error('[[error:no-privileges]]');
|
throw new Error('[[error:no-privileges]]');
|
||||||
} else if (!isSelf && isTargetAdmin) {
|
} else if (!isSelf && !isAdmin) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
} else if (isTargetAdmin) {
|
||||||
throw new Error('[[error:cant-delete-other-admins]]');
|
throw new Error('[[error:cant-delete-other-admins]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Privilege checks -- only deleteAccount is available for non-admins
|
||||||
|
const hasAdminPrivilege = await privileges.admin.can('admin:users', caller.uid);
|
||||||
|
if (!hasAdminPrivilege && ['delete', 'deleteContent'].includes(method)) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-deletions require a password
|
||||||
|
const hasPassword = await user.hasPassword(uid);
|
||||||
|
if (isSelf && hasPassword) {
|
||||||
|
const ok = await user.isPasswordCorrect(uid, password, caller.ip);
|
||||||
|
if (!ok) {
|
||||||
|
throw new Error('[[error:invalid-password]]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: clear user tokens for this uid
|
// TODO: clear user tokens for this uid
|
||||||
await flags.resolveFlag('user', uid, caller.uid);
|
await flags.resolveFlag('user', uid, caller.uid);
|
||||||
const userData = await user.delete(caller.uid, uid);
|
|
||||||
|
let userData;
|
||||||
|
if (method === 'deleteAccount') {
|
||||||
|
userData = await user[method](uid);
|
||||||
|
} else {
|
||||||
|
userData = await user[method](caller.uid, uid);
|
||||||
|
}
|
||||||
|
userData = userData || {};
|
||||||
|
|
||||||
|
sockets.server.sockets.emit('event:user_status_change', { uid: caller.uid, status: 'offline' });
|
||||||
|
|
||||||
|
plugins.fireHook('action:user.delete', {
|
||||||
|
callerUid: caller.uid,
|
||||||
|
uid: uid,
|
||||||
|
ip: caller.ip,
|
||||||
|
user: userData,
|
||||||
|
});
|
||||||
|
|
||||||
await events.log({
|
await events.log({
|
||||||
type: 'user-delete',
|
type: `user-${method}`,
|
||||||
uid: caller.uid,
|
uid: caller.uid,
|
||||||
targetUid: uid,
|
targetUid: uid,
|
||||||
ip: caller.ip,
|
ip: caller.ip,
|
||||||
|
|||||||
@@ -49,7 +49,17 @@ Users.update = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Users.delete = async (req, res) => {
|
Users.delete = async (req, res) => {
|
||||||
await api.users.delete(req, req.params);
|
await api.users.delete(req, { ...req.params, password: req.body.password });
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Users.deleteContent = async (req, res) => {
|
||||||
|
await api.users.deleteContent(req, { ...req.params, password: req.body.password });
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Users.deleteAccount = async (req, res) => {
|
||||||
|
await api.users.deleteAccount(req, { ...req.params, password: req.body.password });
|
||||||
helpers.formatApiResponse(200, res);
|
helpers.formatApiResponse(200, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ function authenticatedRoutes() {
|
|||||||
|
|
||||||
setupApiRoute(router, 'head', '/:uid', [middleware.assert.user], controllers.write.users.exists);
|
setupApiRoute(router, 'head', '/:uid', [middleware.assert.user], controllers.write.users.exists);
|
||||||
setupApiRoute(router, 'put', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.update);
|
setupApiRoute(router, 'put', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.update);
|
||||||
setupApiRoute(router, 'delete', '/:uid', [...middlewares, middleware.assert.user, middleware.exposePrivileges], controllers.write.users.delete);
|
setupApiRoute(router, 'delete', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.delete);
|
||||||
|
setupApiRoute(router, 'delete', '/:uid/content', [...middlewares, middleware.assert.user], controllers.write.users.deleteContent);
|
||||||
|
setupApiRoute(router, 'delete', '/:uid/account', [...middlewares, middleware.assert.user], controllers.write.users.deleteAccount);
|
||||||
|
|
||||||
setupApiRoute(router, 'put', '/:uid/settings', [...middlewares, middleware.checkRequired.bind(null, ['settings'])], controllers.write.users.updateSettings);
|
setupApiRoute(router, 'put', '/:uid/settings', [...middlewares, middleware.checkRequired.bind(null, ['settings'])], controllers.write.users.updateSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const winston = require('winston');
|
|
||||||
|
|
||||||
const db = require('../../database');
|
const db = require('../../database');
|
||||||
const api = require('../../api');
|
const api = require('../../api');
|
||||||
@@ -9,9 +8,7 @@ const groups = require('../../groups');
|
|||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const events = require('../../events');
|
const events = require('../../events');
|
||||||
const meta = require('../../meta');
|
const meta = require('../../meta');
|
||||||
const plugins = require('../../plugins');
|
|
||||||
const translator = require('../../translator');
|
const translator = require('../../translator');
|
||||||
const flags = require('../../flags');
|
|
||||||
const sockets = require('..');
|
const sockets = require('..');
|
||||||
|
|
||||||
const User = module.exports;
|
const User = module.exports;
|
||||||
@@ -125,16 +122,16 @@ User.forcePasswordReset = async function (socket, uids) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
User.deleteUsers = async function (socket, uids) {
|
User.deleteUsers = async function (socket, uids) {
|
||||||
await canDeleteUids(uids);
|
sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/account');
|
||||||
deleteUsers(socket, uids, async function (uid) {
|
await Promise.all(uids.map(async (uid) => {
|
||||||
return await user.deleteAccount(uid);
|
await api.users.deleteAccount(socket, { uid });
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
User.deleteUsersContent = async function (socket, uids) {
|
User.deleteUsersContent = async function (socket, uids) {
|
||||||
await canDeleteUids(uids);
|
sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/content');
|
||||||
await Promise.all(uids.map(async (uid) => {
|
await Promise.all(uids.map(async (uid) => {
|
||||||
await user.deleteContent(socket.uid, uid);
|
await api.users.deleteContent(socket, { uid });
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,42 +140,6 @@ User.deleteUsersAndContent = async function (socket, uids) {
|
|||||||
await api.users.deleteMany(socket, { uids });
|
await api.users.deleteMany(socket, { uids });
|
||||||
};
|
};
|
||||||
|
|
||||||
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]]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteUsers(socket, uids, method) {
|
|
||||||
async function doDelete(uid) {
|
|
||||||
await flags.resolveFlag('user', uid, socket.uid);
|
|
||||||
const userData = await method(uid);
|
|
||||||
await events.log({
|
|
||||||
type: 'user-delete',
|
|
||||||
uid: socket.uid,
|
|
||||||
targetUid: uid,
|
|
||||||
ip: socket.ip,
|
|
||||||
username: userData.username,
|
|
||||||
email: userData.email,
|
|
||||||
});
|
|
||||||
plugins.fireHook('action:user.delete', {
|
|
||||||
callerUid: socket.uid,
|
|
||||||
uid: uid,
|
|
||||||
ip: socket.ip,
|
|
||||||
user: userData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await Promise.all(uids.map(uid => doDelete(uid)));
|
|
||||||
} catch (err) {
|
|
||||||
winston.error(err.stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
User.restartJobs = async function () {
|
User.restartJobs = async function () {
|
||||||
user.startJobs();
|
user.startJobs();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const db = require('../database');
|
|||||||
const userController = require('../controllers/user');
|
const userController = require('../controllers/user');
|
||||||
const privileges = require('../privileges');
|
const privileges = require('../privileges');
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
const flags = require('../flags');
|
|
||||||
const sockets = require('.');
|
const sockets = require('.');
|
||||||
|
|
||||||
const SocketUser = module.exports;
|
const SocketUser = module.exports;
|
||||||
@@ -37,37 +36,8 @@ SocketUser.exists = async function (socket, data) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SocketUser.deleteAccount = async function (socket, data) {
|
SocketUser.deleteAccount = async function (socket, data) {
|
||||||
if (!socket.uid) {
|
sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/account');
|
||||||
throw new Error('[[error:no-privileges]]');
|
await api.users.deleteAccount(socket, data);
|
||||||
}
|
|
||||||
const hasPassword = await user.hasPassword(socket.uid);
|
|
||||||
if (hasPassword) {
|
|
||||||
const ok = await user.isPasswordCorrect(socket.uid, data.password, socket.ip);
|
|
||||||
if (!ok) {
|
|
||||||
throw new Error('[[error:invalid-password]]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const isAdmin = await user.isAdministrator(socket.uid);
|
|
||||||
if (isAdmin) {
|
|
||||||
throw new Error('[[error:cant-delete-admin]]');
|
|
||||||
}
|
|
||||||
if (meta.config.allowAccountDelete !== 1) {
|
|
||||||
throw new Error('[[error:no-privileges]]');
|
|
||||||
}
|
|
||||||
|
|
||||||
await flags.resolveFlag('user', socket.uid, socket.uid);
|
|
||||||
const userData = await user.deleteAccount(socket.uid);
|
|
||||||
|
|
||||||
require('./index').server.sockets.emit('event:user_status_change', { uid: socket.uid, status: 'offline' });
|
|
||||||
|
|
||||||
await events.log({
|
|
||||||
type: 'user-delete',
|
|
||||||
uid: socket.uid,
|
|
||||||
targetUid: socket.uid,
|
|
||||||
ip: socket.ip,
|
|
||||||
username: userData.username,
|
|
||||||
email: userData.email,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketUser.emailExists = async function (socket, data) {
|
SocketUser.emailExists = async function (socket, data) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const nconf = require('nconf');
|
|||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
|
const flags = require('../flags');
|
||||||
const topics = require('../topics');
|
const topics = require('../topics');
|
||||||
const groups = require('../groups');
|
const groups = require('../groups');
|
||||||
const messaging = require('../messaging');
|
const messaging = require('../messaging');
|
||||||
@@ -149,6 +150,7 @@ module.exports = function (User) {
|
|||||||
deleteUserFromFollowers(uid),
|
deleteUserFromFollowers(uid),
|
||||||
deleteImages(uid),
|
deleteImages(uid),
|
||||||
groups.leaveAllGroups(uid),
|
groups.leaveAllGroups(uid),
|
||||||
|
flags.resolveFlag('user', uid, uid),
|
||||||
]);
|
]);
|
||||||
await db.deleteAll(['followers:' + uid, 'following:' + uid, 'user:' + uid]);
|
await db.deleteAll(['followers:' + uid, 'following:' + uid, 'user:' + uid]);
|
||||||
delete deletesInProgress[uid];
|
delete deletesInProgress[uid];
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ describe('API', async () => {
|
|||||||
// Create sample users
|
// Create sample users
|
||||||
const adminUid = await user.create({ username: 'admin', password: '123456', email: 'test@example.org' });
|
const adminUid = await user.create({ username: 'admin', password: '123456', email: 'test@example.org' });
|
||||||
const unprivUid = await user.create({ username: 'unpriv', password: '123456', email: 'unpriv@example.org' });
|
const unprivUid = await user.create({ username: 'unpriv', password: '123456', email: 'unpriv@example.org' });
|
||||||
for (let x = 0; x < 3; x++) {
|
for (let x = 0; x < 4; x++) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await user.create({ username: 'deleteme', password: '123456' }); // for testing of user deletion routes (uids 4-6)
|
await user.create({ username: 'deleteme', password: '123456' }); // for testing of DELETE /users (uids 5, 6) and DELETE /user/:uid/account (uid 7)
|
||||||
}
|
}
|
||||||
await groups.join('administrators', adminUid);
|
await groups.join('administrators', adminUid);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user