prototype automatic follow acceptance with signed request

This commit is contained in:
Will Murphy 2019-09-21 15:02:50 -05:00
parent 06d6f85960
commit 4d7e99461d
2 changed files with 71 additions and 7 deletions

View file

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

View file

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