mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-28 09:36:16 +01:00
73 lines
2.2 KiB
JavaScript
73 lines
2.2 KiB
JavaScript
'use strict';
|
|
|
|
const db = require('../database');
|
|
const meta = require('../meta');
|
|
const activitypub = require('../activitypub');
|
|
|
|
const middleware = module.exports;
|
|
|
|
middleware.enabled = async (req, res, next) => next(!meta.config.activitypubEnabled ? 'route' : undefined);
|
|
|
|
middleware.assertS2S = async function (req, res, next) {
|
|
// For whatever reason, express accepts does not recognize "profile" as a valid differentiator
|
|
// Therefore, manual header parsing is used here.
|
|
const { accept, 'content-type': contentType } = req.headers;
|
|
if (!(accept || contentType)) {
|
|
return next('route');
|
|
}
|
|
|
|
const acceptable = [
|
|
'application/activity+json',
|
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
|
];
|
|
const pass = (accept && accept.split(',').some((value) => {
|
|
const parts = value.split(';').map(v => v.trim());
|
|
return acceptable.includes(value || parts[0]);
|
|
})) || (contentType && acceptable.includes(contentType));
|
|
|
|
if (!pass) {
|
|
return next('route');
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
middleware.validate = async function (req, res, next) {
|
|
// Checks the validity of the incoming payload against the sender and rejects on failure
|
|
const verified = await activitypub.verify(req);
|
|
if (!verified) {
|
|
return res.sendStatus(400);
|
|
}
|
|
|
|
// Sanity-check payload schema
|
|
const required = ['type', 'actor', 'object'];
|
|
if (!required.every(prop => req.body.hasOwnProperty(prop))) {
|
|
return res.sendStatus(400);
|
|
}
|
|
|
|
const { actor, object } = req.body;
|
|
|
|
// Origin checking
|
|
const actorHostname = new URL(actor).hostname;
|
|
const objectHostname = new URL(typeof object === 'string' ? object : object.id).hostname;
|
|
if (actorHostname !== objectHostname) {
|
|
return res.sendStatus(403);
|
|
}
|
|
|
|
// Cross-check key ownership against received actor
|
|
await activitypub.actors.assert(actor);
|
|
const compare = await db.getObjectField(`userRemote:${actor}:keys`, 'id');
|
|
const { signature } = req.headers;
|
|
const keyId = new Map(signature.split(',').filter(Boolean).map(v => v.split('='))).get('keyId');
|
|
if (`"${compare}"` !== keyId) {
|
|
return res.sendStatus(403);
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
middleware.configureResponse = async function (req, res, next) {
|
|
res.header('Content-Type', 'application/activity+json');
|
|
next();
|
|
};
|