From 401efee7cb54c0ac9d9b5fbe16d8962cbf05a345 Mon Sep 17 00:00:00 2001 From: Andrew Pietila Date: Tue, 28 Mar 2023 23:32:02 -0500 Subject: [PATCH] Add basic instance actor, along with rudimentary http signature key generation support. --- lib/keys.js | 49 ++++++++++++++++++++++++++++++ migrations/20230324060224_inbox.js | 2 ++ migrations/20230329035124_keys.js | 25 +++++++++++++++ routes/actor.js | 41 +++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 lib/keys.js create mode 100644 migrations/20230329035124_keys.js create mode 100644 routes/actor.js diff --git a/lib/keys.js b/lib/keys.js new file mode 100644 index 0000000..5edb6f5 --- /dev/null +++ b/lib/keys.js @@ -0,0 +1,49 @@ +'use strict'; + +const crypto = require('crypto'); +const db = require('./db'); + +var generateKeyPair = (type, options) => { + return new Promise((res, rej) => { + crypto.generateKeyPair(type, options, (err, pubkey, privkey) => { + if ( err != null ) { + rej(err); + } else { + res({publicKey: pubkey, privateKey: privkey}); + } + }); + }); +}; + +var getKeyPair = async (actor) => { + var result = await db('keys').where({actor}).andWhere('expiry', '>', (new Date()).getTime()/1000); + if ( result.length != 0 ) { + return { + publicKey: result[0].public, + privateKey: result[0].private + }; + } else { + var {publicKey, privateKey} = await generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + format: 'pem' + }, + privateKeyEncoding: { + format: 'pem' + } + }); + await db('keys').insert({ + expiry: (new Date().setDate(new Date().getDate()+7).getTime()/1000), + public: publicKey, + private: privateKey, + actor: actor + }); + return { + publicKey, privateKey + }; + } +}; + +module.exports = { + getKeyPair +}; diff --git a/migrations/20230324060224_inbox.js b/migrations/20230324060224_inbox.js index 48e1c5d..bc912cb 100644 --- a/migrations/20230324060224_inbox.js +++ b/migrations/20230324060224_inbox.js @@ -1,3 +1,5 @@ +/* eslint-disable jsdoc/valid-types */ +// eslint doesn't seem to understand, but vscode does. 'use strict'; /** diff --git a/migrations/20230329035124_keys.js b/migrations/20230329035124_keys.js new file mode 100644 index 0000000..0f63fdf --- /dev/null +++ b/migrations/20230329035124_keys.js @@ -0,0 +1,25 @@ +/* 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('keys', (table) => { + table.increments('id'); + table.integer('expiry'); + table.string('private'); + table.string('public'); + table.text('actor'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.dropTable('keys'); +}; diff --git a/routes/actor.js b/routes/actor.js new file mode 100644 index 0000000..7813bf3 --- /dev/null +++ b/routes/actor.js @@ -0,0 +1,41 @@ +'use strict'; + +const express = require('express'); +const { getKeyPair } = require('../lib/keys'); + +module.exports = { + /** + * @param {express.IRoute} routeObj + */ + route: (routeObj) => { + routeObj.get((req, res, _next) => { + res.setHeader('content-type', 'application/activity+json'); + res.json({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + security: 'https://w3id.org/security#' + } + ], + 'id': `https://${req.hostname}/actor`, + 'type': 'Application', + 'inbox': `https://${req.hostname}/actor/inbox`, + 'security:publicKey': { + 'id': `https://${req.hostname}/actor#main-key`, + 'security:owner': { + id: `https://${req.hostname}/actor` + }, + 'security:publicKeyPem': getKeyPair(`https://${req.hostname}/actor`) + }, + 'endpoints': { + sharedInbox: `https://${req.hostname}/inbox` + }, + 'as:manuallyApprovesFollowers': true, + 'outbox': `https://${req.hostname}/actor/outbox`, + 'preferredUsername': '${req.hostname}', + 'url': `https://${req.hostname}/about/more?instance_actor=true` + }); + return res.end(); + }); + } +};