mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-30 18:46:01 +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 nconf = require('nconf'); | ||||||
| const validator = require('validator'); | const validator = require('validator'); | ||||||
|  |  | ||||||
|  | const posts = require('../posts'); | ||||||
| const request = require('../request'); | const request = require('../request'); | ||||||
| const db = require('../database'); | const db = require('../database'); | ||||||
| const ttl = require('../cache/ttl'); | const ttl = require('../cache/ttl'); | ||||||
| @@ -99,28 +100,34 @@ Helpers.generateKeys = async (type, id) => { | |||||||
|  |  | ||||||
| Helpers.resolveLocalId = async (input) => { | Helpers.resolveLocalId = async (input) => { | ||||||
| 	if (Helpers.isUri(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) { | 		if (host === nconf.get('url_parsed').host) { | ||||||
| 			const [prefix, value] = pathname.replace(nconf.get('relative_path'), '').split('/').filter(Boolean); | 			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) { | 			switch (prefix) { | ||||||
| 				case 'uid': | 				case 'uid': | ||||||
| 					return { type: 'user', id: value }; | 					return { type: 'user', id: value, ...activityData }; | ||||||
|  |  | ||||||
| 				case 'post': | 				case 'post': | ||||||
| 					return { type: 'post', id: value }; | 					return { type: 'post', id: value, ...activityData }; | ||||||
|  |  | ||||||
| 				case 'category': | 				case 'category': | ||||||
| 					return { type: 'category', id: value }; | 					return { type: 'category', id: value, ...activityData }; | ||||||
|  |  | ||||||
| 				case 'user': { | 				case 'user': { | ||||||
| 					const uid = await user.getUidByUserslug(value); | 					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 }; | 		return { type: null, id: null }; | ||||||
| @@ -132,3 +139,75 @@ Helpers.resolveLocalId = async (input) => { | |||||||
|  |  | ||||||
| 	return { type: null, id: null }; | 	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 actorHostname = new URL(actor).hostname; | ||||||
| 		const objectHostname = new URL(object.id).hostname; | 		const objectHostname = new URL(object.id).hostname; | ||||||
| 		if (actorHostname !== objectHostname) { | 		if (actorHostname !== objectHostname) { | ||||||
| 			winston.verbose('[middleware/activitypub] Origin check failed.'); | 			winston.verbose('[middleware/activitypub] Origin check failed, stripping object down to id.'); | ||||||
| 			return res.sendStatus(403); | 			req.body.object = [object.id]; | ||||||
| 		} | 		} | ||||||
| 		winston.verbose('[middleware/activitypub] Origin check passed.'); | 		winston.verbose('[middleware/activitypub] Origin check passed.'); | ||||||
| 	} | 	} | ||||||
| @@ -75,6 +75,21 @@ middleware.validate = async function (req, res, next) { | |||||||
| 	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) { | middleware.configureResponse = async function (req, res, next) { | ||||||
| 	res.header('Content-Type', 'application/activity+json'); | 	res.header('Content-Type', 'application/activity+json'); | ||||||
| 	next(); | 	next(); | ||||||
|   | |||||||
| @@ -17,13 +17,18 @@ module.exports = function (app, middleware, controllers) { | |||||||
| 		middleware.activitypub.configureResponse, | 		middleware.activitypub.configureResponse, | ||||||
| 	]; | 	]; | ||||||
|  |  | ||||||
|  | 	const inboxMiddlewares = [ | ||||||
|  | 		middleware.activitypub.validate, | ||||||
|  | 		middleware.activitypub.resolveObjects, | ||||||
|  | 	]; | ||||||
|  |  | ||||||
| 	app.get('/actor', middlewares, controllers.activitypub.actors.application); | 	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('/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('/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.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.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.getOutbox); | ||||||
| 	app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.postOutbox); | 	app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.postOutbox); | ||||||
| 	app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowing); | 	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/:slug?', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category); | ||||||
| 	app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], controllers.activitypub.getInbox); | 	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.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox); | ||||||
| 	app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox); | 	app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox); | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user