feat: resolve objects from ids in middleware

This commit is contained in:
Opliko
2024-04-09 23:58:00 +02:00
parent d437d969cc
commit 102c174e03
3 changed files with 110 additions and 11 deletions

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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);
};