From 0263d8bc5e2584d4fe96f14f5324dd82d0a6d90b Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Feb 2023 11:50:30 -0700 Subject: [PATCH] Various fixes: * Fix socket hangup bug in http_util requests * Disallow users to follow themselves * GET's to /followers, /following, etc. are not signed; don't try to enforce it * Fix a couple callbacks * WIP: Start more on Delete of inbox items --- core/activitypub/actor_search.js | 9 ++- core/activitypub/collection.js | 1 + core/http_util.js | 6 +- .../content/web_handlers/activitypub.js | 61 +++++++++++-------- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/core/activitypub/actor_search.js b/core/activitypub/actor_search.js index cc1f2e6a..36a83986 100644 --- a/core/activitypub/actor_search.js +++ b/core/activitypub/actor_search.js @@ -8,6 +8,7 @@ const Collection = require('./collection'); const EnigAssert = require('../enigma_assert'); const { sendFollowRequest, sendUnfollowRequest } = require('./follow_util'); const { getServer } = require('../listening_server'); +const UserProps = require('../user_property'); // deps const async = require('async'); @@ -231,7 +232,13 @@ exports.getModule = class ActivityPubActorSearch extends MenuModule { _toggleFollowStatus(cb) { // catch early key presses if (!this.selectedActorInfo) { - return; + return cb(Errors.UnexpectedState('No Actor selected')); + } + + // Don't allow users to follow themselves + const currentActorId = this.client.user.getProperty(UserProps.ActivityPubActorId); + if (currentActorId === this.selectedActorInfo.id) { + return cb(Errors.Invalid('You cannot follow yourself!')); } this.selectedActorInfo._isFollowing = !this.selectedActorInfo._isFollowing; diff --git a/core/activitypub/collection.js b/core/activitypub/collection.js index ac0c5af2..69376501 100644 --- a/core/activitypub/collection.js +++ b/core/activitypub/collection.js @@ -31,6 +31,7 @@ module.exports = class Collection extends ActivityPubObject { const headers = { Accept: ActivityStreamMediaType, }; + getJson( collectionUrl, { headers, validContentTypes: [ActivityStreamMediaType] }, diff --git a/core/http_util.js b/core/http_util.js index a1469c2d..38a59899 100644 --- a/core/http_util.js +++ b/core/http_util.js @@ -105,8 +105,7 @@ function _makeRequest(url, options, cb) { try { httpSignature.sign(req, options.sign); } catch (e) { - req.destroy(); - return cbWrapper(Errors.Invalid(`Invalid signing material: ${e}`)); + req.destroy(Errors.Invalid(`Invalid signing material: ${e}`)); } } @@ -115,8 +114,7 @@ function _makeRequest(url, options, cb) { }); req.on('timeout', () => { - req.destroy(); - return cbWrapper(Errors.Timeout('Timeout making HTTP request')); + req.destroy(Errors.Timeout('Timeout making HTTP request')); }); if (options.body) { diff --git a/core/servers/content/web_handlers/activitypub.js b/core/servers/content/web_handlers/activitypub.js index c153a47b..ebfe6f5c 100644 --- a/core/servers/content/web_handlers/activitypub.js +++ b/core/servers/content/web_handlers/activitypub.js @@ -99,6 +99,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { this.webServer.addRoute({ method: 'GET', path: /^\/_enig\/ap\/users\/.+\/outbox(\?page=[0-9]+)?$/, + // :TODO: fix me: What are we exposing to the outbox? Should be public only; GET's don't have signatures handler: (req, resp) => { return this._enforceMainKeySignatureValidity( req, @@ -111,25 +112,13 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { this.webServer.addRoute({ method: 'GET', path: /^\/_enig\/ap\/users\/.+\/followers(\?page=[0-9]+)?$/, - handler: (req, resp) => { - return this._enforceMainKeySignatureValidity( - req, - resp, - this._followersGetHandler.bind(this) - ); - }, + handler: this._followersGetHandler.bind(this), }); this.webServer.addRoute({ method: 'GET', path: /^\/_enig\/ap\/users\/.+\/following(\?page=[0-9]+)?$/, - handler: (req, resp) => { - return this._enforceMainKeySignatureValidity( - req, - resp, - this._followingGetHandler.bind(this) - ); - }, + handler: this._followingGetHandler.bind(this), }); this.webServer.addRoute({ @@ -469,26 +458,41 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { // possible for example, that we're being asked to delete an Actor; // If this is the case, they may be following multiple local Actor/users // and we have multiple entries. + const stats = { + deleted: [], + failed: [], + }; async.forEachSeries( objectsInfo, (objInfo, nextObjInfo) => { + const collectionName = objInfo.info.name; + if (objInfo.object) { // Based on the collection we find this entry in, // we may have additional validation or actions - switch (objInfo.info.name) { + switch (collectionName) { case Collections.Inbox: - if (inboxType !== Collections.Inbox) { - // :TODO: LOG ME + case Collections.SharedInbox: + // Validate the inbox this was sent to + if (inboxType !== collectionName) { + this.log.warn( + { inboxType, collectionName, objectId }, + 'Will not Delete object(s) from mismatched collection!' + ); return nextObjInfo(null); } + + // Validate signature + break; - case Collections.SharedInbox: - if (inboxType !== Collections.SharedInbox) { - // :TODO: log me - return nextObjInfo(null); - } + case Collections.Actors: + // Validate signature; Delete Actor and Following entries if any break; + + case Collection.Following: + break; + default: break; } @@ -496,12 +500,15 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { return nextObjInfo(null); } else { // it's unparsable, so we'll delete it - Collection.removeById(objInfo.info.name, objectId, err => { + Collection.removeById(collectionName, objectId, err => { if (err) { this.log.warn( - { objectId, collectionName: objInfo.info.name }, + { objectId, collectionName }, 'Failed to remove object' ); + stats.failed.push({ collectionName, objectId }); + } else { + stats.deleted.push({ collectionName, objectId }); } return nextObjInfo(null); }); @@ -511,6 +518,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { if (err) { // :TODO: log me } + + this.log.info({ stats, inboxType }, 'Inbox Delete request complete'); return this.webServer.accepted(resp); } ); @@ -823,6 +832,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { } _actorCollectionRequest(collectionName, req, resp) { + this.log.debug({ url: req.url }, `Request for "${collectionName}"`); + const getCollection = Collection[collectionName]; if (!getCollection) { return this.webServer.resourceNotFound(resp); @@ -863,12 +874,10 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule { } _followingGetHandler(req, resp) { - this.log.debug({ url: req.url }, 'Request for "following"'); return this._actorCollectionRequest(Collections.Following, req, resp); } _followersGetHandler(req, resp) { - this.log.debug({ url: req.url }, 'Request for "followers"'); return this._actorCollectionRequest(Collections.Followers, req, resp); }