From c6f2155f6af0a83e3821092bf2df54d16c92c03d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 7 Jun 2024 12:53:06 -0400 Subject: [PATCH 1/3] fix: incorrect use of .map on a Set --- src/posts/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index e51128b6e4..b8ce1dd3db 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -72,7 +72,7 @@ module.exports = function (Posts) { Posts.clearCachedPost = function (pid) { const cache = require('./cache'); - cache.del(allowedTypes.map(type => `${String(pid)}|${type}`)); + cache.del(Array.from(allowedTypes).map(type => `${String(pid)}|${type}`)); }; Posts.parseSignature = async function (userData, uid) { From 3dca79dd3bba57bff54113f197a6c13d111b662f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 7 Jun 2024 12:55:25 -0400 Subject: [PATCH 2/3] feat: allow user.exists to respond to requests for remote uids too --- src/user/index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/user/index.js b/src/user/index.js index 3590c1c8ce..50c7bcfc53 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -43,11 +43,13 @@ require('./blocks')(User); require('./uploads')(User); User.exists = async function (uids) { - return await ( - Array.isArray(uids) ? - db.isSortedSetMembers('users:joindate', uids) : - db.isSortedSetMember('users:joindate', uids) - ); + const singular = !Array.isArray(uids); + uids = singular ? [uids] : uids; + + let results = await Promise.all(uids.map(async uid => await db.isMemberOfSortedSets(['users:joindate', 'usersRemote:lastCrawled'], uid))); + results = results.map(set => set.some(Boolean)); + + return singular ? results.pop() : results; }; User.existsBySlug = async function (userslug) { From 539300ffecd93b1d2b74dcd27105d121f8f53050 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 7 Jun 2024 12:55:52 -0400 Subject: [PATCH 3/3] feat: remote user deletion logic, #12611 --- public/src/admin/manage/users.js | 2 +- src/activitypub/actors.js | 28 ++++++++++++++++++++++++++++ src/api/users.js | 2 +- src/middleware/assert.js | 4 ++-- src/user/delete.js | 9 ++++++--- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 0f8dffaa29..1c338a0975 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -510,7 +510,7 @@ define('admin/manage/users', [ if (confirm) { Promise.all( uids.map( - uid => api.del(`/users/${uid}${path}`, {}).then(() => { + uid => api.del(`/users/${encodeURIComponent(uid)}${path}`, {}).then(() => { if (path !== '/content') { removeRow(uid); } diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index aa093802dd..158ee9b893 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -71,6 +71,8 @@ Actors.assert = async (ids, options = {}) => { // 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 followersUrlMap = new Map(); const pubKeysMap = new Map(); @@ -207,3 +209,29 @@ Actors.getLocalFollowersCount = async (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), + ]); +}; diff --git a/src/api/users.js b/src/api/users.js index 6b3398db9d..924dd1daa0 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -515,7 +515,7 @@ async function isPrivilegedOrSelfAndPasswordMatch(caller, data) { async function processDeletion({ uid, method, password, caller }) { 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); if (isSelf && meta.config.allowAccountDelete !== 1) { diff --git a/src/middleware/assert.js b/src/middleware/assert.js index 67a63b0344..6b20667379 100644 --- a/src/middleware/assert.js +++ b/src/middleware/assert.js @@ -29,8 +29,8 @@ Assert.user = helpers.try(async (req, res, next) => { const uid = req.params.uid || res.locals.uid; if ( - (utils.isNumber(uid) && await user.exists(uid)) || - (uid.indexOf('@') !== -1 && await activitypub.helpers.query(uid)) + ((utils.isNumber(uid) || activitypub.helpers.isUri(uid)) && await user.exists(uid)) || + (uid.indexOf('@') !== -1 && await user.existsBySlug(uid)) ) { return next(); } diff --git a/src/user/delete.js b/src/user/delete.js index 9efd8802ae..c6eb13809f 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -13,7 +13,9 @@ const topics = require('../topics'); const groups = require('../groups'); const messaging = require('../messaging'); const plugins = require('../plugins'); +const activitypub = require('../activitypub'); const batch = require('../batch'); +const utils = require('../utils'); module.exports = function (User) { const deletesInProgress = {}; @@ -24,7 +26,7 @@ module.exports = function (User) { }; 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]]'); } if (deletesInProgress[uid]) { @@ -61,7 +63,7 @@ module.exports = function (User) { let deleteIds = []; await batch.processSortedSet('post:queue', async (ids) => { 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); }, { batch: 500 }); await async.eachSeries(deleteIds, posts.removeFromQueue); @@ -90,7 +92,7 @@ module.exports = function (User) { deletesInProgress[uid] = 'user.deleteAccount'; 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) { delete deletesInProgress[uid]; @@ -153,6 +155,7 @@ module.exports = function (User) { flags.resolveFlag('user', uid, uid), User.reset.cleanByUid(uid), User.email.expireValidation(uid), + activitypub.actors.remove(uid), ]); await db.deleteAll([ `followers:${uid}`, `following:${uid}`, `user:${uid}`,