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
This commit is contained in:
Bryan Ashby 2023-02-25 11:50:30 -07:00
parent a205445dd1
commit 0263d8bc5e
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
4 changed files with 46 additions and 31 deletions

View File

@ -8,6 +8,7 @@ const Collection = require('./collection');
const EnigAssert = require('../enigma_assert'); const EnigAssert = require('../enigma_assert');
const { sendFollowRequest, sendUnfollowRequest } = require('./follow_util'); const { sendFollowRequest, sendUnfollowRequest } = require('./follow_util');
const { getServer } = require('../listening_server'); const { getServer } = require('../listening_server');
const UserProps = require('../user_property');
// deps // deps
const async = require('async'); const async = require('async');
@ -231,7 +232,13 @@ exports.getModule = class ActivityPubActorSearch extends MenuModule {
_toggleFollowStatus(cb) { _toggleFollowStatus(cb) {
// catch early key presses // catch early key presses
if (!this.selectedActorInfo) { 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; this.selectedActorInfo._isFollowing = !this.selectedActorInfo._isFollowing;

View File

@ -31,6 +31,7 @@ module.exports = class Collection extends ActivityPubObject {
const headers = { const headers = {
Accept: ActivityStreamMediaType, Accept: ActivityStreamMediaType,
}; };
getJson( getJson(
collectionUrl, collectionUrl,
{ headers, validContentTypes: [ActivityStreamMediaType] }, { headers, validContentTypes: [ActivityStreamMediaType] },

View File

@ -105,8 +105,7 @@ function _makeRequest(url, options, cb) {
try { try {
httpSignature.sign(req, options.sign); httpSignature.sign(req, options.sign);
} catch (e) { } catch (e) {
req.destroy(); req.destroy(Errors.Invalid(`Invalid signing material: ${e}`));
return cbWrapper(Errors.Invalid(`Invalid signing material: ${e}`));
} }
} }
@ -115,8 +114,7 @@ function _makeRequest(url, options, cb) {
}); });
req.on('timeout', () => { req.on('timeout', () => {
req.destroy(); req.destroy(Errors.Timeout('Timeout making HTTP request'));
return cbWrapper(Errors.Timeout('Timeout making HTTP request'));
}); });
if (options.body) { if (options.body) {

View File

@ -99,6 +99,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
this.webServer.addRoute({ this.webServer.addRoute({
method: 'GET', method: 'GET',
path: /^\/_enig\/ap\/users\/.+\/outbox(\?page=[0-9]+)?$/, 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) => { handler: (req, resp) => {
return this._enforceMainKeySignatureValidity( return this._enforceMainKeySignatureValidity(
req, req,
@ -111,25 +112,13 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
this.webServer.addRoute({ this.webServer.addRoute({
method: 'GET', method: 'GET',
path: /^\/_enig\/ap\/users\/.+\/followers(\?page=[0-9]+)?$/, path: /^\/_enig\/ap\/users\/.+\/followers(\?page=[0-9]+)?$/,
handler: (req, resp) => { handler: this._followersGetHandler.bind(this),
return this._enforceMainKeySignatureValidity(
req,
resp,
this._followersGetHandler.bind(this)
);
},
}); });
this.webServer.addRoute({ this.webServer.addRoute({
method: 'GET', method: 'GET',
path: /^\/_enig\/ap\/users\/.+\/following(\?page=[0-9]+)?$/, path: /^\/_enig\/ap\/users\/.+\/following(\?page=[0-9]+)?$/,
handler: (req, resp) => { handler: this._followingGetHandler.bind(this),
return this._enforceMainKeySignatureValidity(
req,
resp,
this._followingGetHandler.bind(this)
);
},
}); });
this.webServer.addRoute({ 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; // 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 // If this is the case, they may be following multiple local Actor/users
// and we have multiple entries. // and we have multiple entries.
const stats = {
deleted: [],
failed: [],
};
async.forEachSeries( async.forEachSeries(
objectsInfo, objectsInfo,
(objInfo, nextObjInfo) => { (objInfo, nextObjInfo) => {
const collectionName = objInfo.info.name;
if (objInfo.object) { if (objInfo.object) {
// Based on the collection we find this entry in, // Based on the collection we find this entry in,
// we may have additional validation or actions // we may have additional validation or actions
switch (objInfo.info.name) { switch (collectionName) {
case Collections.Inbox: case Collections.Inbox:
if (inboxType !== Collections.Inbox) { case Collections.SharedInbox:
// :TODO: LOG ME // 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); return nextObjInfo(null);
} }
// Validate signature
break; break;
case Collections.SharedInbox: case Collections.Actors:
if (inboxType !== Collections.SharedInbox) { // Validate signature; Delete Actor and Following entries if any
// :TODO: log me
return nextObjInfo(null);
}
break; break;
case Collection.Following:
break;
default: default:
break; break;
} }
@ -496,12 +500,15 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return nextObjInfo(null); return nextObjInfo(null);
} else { } else {
// it's unparsable, so we'll delete it // it's unparsable, so we'll delete it
Collection.removeById(objInfo.info.name, objectId, err => { Collection.removeById(collectionName, objectId, err => {
if (err) { if (err) {
this.log.warn( this.log.warn(
{ objectId, collectionName: objInfo.info.name }, { objectId, collectionName },
'Failed to remove object' 'Failed to remove object'
); );
stats.failed.push({ collectionName, objectId });
} else {
stats.deleted.push({ collectionName, objectId });
} }
return nextObjInfo(null); return nextObjInfo(null);
}); });
@ -511,6 +518,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
if (err) { if (err) {
// :TODO: log me // :TODO: log me
} }
this.log.info({ stats, inboxType }, 'Inbox Delete request complete');
return this.webServer.accepted(resp); return this.webServer.accepted(resp);
} }
); );
@ -823,6 +832,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
} }
_actorCollectionRequest(collectionName, req, resp) { _actorCollectionRequest(collectionName, req, resp) {
this.log.debug({ url: req.url }, `Request for "${collectionName}"`);
const getCollection = Collection[collectionName]; const getCollection = Collection[collectionName];
if (!getCollection) { if (!getCollection) {
return this.webServer.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
@ -863,12 +874,10 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
} }
_followingGetHandler(req, resp) { _followingGetHandler(req, resp) {
this.log.debug({ url: req.url }, 'Request for "following"');
return this._actorCollectionRequest(Collections.Following, req, resp); return this._actorCollectionRequest(Collections.Following, req, resp);
} }
_followersGetHandler(req, resp) { _followersGetHandler(req, resp) {
this.log.debug({ url: req.url }, 'Request for "followers"');
return this._actorCollectionRequest(Collections.Followers, req, resp); return this._actorCollectionRequest(Collections.Followers, req, resp);
} }