feat: actor cache, method to resolve inboxes, stub code for sending requests. Now base64 encoding digest as expected by Mastodon

This commit is contained in:
Julian Lam
2023-06-23 14:59:47 -04:00
parent 2e89900886
commit cdc4275fec
5 changed files with 87 additions and 27 deletions

View File

@@ -1,7 +1,7 @@
'use strict';
const request = require('request-promise-native');
const { generateKeyPairSync, sign } = require('crypto');
const { generateKeyPairSync } = require('crypto');
const winston = require('winston');
const db = require('../database');

View File

@@ -7,12 +7,18 @@ const { createHash, createSign, createVerify } = require('crypto');
const db = require('../database');
const user = require('../user');
const ttl = require('../cache/ttl');
const actorCache = ttl({ ttl: 1000 * 60 * 60 * 24 }); // 24 hours
const ActivityPub = module.exports;
ActivityPub.helpers = require('./helpers');
ActivityPub.getActor = async (id) => {
if (actorCache.has(id)) {
return actorCache.get(id);
}
const { hostname, actorUri: uri } = await ActivityPub.helpers.query(id);
if (!uri) {
return false;
@@ -28,9 +34,15 @@ ActivityPub.getActor = async (id) => {
actor.hostname = hostname;
actorCache.set(id, actor);
return actor;
};
ActivityPub.resolveInboxes = async ids => await Promise.all(ids.map(async (id) => {
const actor = await ActivityPub.getActor(id);
return actor.inbox;
}));
ActivityPub.getPublicKey = async (uid) => {
let publicKey;
@@ -84,7 +96,7 @@ ActivityPub.sign = async (uid, url, payload) => {
if (payload) {
const payloadHash = createHash('sha256');
payloadHash.update(JSON.stringify(payload));
digest = payloadHash.digest('hex');
digest = `sha-256=${payloadHash.digest('base64')}`;
headers += ' digest';
signed_string += `\ndigest: ${digest}`;
}
@@ -146,26 +158,43 @@ ActivityPub.verify = async (req) => {
}
};
/**
* This is just some code to test signing and verification. This should really be in the test suite.
*/
// setTimeout(async () => {
// const payload = {
// foo: 'bar',
// };
// const signature = await ActivityPub.sign(1, 'http://127.0.0.1:4567/user/julian/inbox', payload);
ActivityPub.send = async (uid, targets, payload) => {
if (!Array.isArray(targets)) {
targets = [targets];
}
// const res = await request({
// uri: 'http://127.0.0.1:4567/user/julian/inbox',
// method: 'post',
// headers: {
// Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
// ...signature,
// },
// json: true,
// body: payload,
// simple: false,
// });
const userslug = await user.getUserField(uid, 'userslug');
const inboxes = await ActivityPub.resolveInboxes(targets);
// console.log(res);
// }, 1000);
payload = {
...{
'@context': 'https://www.w3.org/ns/activitystreams',
actor: {
type: 'Person',
name: `${userslug}@${nconf.get('url_parsed').host}`,
},
},
...payload,
};
await Promise.all(inboxes.map(async (uri) => {
const { date, digest, signature } = await ActivityPub.sign(uid, uri, payload);
const response = await request(uri, {
method: payload ? 'post' : 'get',
headers: {
date,
digest,
signature,
'content-type': 'application/ld+json; profile="http://www.w3.org/ns/activitystreams',
accept: 'application/ld+json; profile="http://www.w3.org/ns/activitystreams',
},
json: true,
body: payload,
simple: false,
resolveWithFullResponse: true,
});
console.log(response.statusCode, response.body);
}));
};

View File

@@ -2,7 +2,7 @@
const nconf = require('nconf');
const user = require('../user');
const user = require('../../user');
const activitypub = require('../../activitypub');
const Controller = module.exports;
@@ -103,3 +103,24 @@ Controller.postInbox = async (req, res) => {
res.sendStatus(201);
};
/**
* Main ActivityPub verbs
*/
Controller.follow = async (req, res) => {
await activitypub.send(req.uid, req.params.uid, {
type: 'Follow',
object: {
type: 'Person',
name: req.params.uid,
},
});
res.sendStatus(201);
};
Controller.unfollow = async (req, res) => {
console.log('got here');
res.sendStatus(201);
};

View File

@@ -9,6 +9,8 @@ const user = require('../../user');
const helpers = require('../helpers');
const activitypubController = require('../activitypub');
const Users = module.exports;
Users.redirectBySlug = async (req, res) => {
@@ -92,11 +94,19 @@ Users.changePassword = async (req, res) => {
};
Users.follow = async (req, res) => {
if (req.params.uid.indexOf('@') !== -1) {
return await activitypubController.follow(req, res);
}
await api.users.follow(req, req.params);
helpers.formatApiResponse(200, res);
};
Users.unfollow = async (req, res) => {
if (req.params.uid.indexOf('@') !== -1) {
return await activitypubController.unfollow(req, res);
}
await api.users.unfollow(req, req.params);
helpers.formatApiResponse(200, res);
};

View File

@@ -251,14 +251,14 @@ describe('ActivityPub integration', () => {
const { digest } = await activitypub.sign(uid, endpoint, payload);
const hash = createHash('sha256');
hash.update(JSON.stringify(payload));
const checksum = hash.digest('hex');
const checksum = hash.digest('base64');
assert(digest);
assert.strictEqual(digest, checksum);
assert.strictEqual(digest, `sha-256=${checksum}`);
});
});
describe.only('.verify()', () => {
describe('.verify()', () => {
let uid;
let username;
const mockReqBase = {