feat: remote user deletion logic, #12611

This commit is contained in:
Julian Lam
2024-06-07 12:55:52 -04:00
parent 3dca79dd3b
commit 539300ffec
5 changed files with 38 additions and 7 deletions

View File

@@ -510,7 +510,7 @@ define('admin/manage/users', [
if (confirm) { if (confirm) {
Promise.all( Promise.all(
uids.map( uids.map(
uid => api.del(`/users/${uid}${path}`, {}).then(() => { uid => api.del(`/users/${encodeURIComponent(uid)}${path}`, {}).then(() => {
if (path !== '/content') { if (path !== '/content') {
removeRow(uid); removeRow(uid);
} }

View File

@@ -71,6 +71,8 @@ Actors.assert = async (ids, options = {}) => {
// winston.verbose(`[activitypub/actors] Asserting ${ids.length} actor(s)`); // winston.verbose(`[activitypub/actors] Asserting ${ids.length} actor(s)`);
// NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVE!
const urlMap = new Map(); const urlMap = new Map();
const followersUrlMap = new Map(); const followersUrlMap = new Map();
const pubKeysMap = new Map(); const pubKeysMap = new Map();
@@ -207,3 +209,29 @@ Actors.getLocalFollowersCount = async (id) => {
return await db.sortedSetCard(`followersRemote:${id}`); return await db.sortedSetCard(`followersRemote:${id}`);
}; };
Actors.remove = async (id) => {
const exists = await db.isSortedSetMember('usersRemote:lastCrawled', id);
if (!exists) {
return false;
}
let { username, fullname, url, followersUrl } = await user.getUserFields(id, ['username', 'fullname', 'url', 'followersUrl']);
username = username.toLowerCase();
await Promise.all([
db.sortedSetRemoveBulk([
['ap.preferredUsername:sorted', `${username}:${id}`],
['ap.name:sorted', `${fullname.toLowerCase()}:${id}`],
]),
db.deleteObjectField('handle:uid', username),
db.deleteObjectField('followersUrl:uid', followersUrl),
db.deleteObjectField('remoteUrl:uid', url),
db.delete(`userRemote:${id}:keys`),
]);
await Promise.all([
db.delete(`userRemote:${id}`),
db.sortedSetRemove('usersRemote:lastCrawled', id),
]);
};

View File

@@ -515,7 +515,7 @@ async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
async function processDeletion({ uid, method, password, 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) === parseInt(caller.uid, 10); const isSelf = String(uid) === String(caller.uid);
const hasAdminPrivilege = await privileges.admin.can('admin:users', caller.uid); const hasAdminPrivilege = await privileges.admin.can('admin:users', caller.uid);
if (isSelf && meta.config.allowAccountDelete !== 1) { if (isSelf && meta.config.allowAccountDelete !== 1) {

View File

@@ -29,8 +29,8 @@ Assert.user = helpers.try(async (req, res, next) => {
const uid = req.params.uid || res.locals.uid; const uid = req.params.uid || res.locals.uid;
if ( if (
(utils.isNumber(uid) && await user.exists(uid)) || ((utils.isNumber(uid) || activitypub.helpers.isUri(uid)) && await user.exists(uid)) ||
(uid.indexOf('@') !== -1 && await activitypub.helpers.query(uid)) (uid.indexOf('@') !== -1 && await user.existsBySlug(uid))
) { ) {
return next(); return next();
} }

View File

@@ -13,7 +13,9 @@ const topics = require('../topics');
const groups = require('../groups'); const groups = require('../groups');
const messaging = require('../messaging'); const messaging = require('../messaging');
const plugins = require('../plugins'); const plugins = require('../plugins');
const activitypub = require('../activitypub');
const batch = require('../batch'); const batch = require('../batch');
const utils = require('../utils');
module.exports = function (User) { module.exports = function (User) {
const deletesInProgress = {}; const deletesInProgress = {};
@@ -24,7 +26,7 @@ module.exports = function (User) {
}; };
User.deleteContent = async function (callerUid, uid) { User.deleteContent = async function (callerUid, uid) {
if (parseInt(uid, 10) <= 0) { if (utils.isNumber(uid) && parseInt(uid, 10) <= 0) {
throw new Error('[[error:invalid-uid]]'); throw new Error('[[error:invalid-uid]]');
} }
if (deletesInProgress[uid]) { if (deletesInProgress[uid]) {
@@ -61,7 +63,7 @@ module.exports = function (User) {
let deleteIds = []; let deleteIds = [];
await batch.processSortedSet('post:queue', async (ids) => { await batch.processSortedSet('post:queue', async (ids) => {
const data = await db.getObjects(ids.map(id => `post:queue:${id}`)); const data = await db.getObjects(ids.map(id => `post:queue:${id}`));
const userQueuedIds = data.filter(d => parseInt(d.uid, 10) === parseInt(uid, 10)).map(d => d.id); const userQueuedIds = data.filter(d => String(d.uid) === String(uid)).map(d => d.id);
deleteIds = deleteIds.concat(userQueuedIds); deleteIds = deleteIds.concat(userQueuedIds);
}, { batch: 500 }); }, { batch: 500 });
await async.eachSeries(deleteIds, posts.removeFromQueue); await async.eachSeries(deleteIds, posts.removeFromQueue);
@@ -90,7 +92,7 @@ module.exports = function (User) {
deletesInProgress[uid] = 'user.deleteAccount'; deletesInProgress[uid] = 'user.deleteAccount';
await removeFromSortedSets(uid); await removeFromSortedSets(uid);
const userData = await db.getObject(`user:${uid}`); const userData = await db.getObject(utils.isNumber(uid) ? `user:${uid}` : `userRemote:${uid}`);
if (!userData || !userData.username) { if (!userData || !userData.username) {
delete deletesInProgress[uid]; delete deletesInProgress[uid];
@@ -153,6 +155,7 @@ module.exports = function (User) {
flags.resolveFlag('user', uid, uid), flags.resolveFlag('user', uid, uid),
User.reset.cleanByUid(uid), User.reset.cleanByUid(uid),
User.email.expireValidation(uid), User.email.expireValidation(uid),
activitypub.actors.remove(uid),
]); ]);
await db.deleteAll([ await db.deleteAll([
`followers:${uid}`, `following:${uid}`, `user:${uid}`, `followers:${uid}`, `following:${uid}`, `user:${uid}`,