diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js new file mode 100644 index 0000000000..7c77462839 --- /dev/null +++ b/src/activitypub/helpers.js @@ -0,0 +1,32 @@ +'use strict'; + +const request = require('request-promise-native'); + +const Helpers = module.exports; + +Helpers.query = async (id) => { + const [username, hostname] = id.split('@'); + if (!username || !hostname) { + return false; + } + + // Make a webfinger query to retrieve routing information + const response = await request(`https://${hostname}/.well-known/webfinger?resource=acct:${id}`, { + simple: false, + resolveWithFullResponse: true, + json: true, + }); + + if (response.statusCode !== 200 || !response.body.hasOwnProperty('links')) { + return false; + } + + // Parse links to find actor endpoint + let actorUri = response.body.links.filter(link => link.type === 'application/activity+json' && link.rel === 'self'); + if (actorUri.length) { + actorUri = actorUri.pop(); + ({ href: actorUri } = actorUri); + } + + return { username, hostname, actorUri }; +}; diff --git a/src/activitypub.js b/src/activitypub/index.js similarity index 63% rename from src/activitypub.js rename to src/activitypub/index.js index 4f4fe7ae37..7bac91ff48 100644 --- a/src/activitypub.js +++ b/src/activitypub/index.js @@ -3,11 +3,32 @@ const { generateKeyPairSync } = require('crypto'); const winston = require('winston'); +const request = require('request-promise-native'); -const db = require('./database'); +const db = require('../database'); +const helpers = require('./helpers'); const ActivityPub = module.exports; +ActivityPub.getActor = async (id) => { + const { hostname, actorUri: uri } = await helpers.query(id); + if (!uri) { + return false; + } + + const actor = await request({ + uri, + headers: { + Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + }, + json: true, + }); + + actor.hostname = hostname; + + return actor; +}; + ActivityPub.getPublicKey = async (uid) => { let publicKey; diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index 1ef9756784..909066da18 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -10,12 +10,18 @@ const categories = require('../../categories'); const plugins = require('../../plugins'); const privileges = require('../../privileges'); const accountHelpers = require('./helpers'); +const { getActor } = require('../../activitypub'); const helpers = require('../helpers'); +const slugify = require('../../slugify'); const utils = require('../../utils'); const profileController = module.exports; profileController.get = async function (req, res, next) { + if (res.locals.uid === -2) { + return profileController.getFederated(req, res, next); + } + const lowercaseSlug = req.params.userslug.toLowerCase(); if (req.params.userslug !== lowercaseSlug) { @@ -58,6 +64,32 @@ profileController.get = async function (req, res, next) { res.render('account/profile', userData); }; +profileController.getFederated = async function (req, res, next) { + const { userslug: uid } = req.params; + const actor = await getActor(uid); + if (!actor) { + return next(); + } + // console.log(actor); + const { preferredUsername, published, icon, image, name, summary, hostname } = actor; + + const payload = { + uid, + username: `${preferredUsername}@${hostname}`, + userslug: slugify(`${preferredUsername}@${hostname}`), + fullname: name, + joindate: new Date(published).getTime(), + picture: typeof icon === 'string' ? icon : icon.url, + uploadedpicture: typeof icon === 'string' ? icon : icon.url, + 'cover:url': typeof image === 'string' ? image : image.url, + 'cover:position': '50% 50%', + aboutme: summary, + aboutmeParsed: summary, + }; + + res.render('account/profile', payload); +}; + async function incrementProfileViews(req, userData) { if (req.uid >= 1) { req.session.uids_viewed = req.session.uids_viewed || {}; diff --git a/src/middleware/index.js b/src/middleware/index.js index f8ba614ab2..349178598d 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -172,7 +172,15 @@ async function expose(exposedField, method, field, req, res, next) { if (!req.params.hasOwnProperty(field)) { return next(); } - const value = await method(String(req.params[field]).toLowerCase()); + const param = String(req.params[field]).toLowerCase(); + + // potential hostname — ActivityPub + if (param.indexOf('@') !== -1) { + res.locals[exposedField] = -2; + return next(); + } + + const value = await method(param); if (!value) { next('route'); return;