From 35c029b12403a9511128aad07c84578f1f2e7729 Mon Sep 17 00:00:00 2001 From: Andrew Pietila Date: Tue, 11 Apr 2023 08:41:00 -0500 Subject: [PATCH] Set inbox to queue important tasks rather than handling them on arrival. --- migrations/20230411044004_inbox_signedby.js | 23 +++++++ migrations/20230411044233_inbox_expiry.js | 23 +++++++ migrations/20230411052346_queue.js | 23 +++++++ routes/inbox.js | 70 ++++++++++----------- 4 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 migrations/20230411044004_inbox_signedby.js create mode 100644 migrations/20230411044233_inbox_expiry.js create mode 100644 migrations/20230411052346_queue.js diff --git a/migrations/20230411044004_inbox_signedby.js b/migrations/20230411044004_inbox_signedby.js new file mode 100644 index 0000000..e3ac910 --- /dev/null +++ b/migrations/20230411044004_inbox_signedby.js @@ -0,0 +1,23 @@ +/* eslint-disable jsdoc/valid-types */ +// eslint doesn't seem to understand, but vscode does. +'use strict'; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.alterTable("inbox", (table) => { + table.string("signedby"); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.alterTable("inbox", (table) => { + table.dropColumn("signedby"); + }); +}; diff --git a/migrations/20230411044233_inbox_expiry.js b/migrations/20230411044233_inbox_expiry.js new file mode 100644 index 0000000..55db4c8 --- /dev/null +++ b/migrations/20230411044233_inbox_expiry.js @@ -0,0 +1,23 @@ +/* eslint-disable jsdoc/valid-types */ +// eslint doesn't seem to understand, but vscode does. +'use strict'; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.alterTable("inbox", (table) => { + table.string("expires"); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.alterTable("inbox", (table) => { + table.dropColumn("expires"); + }); +}; diff --git a/migrations/20230411052346_queue.js b/migrations/20230411052346_queue.js new file mode 100644 index 0000000..52b5f8b --- /dev/null +++ b/migrations/20230411052346_queue.js @@ -0,0 +1,23 @@ +/* eslint-disable jsdoc/valid-types */ +// eslint doesn't seem to understand, but vscode does. +'use strict'; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.createTable('queue', (table) => { + table.increments('id'); + table.string('task'); + table.string('data'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.dropTable('queue'); +}; diff --git a/routes/inbox.js b/routes/inbox.js index b9c2652..5a0a48c 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -3,7 +3,7 @@ var db = require('../lib/db'); var express = require('express'); var jsonld = require('jsonld'); -const http_agent = require('../lib/http_agent'); +var crypto = require('crypto'); module.exports = { /** @@ -11,47 +11,43 @@ module.exports = { */ route: (routeObj) => { routeObj.post(async (req, res, _next) => { - if ( req.body ) { - var bodyParsed = await jsonld.compact(req.body, 'https://www.w3.org/ns/activitystreams'); - if ( bodyParsed.type === 'Create' || bodyParsed.type === 'Announce' ) { - var to; - var cc; - if ( typeof bodyParsed.object === 'string' ) { - bodyParsed.object = await jsonld.compact(await http_agent.request(bodyParsed.object, {actor: `https://${req.hostname}/actor`}), 'https://www.w3.org/ns/activitystreams'); - } - var result = await db("inbox").where({origin: bodyParsed.object.id}); - if ( result[0] ) { + // So, inbox only cares about the request. So what do we care about on the request? + var take_at_face_value = false; + // 1. If it is signed... + if ( req.header("signature") ) { + // ... and said signature is valid... + var signature_split = req.header("signature").split(/,/); + var signature_elements = {}; + signature_split.forEach((obj) => { + signature_elements[obj.split(/=/)[0]] = obj.split(/=/)[1]; + }); + var headers_to_check = signature_elements["headers"].split(/ /); + if ( headers_to_check.includes("host") && headers_to_check.includes("date") && headers_to_check.includes("digest") && headers_to_check.includes("(request-target)")) { + // ... and checks all the correct headers... + var digest = crypto.createHash("sha256").update(req.body).end().digest("hex"); + if ( digest === req.header("digest") ) { + // ... and the body is un-tampered... + var signed_block = headers_to_check.map((header) => { + if ( header === "(request-target)" ) { + return `(request-target): ${req.method.toLowerCase()} ${req.originalUrl}`; + } else { + return `${header}: ${req.header(header)}`; + } + }).join('\n'); + // ... then lets dump this in the queue for now. + await db("queue").insert({task: "verify_inbox", data: JSON.stringify({signed_block, body: req.body, date: (new Date()).toISOString()})}); res.status(204); return res.end(); } - if ( typeof bodyParsed.object.to === 'string' ) { - to = [bodyParsed.object.to]; - } else { - to = bodyParsed.object.to; - } - if ( typeof bodyParsed.object.cc === 'string' ) { - to = [bodyParsed.object.cc]; - } else { - cc = bodyParsed.object.cc; - } - await db('inbox').insert({ - origin: bodyParsed.object.id, - to: JSON.stringify(to), - cc: JSON.stringify(cc), - object: JSON.stringify( - await jsonld.compact( - await jsonld.expand(bodyParsed)[0]['https://www.w3.org/ns/activitystreams#object'], 'https://www.w3.org/ns/activitystreams' - ) - ) - }); - res.status(204); - return res.end(); - } else { - res.status(422); - res.body('Unimplemented'); - return res.end(); } } + if ( !take_at_face_value ) { + // 2. If the signature is not valid, or non-existant, then we fetch the resource manually. + var bodyParsed = await jsonld.compact(req.body, 'https://www.w3.org/ns/activitystreams'); + await db("queue").insert({task: "fetch_object", data: JSON.stringify({id: bodyParsed.id})}); + res.status(204); + return res.end(); + } }); } };