mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-02-06 06:40:07 +01:00
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:
@@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const request = require('request-promise-native');
|
const request = require('request-promise-native');
|
||||||
const { generateKeyPairSync, sign } = require('crypto');
|
const { generateKeyPairSync } = require('crypto');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ const { createHash, createSign, createVerify } = require('crypto');
|
|||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
|
const ttl = require('../cache/ttl');
|
||||||
|
|
||||||
|
const actorCache = ttl({ ttl: 1000 * 60 * 60 * 24 }); // 24 hours
|
||||||
const ActivityPub = module.exports;
|
const ActivityPub = module.exports;
|
||||||
|
|
||||||
ActivityPub.helpers = require('./helpers');
|
ActivityPub.helpers = require('./helpers');
|
||||||
|
|
||||||
ActivityPub.getActor = async (id) => {
|
ActivityPub.getActor = async (id) => {
|
||||||
|
if (actorCache.has(id)) {
|
||||||
|
return actorCache.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
const { hostname, actorUri: uri } = await ActivityPub.helpers.query(id);
|
const { hostname, actorUri: uri } = await ActivityPub.helpers.query(id);
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
return false;
|
return false;
|
||||||
@@ -28,9 +34,15 @@ ActivityPub.getActor = async (id) => {
|
|||||||
|
|
||||||
actor.hostname = hostname;
|
actor.hostname = hostname;
|
||||||
|
|
||||||
|
actorCache.set(id, actor);
|
||||||
return 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) => {
|
ActivityPub.getPublicKey = async (uid) => {
|
||||||
let publicKey;
|
let publicKey;
|
||||||
|
|
||||||
@@ -84,7 +96,7 @@ ActivityPub.sign = async (uid, url, payload) => {
|
|||||||
if (payload) {
|
if (payload) {
|
||||||
const payloadHash = createHash('sha256');
|
const payloadHash = createHash('sha256');
|
||||||
payloadHash.update(JSON.stringify(payload));
|
payloadHash.update(JSON.stringify(payload));
|
||||||
digest = payloadHash.digest('hex');
|
digest = `sha-256=${payloadHash.digest('base64')}`;
|
||||||
headers += ' digest';
|
headers += ' digest';
|
||||||
signed_string += `\ndigest: ${digest}`;
|
signed_string += `\ndigest: ${digest}`;
|
||||||
}
|
}
|
||||||
@@ -146,26 +158,43 @@ ActivityPub.verify = async (req) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
ActivityPub.send = async (uid, targets, payload) => {
|
||||||
* This is just some code to test signing and verification. This should really be in the test suite.
|
if (!Array.isArray(targets)) {
|
||||||
*/
|
targets = [targets];
|
||||||
// setTimeout(async () => {
|
}
|
||||||
// const payload = {
|
|
||||||
// foo: 'bar',
|
|
||||||
// };
|
|
||||||
// const signature = await ActivityPub.sign(1, 'http://127.0.0.1:4567/user/julian/inbox', payload);
|
|
||||||
|
|
||||||
// const res = await request({
|
const userslug = await user.getUserField(uid, 'userslug');
|
||||||
// uri: 'http://127.0.0.1:4567/user/julian/inbox',
|
const inboxes = await ActivityPub.resolveInboxes(targets);
|
||||||
// method: 'post',
|
|
||||||
// headers: {
|
|
||||||
// Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
|
||||||
// ...signature,
|
|
||||||
// },
|
|
||||||
// json: true,
|
|
||||||
// body: payload,
|
|
||||||
// simple: false,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// console.log(res);
|
payload = {
|
||||||
// }, 1000);
|
...{
|
||||||
|
'@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);
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const user = require('../user');
|
const user = require('../../user');
|
||||||
const activitypub = require('../../activitypub');
|
const activitypub = require('../../activitypub');
|
||||||
|
|
||||||
const Controller = module.exports;
|
const Controller = module.exports;
|
||||||
@@ -103,3 +103,24 @@ Controller.postInbox = async (req, res) => {
|
|||||||
|
|
||||||
res.sendStatus(201);
|
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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const user = require('../../user');
|
|||||||
|
|
||||||
const helpers = require('../helpers');
|
const helpers = require('../helpers');
|
||||||
|
|
||||||
|
const activitypubController = require('../activitypub');
|
||||||
|
|
||||||
const Users = module.exports;
|
const Users = module.exports;
|
||||||
|
|
||||||
Users.redirectBySlug = async (req, res) => {
|
Users.redirectBySlug = async (req, res) => {
|
||||||
@@ -92,11 +94,19 @@ Users.changePassword = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Users.follow = 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);
|
await api.users.follow(req, req.params);
|
||||||
helpers.formatApiResponse(200, res);
|
helpers.formatApiResponse(200, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
Users.unfollow = async (req, 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);
|
await api.users.unfollow(req, req.params);
|
||||||
helpers.formatApiResponse(200, res);
|
helpers.formatApiResponse(200, res);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -251,14 +251,14 @@ describe('ActivityPub integration', () => {
|
|||||||
const { digest } = await activitypub.sign(uid, endpoint, payload);
|
const { digest } = await activitypub.sign(uid, endpoint, payload);
|
||||||
const hash = createHash('sha256');
|
const hash = createHash('sha256');
|
||||||
hash.update(JSON.stringify(payload));
|
hash.update(JSON.stringify(payload));
|
||||||
const checksum = hash.digest('hex');
|
const checksum = hash.digest('base64');
|
||||||
|
|
||||||
assert(digest);
|
assert(digest);
|
||||||
assert.strictEqual(digest, checksum);
|
assert.strictEqual(digest, `sha-256=${checksum}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.only('.verify()', () => {
|
describe('.verify()', () => {
|
||||||
let uid;
|
let uid;
|
||||||
let username;
|
let username;
|
||||||
const mockReqBase = {
|
const mockReqBase = {
|
||||||
|
|||||||
Reference in New Issue
Block a user