From 4d7e99461d5e18ae835dfbf8413f050db43933bb Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Sat, 21 Sep 2019 15:02:50 -0500 Subject: [PATCH] prototype automatic follow acceptance with signed request --- routes/inbox.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++- utils/index.js | 14 ++++++----- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/routes/inbox.js b/routes/inbox.js index b8c8d6c..d7a0d59 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -1,10 +1,72 @@ const express = require('express') const router = express.Router() const utils = require('../utils') +const request = require('request-promise-native') +const httpSignature = require('http-signature') +const {ObjectId} = require('mongodb') router.post('/', utils.validators.activity, function (req, res) { const db = req.app.get('db'); - req.body._target = req.user + let outgoingResponse + req.body._meta = {_target: utils.usernameToIRI(req.user)} + // side effects + switch(req.body.type) { + case 'Accept': + // workaround for node-http-signature#87 + const tempUrl = req.url + req.url = req.originalUrl + sigHead = httpSignature.parse(req, {clockSkew: 3000}) + req.url = tempUrl + // TODO - real key lookup with remote fetch + utils.getOrCreateActor(sigHead.keyId.replace(/.*\//, ''), db) + .then(user => { + const valid = httpSignature.verifySignature(sigHead, user.publicKey.publicKeyPem) + console.log('key validation', valid) + return res.status(valid ? 200 : 401).send() + }) + break + case 'Follow': + req.body._meta._target = req.body.object.id + // send acceptance reply + Promise.all([ + utils.getOrCreateActor(req.user, db, true), + request({ + url: utils.actorFromActivity(req.body), + headers: {Accept: 'application/activity+json'}, + json: true, + }) + ]) + .then(([user, actor]) => { + if (!actor || !actor.inbox) { + throw new Error('unable to send follow request acceptance: actor inbox not retrievable') + } + const newID = new ObjectId() + const responseOpts = { + method: 'POST', + url: actor.inbox, + headers: { + 'Content-Type': 'application/activity+json', + }, + httpSignature: { + key: user._meta.privateKey, + keyId: user.id, + headers: ['(request-target)', 'host', 'date'], + }, + json: true, + body: utils.toJSONLD({ + _id: newID, + type: 'Accept', + id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`, + actor: user.id, + object: req.body, + }), + } + return request(responseOpts) + }) + .then(result => console.log('success', result)) + .catch(e => console.log(e)) + break + } Promise.all([ db.collection('objects').insertOne(req.body.object), db.collection('streams').insertOne(req.body) diff --git a/utils/index.js b/utils/index.js index 3d85cd4..be11f8d 100644 --- a/utils/index.js +++ b/utils/index.js @@ -51,8 +51,6 @@ function createLocalActor (name, type) { privateKeyEncoding: { type: 'pkcs8', format: 'pem', - cipher: 'aes-256-cbc', - passphrase: config.KEYPASS } }).then(pair => { const actorBase = usernameToIRI(name); @@ -80,13 +78,15 @@ function createLocalActor (name, type) { }) } module.exports.createLocalActor = createLocalActor - -async function getOrCreateActor(preferredUsername, db) { +const actorProj = {_id: 0, _meta: 0} +const metaActorProj = {_id: 0} +async function getOrCreateActor(preferredUsername, db, includeMeta) { const id = usernameToIRI(preferredUsername) let user = await db.collection('objects') .find({id: id}) .limit(1) - .project({_id: 0, _meta: 0}) + // strict comparison as we don't want to return private keys on accident + .project(includeMeta === true ? metaActorProj : actorProj) .next() if (user) { return user @@ -95,8 +95,10 @@ async function getOrCreateActor(preferredUsername, db) { user = await createLocalActor(preferredUsername, 'Group') await db.collection('objects').insertOne(user) // only executed on success - delete user._meta delete user._id + if (includeMeta !== true) { + delete user._meta + } return user } module.exports.getOrCreateActor = getOrCreateActor