mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-30 02:25:55 +01:00
feat: security, cross-check key ownership against received actor
This commit is contained in:
@@ -31,6 +31,7 @@ Actors.assert = async (ids, options = {}) => {
|
|||||||
winston.verbose(`[activitypub/actors] Asserting ${ids.length} actor(s)`);
|
winston.verbose(`[activitypub/actors] Asserting ${ids.length} actor(s)`);
|
||||||
|
|
||||||
const followersUrlMap = new Map();
|
const followersUrlMap = new Map();
|
||||||
|
const pubKeysMap = new Map();
|
||||||
const actors = await Promise.all(ids.map(async (id) => {
|
const actors = await Promise.all(ids.map(async (id) => {
|
||||||
try {
|
try {
|
||||||
winston.verbose(`[activitypub/actors] Processing ${id}`);
|
winston.verbose(`[activitypub/actors] Processing ${id}`);
|
||||||
@@ -58,6 +59,9 @@ Actors.assert = async (ids, options = {}) => {
|
|||||||
followersUrlMap.set(actor.followers, actor.id);
|
followersUrlMap.set(actor.followers, actor.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public keys
|
||||||
|
pubKeysMap.set(actor.id, actor.publicKey);
|
||||||
|
|
||||||
return actor;
|
return actor;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -68,13 +72,14 @@ Actors.assert = async (ids, options = {}) => {
|
|||||||
const profiles = await activitypub.mocks.profile(actors);
|
const profiles = await activitypub.mocks.profile(actors);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
const bulkSet = profiles.map((profile) => {
|
const bulkSet = profiles.reduce((memo, profile) => {
|
||||||
if (!profile) {
|
if (profile) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const key = `userRemote:${profile.uid}`;
|
const key = `userRemote:${profile.uid}`;
|
||||||
return [key, profile];
|
memo.push([key, profile], [`${key}:keys`, pubKeysMap.get(profile.uid)]);
|
||||||
}).filter(Boolean);
|
}
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}, []);
|
||||||
if (followersUrlMap.size) {
|
if (followersUrlMap.size) {
|
||||||
bulkSet.push(['followersUrl:uid', Object.fromEntries(followersUrlMap)]);
|
bulkSet.push(['followersUrl:uid', Object.fromEntries(followersUrlMap)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const db = require('../database');
|
||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
const activitypub = require('../activitypub');
|
const activitypub = require('../activitypub');
|
||||||
|
|
||||||
@@ -39,11 +40,29 @@ middleware.validate = async function (req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sanity-check payload schema
|
// Sanity-check payload schema
|
||||||
const required = ['type'];
|
const required = ['type', 'actor', 'object'];
|
||||||
if (!required.every(prop => req.body.hasOwnProperty(prop))) {
|
if (!required.every(prop => req.body.hasOwnProperty(prop))) {
|
||||||
return res.sendStatus(400);
|
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();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ describe('ActivityPub integration', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.only('.resolveId()', () => {
|
describe('.resolveId()', () => {
|
||||||
let url;
|
let url;
|
||||||
let resolved;
|
let resolved;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user