mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: resolve objects from ids in middleware
This commit is contained in:
@@ -5,6 +5,7 @@ const winston = require('winston');
|
||||
const nconf = require('nconf');
|
||||
const validator = require('validator');
|
||||
|
||||
const posts = require('../posts');
|
||||
const request = require('../request');
|
||||
const db = require('../database');
|
||||
const ttl = require('../cache/ttl');
|
||||
@@ -99,28 +100,34 @@ Helpers.generateKeys = async (type, id) => {
|
||||
|
||||
Helpers.resolveLocalId = async (input) => {
|
||||
if (Helpers.isUri(input)) {
|
||||
const { host, pathname } = new URL(input);
|
||||
const { host, pathname, hash } = new URL(input);
|
||||
|
||||
if (host === nconf.get('url_parsed').host) {
|
||||
const [prefix, value] = pathname.replace(nconf.get('relative_path'), '').split('/').filter(Boolean);
|
||||
|
||||
let activityData = {};
|
||||
if (hash.startsWith('#activity')) {
|
||||
const [, activity, data] = hash.split('/', 3);
|
||||
activityData = { activity, data };
|
||||
}
|
||||
|
||||
switch (prefix) {
|
||||
case 'uid':
|
||||
return { type: 'user', id: value };
|
||||
return { type: 'user', id: value, ...activityData };
|
||||
|
||||
case 'post':
|
||||
return { type: 'post', id: value };
|
||||
return { type: 'post', id: value, ...activityData };
|
||||
|
||||
case 'category':
|
||||
return { type: 'category', id: value };
|
||||
return { type: 'category', id: value, ...activityData };
|
||||
|
||||
case 'user': {
|
||||
const uid = await user.getUidByUserslug(value);
|
||||
return { type: 'user', id: uid };
|
||||
return { type: 'user', id: uid, ...activityData };
|
||||
}
|
||||
}
|
||||
|
||||
return { type: null, id: null };
|
||||
return { type: null, id: null, ...activityData };
|
||||
}
|
||||
|
||||
return { type: null, id: null };
|
||||
@@ -132,3 +139,75 @@ Helpers.resolveLocalId = async (input) => {
|
||||
|
||||
return { type: null, id: null };
|
||||
};
|
||||
|
||||
Helpers.resolveActor = (type, id) => {
|
||||
switch (type) {
|
||||
case 'user':
|
||||
case 'uid': {
|
||||
return `${nconf.get('url')}${id > 0 ? `/uid/${id}` : '/actor'}`;
|
||||
}
|
||||
|
||||
case 'category':
|
||||
case 'cid': {
|
||||
return `${nconf.get('url')}/category/${id}`;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error('[[error:activitypub.invalid-id]]');
|
||||
}
|
||||
};
|
||||
|
||||
Helpers.resolveActivity = async (activity, data, id, resolved) => {
|
||||
switch (activity.toLowerCase()) {
|
||||
case 'follow': {
|
||||
const actor = await Helpers.resolveActor(resolved.type, resolved.id);
|
||||
const { actorUri: targetUri } = await Helpers.query(data);
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
actor,
|
||||
id,
|
||||
type: 'Follow',
|
||||
object: targetUri,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
throw new Error('[[error:activitypub.not-implemented]]');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Helpers.resolveObjects = async (ids) => {
|
||||
if (!Array.isArray(ids)) {
|
||||
ids = [ids];
|
||||
}
|
||||
const objects = await Promise.all(ids.map(async (id) => {
|
||||
const { type, id: resolvedId, activity, data: activityData } = await Helpers.resolveLocalId(id);
|
||||
if (activity) {
|
||||
return Helpers.resolveActivity(activity, activityData, id, { type, id: resolvedId });
|
||||
}
|
||||
switch (type) {
|
||||
case 'user': {
|
||||
return activitypub.mocks.actors.user(resolvedId);
|
||||
}
|
||||
case 'post': {
|
||||
const post = (await posts.getPostSummaryByPids(
|
||||
[resolvedId],
|
||||
activitypub._constants.uid,
|
||||
{ stripTags: false }
|
||||
)).pop();
|
||||
if (!post) {
|
||||
return;
|
||||
}
|
||||
return activitypub.mocks.note(post);
|
||||
}
|
||||
case 'category': {
|
||||
return activitypub.mocks.category(resolvedId);
|
||||
}
|
||||
default: {
|
||||
return activitypub.get('uid', 0, id);
|
||||
}
|
||||
}
|
||||
}));
|
||||
return objects.length === 1 ? objects[0] : objects;
|
||||
};
|
||||
|
||||
@@ -55,8 +55,8 @@ middleware.validate = async function (req, res, next) {
|
||||
const actorHostname = new URL(actor).hostname;
|
||||
const objectHostname = new URL(object.id).hostname;
|
||||
if (actorHostname !== objectHostname) {
|
||||
winston.verbose('[middleware/activitypub] Origin check failed.');
|
||||
return res.sendStatus(403);
|
||||
winston.verbose('[middleware/activitypub] Origin check failed, stripping object down to id.');
|
||||
req.body.object = [object.id];
|
||||
}
|
||||
winston.verbose('[middleware/activitypub] Origin check passed.');
|
||||
}
|
||||
@@ -75,6 +75,21 @@ middleware.validate = async function (req, res, next) {
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.resolveObjects = async function (req, res, next) {
|
||||
const { object } = req.body;
|
||||
if (typeof object === 'string' || (Array.isArray(object) && object.every(o => typeof o === 'string'))) {
|
||||
winston.verbose('[middleware/activitypub] Resolving object(s)...');
|
||||
try {
|
||||
req.body.object = await activitypub.helpers.resolveObjects(object);
|
||||
winston.verbose('[middleware/activitypub] Object(s) successfully resolved.');
|
||||
} catch (e) {
|
||||
winston.verbose('[middleware/activitypub] Failed to resolve object(s).');
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.configureResponse = async function (req, res, next) {
|
||||
res.header('Content-Type', 'application/activity+json');
|
||||
next();
|
||||
|
||||
@@ -17,13 +17,18 @@ module.exports = function (app, middleware, controllers) {
|
||||
middleware.activitypub.configureResponse,
|
||||
];
|
||||
|
||||
const inboxMiddlewares = [
|
||||
middleware.activitypub.validate,
|
||||
middleware.activitypub.resolveObjects,
|
||||
];
|
||||
|
||||
app.get('/actor', middlewares, controllers.activitypub.actors.application);
|
||||
app.post('/inbox', [...middlewares, middleware.activitypub.validate], controllers.activitypub.postInbox);
|
||||
app.post('/inbox', [...middlewares, ...inboxMiddlewares], controllers.activitypub.postInbox);
|
||||
|
||||
app.get('/uid/:uid', [...middlewares, middleware.assert.user], controllers.activitypub.actors.user);
|
||||
app.get('/user/:userslug', [...middlewares, middleware.exposeUid, middleware.assert.user], controllers.activitypub.actors.userBySlug);
|
||||
app.get('/uid/:uid/inbox', [...middlewares, middleware.assert.user], controllers.activitypub.getInbox);
|
||||
app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, middleware.activitypub.validate], controllers.activitypub.postInbox);
|
||||
app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, ...inboxMiddlewares], controllers.activitypub.postInbox);
|
||||
app.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.getOutbox);
|
||||
app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.postOutbox);
|
||||
app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowing);
|
||||
@@ -35,7 +40,7 @@ module.exports = function (app, middleware, controllers) {
|
||||
|
||||
app.get('/category/:cid/:slug?', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category);
|
||||
app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], controllers.activitypub.getInbox);
|
||||
app.post('/category/:cid/inbox', [...middlewares, middleware.assert.category, middleware.activitypub.validate], controllers.activitypub.postInbox);
|
||||
app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], controllers.activitypub.postInbox);
|
||||
app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox);
|
||||
app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user