import * as dotenv from "dotenv"; dotenv.config(); if (process.env.NODE_ENV != "development") { process.env.NODE_ENV = "production"; console.debug = () => {}; } if (process.env.DOMAIN_WHITELIST) var DOMAIN_WHITELIST = process.env.DOMAIN_WHITELIST.split(',').map(x=>x.trim().toLowerCase()).filter(x=>x); if (process.env.USER_WHITELIST) var USER_WHITELIST = process.env.USER_WHITELIST.split(',').map(x=>x.trim()).filter(x=>x); import express from "express"; import "express-async-errors"; import fetch from "node-fetch"; import Keyv from "keyv"; import {Sha256Signer, Parser} from "activitypub-http-signatures"; import * as crypto from "crypto"; import * as util from "util"; var generateKeyPair = util.promisify(crypto.generateKeyPair); var parser = new Parser(); var keystore = new Keyv("sqlite://keys.sqlite"); var app = express(); app.set("trust proxy", true); app.listen(process.env.PORT || 80, process.env.BIND_IP); app.use(express.text({ type: [ "application/jrd+json", "application/activity+json", "application/json", "text/*" ] })); app.use(async (req, res, next) => { console.debug({ ip: req.ip, method: req.method, url: req.url, headers: req.headers, body: req.body }); if (!["GET","POST"].includes(req.method)) return next(); if (!req.subdomains[0]) return next(); var TARGET_NODE = req.subdomains[0].replaceAll(/(? contentType?.toLowerCase().startsWith(t))) { var originalText = await target_res.text(), modifiedText = originalText; console.debug({originalText}) if (contentType.includes("json")) { var json = JSON.parse(originalText); if (json.preferredUsername && USER_WHITELIST && !USER_WHITELIST.includes(json.preferredUsername)) { console.debug(`user ${json.preferredUsername} blocked by whitelist`); //res.status(403).send(`${json.preferredUsername} is not whitelisted`); res.redirect(308, TARGET_URL); return; } if (json.publicKey) { console.debug("has key"); await keystore.set(json.publicKey.id, {publicKey: json.publicKey.publicKeyPem}); var masqueradeKeyId = json.publicKey.id.replaceAll(TARGET_REGEXP, TARGET_MASQUERADE); var masqueradeKeyPem = (await getLocalKeypair(masqueradeKeyId)).publicKey; json.publicKey.id = masqueradeKeyId; json.publicKey.publicKeyPem = masqueradeKeyPem; modifiedText = JSON.stringify(json); } } modifiedText = modifiedText.replaceAll(TARGET_REGEXP, TARGET_MASQUERADE); console.debug({modifiedText}); } else console.debug("passthrough"); if (!target_res.ok && !modifiedText && contentType.startsWith("text/")) console.debug("response:", await target_res.text()); res.status(target_res.status); res.header("Content-Type", contentType); if (modifiedText) res.send(modifiedText); else target_res.body.pipe(res); }); async function getLocalKeypair(id) { var keys = await keystore.get(id); if (keys) return keys; console.debug("making new masquerade key"); keys = await generateKeyPair('rsa', { publicKeyEncoding: {type:'pkcs1', format: 'pem'}, privateKeyEncoding: {type:'pkcs1', format: 'pem'}, modulusLength: 2048 }); await keystore.set(id, keys); return keys; } async function getRemotePubkey(id) { var publicKey = (await keystore.get(id))?.publicKey; if (publicKey) return publicKey; console.debug("fetching public key", id); var res = await fetch(id, {headers: {Accept: "application/activity+json"}}); console.debug(res.status, res.statusText, res.headers.get("content-type")); if (!res.ok || !res.headers.get("content-type").includes("json")) { console.debug("could not get key"); return false; }; var json = await res.json(); console.debug(json); var publicKey = json?.publicKey?.publicKeyPem; if (publicKey) keystore.set(id, {publicKey}); return publicKey; }