diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index ac91ad239d..89c62192e2 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -10,16 +10,16 @@ const inbox = module.exports; inbox.follow = async (req) => { // Sanity checks - const from = await activitypub.getActor(req.body.actor); - if (!from) { - throw new Error('[[error:invalid-uid]]'); // should probably be AP specific - } - const localUid = await helpers.resolveLocalUid(req.body.object); if (!localUid) { throw new Error('[[error:invalid-uid]]'); } + const from = await activitypub.getActor(localUid, req.body.actor); + if (!from) { + throw new Error('[[error:invalid-uid]]'); // should probably be AP specific + } + const isFollowed = await inbox.isFollowed(from.id, localUid); if (isFollowed) { // No additional parsing required @@ -51,15 +51,14 @@ inbox.accept = async (req) => { let { actor, object } = req.body; const { type } = object; - actor = await activitypub.getActor(actor); + const uid = await helpers.resolveLocalUid(object.actor); + if (!uid) { + throw new Error('[[error:invalid-uid]]'); + } + + actor = await activitypub.getActor(uid, actor); if (type === 'Follow') { - // todo: should check that actor and object.actor are the same person? - const uid = await helpers.resolveLocalUid(object.actor); - if (!uid) { - throw new Error('[[error:invalid-uid]]'); - } - const now = Date.now(); await Promise.all([ db.sortedSetAdd(`followingRemote:${uid}`, now, actor.id), @@ -72,15 +71,14 @@ inbox.undo = async (req) => { let { actor, object } = req.body; const { type } = object; - actor = await activitypub.getActor(actor); + const uid = await helpers.resolveLocalUid(object.object); + if (!uid) { + throw new Error('[[error:invalid-uid]]'); + } + + actor = await activitypub.getActor(uid, actor); if (type === 'Follow') { - // todo: should check that actor and object.actor are the same person? - const uid = await helpers.resolveLocalUid(object.object); - if (!uid) { - throw new Error('[[error:invalid-uid]]'); - } - await Promise.all([ db.sortedSetRemove(`followingRemote:${uid}`, actor.id), db.decrObjectField(`user:${uid}`, 'followingRemoteCount'), diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 98f390ddb7..272206a168 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -1,6 +1,7 @@ 'use strict'; const nconf = require('nconf'); +const winston = require('winston'); const { createHash, createSign, createVerify } = require('crypto'); const request = require('../request'); @@ -14,7 +15,7 @@ const ActivityPub = module.exports; ActivityPub.helpers = require('./helpers'); ActivityPub.inbox = require('./inbox'); -ActivityPub.getActor = async (input) => { +ActivityPub.getActor = async (uid, input) => { // Can be a webfinger id, uri, or object, handle as appropriate let uri; if (ActivityPub.helpers.isUri(input)) { @@ -33,7 +34,7 @@ ActivityPub.getActor = async (input) => { return actorCache.get(uri); } - const actor = await ActivityPub.get(uri); + const actor = await ActivityPub.get(uid, uri); // todo: remove this after ActivityPub.get is updated to handle errors more effectively if (typeof actor === 'string' || actor.hasOwnProperty('error')) { @@ -41,8 +42,8 @@ ActivityPub.getActor = async (input) => { } const [followers, following] = await Promise.all([ - actor.followers ? ActivityPub.get(actor.followers) : { totalItems: 0 }, - actor.following ? ActivityPub.get(actor.following) : { totalItems: 0 }, + actor.followers ? ActivityPub.get(uid, actor.followers) : { totalItems: 0 }, + actor.following ? ActivityPub.get(uid, actor.following) : { totalItems: 0 }, ]); actor.hostname = new URL(uri).hostname; @@ -64,7 +65,7 @@ ActivityPub.mockProfile = async (actors, callerUid = 0) => { const profiles = (await Promise.all(actors.map(async (actor) => { // convert uri to actor object if (typeof actor === 'string' && ActivityPub.helpers.isUri(actor)) { - actor = await ActivityPub.getActor(actor); + actor = await ActivityPub.getActor(callerUid, actor); } if (!actor) { @@ -111,8 +112,8 @@ ActivityPub.mockProfile = async (actors, callerUid = 0) => { return single ? profiles.pop() : profiles; }; -ActivityPub.resolveInboxes = async ids => await Promise.all(ids.map(async (id) => { - const actor = await ActivityPub.getActor(id); +ActivityPub.resolveInboxes = async (uid, ids) => await Promise.all(ids.map(async (id) => { + const actor = await ActivityPub.getActor(uid, id); return actor.inbox; })); @@ -222,13 +223,22 @@ ActivityPub.verify = async (req) => { } }; -ActivityPub.get = async (uri) => { - const { body } = await request.get(uri, { +ActivityPub.get = async (uid, uri) => { + const headers = uid > 0 ? await ActivityPub.sign(uid, uri) : {}; + const { response, body } = await request.get(uri, { headers: { + ...headers, Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', }, }); + if (!String(response.statusCode).startsWith('2')) { + winston.error(`[activitypub/get] Received ${response.statusCode} when querying ${uri}`); + if (body.hasOwnProperty('error')) { + winston.error(`[activitypub/get] Error received: ${body.error}`); + } + } + return body; }; @@ -238,7 +248,7 @@ ActivityPub.send = async (uid, targets, payload) => { } const userslug = await user.getUserField(uid, 'userslug'); - const inboxes = await ActivityPub.resolveInboxes(targets); + const inboxes = await ActivityPub.resolveInboxes(uid, targets); payload = { '@context': 'https://www.w3.org/ns/activitystreams', diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 8e843b2857..86d9fb9d32 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -17,7 +17,7 @@ const user = require('../user'); const activitypubApi = module.exports; activitypubApi.follow = async (caller, { actorId } = {}) => { - const object = await activitypub.getActor(actorId); + const object = await activitypub.getActor(caller.uid, actorId); if (!object) { throw new Error('[[error:invalid-uid]]'); // should be activitypub-specific } @@ -29,7 +29,7 @@ activitypubApi.follow = async (caller, { actorId } = {}) => { }; activitypubApi.unfollow = async (caller, { actorId }) => { - const object = await activitypub.getActor(actorId); + const object = await activitypub.getActor(caller.uid, actorId); const userslug = await user.getUserField(caller.uid, 'userslug'); if (!object) { throw new Error('[[error:invalid-uid]]'); // should be activitypub-specific