import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
import { getActor } from "../federate";

export = async (req: Request, res: Response, next: NextFunction) => {
	if (req.method !== "POST") {
		next();
		return;
	}
	const actor = await getActor(req.body.actor as string);
	if (actor && actor.publicKey && validate(req, actor.publicKey.publicKeyPem)) {
		next();
	} else {
		// if the first check fails, force re-fetch the actor and try again
		const actor = await getActor(req.body.actor as string, true);
		if (!actor) {
			// probably caused by Delete activity for an actor
			if (req.body.type === "Delete") {
				// if we don't have a cached copy of the key, we have had no interaction with actor
				// so the Delete can be safely ignored
				// we still send a 200 OK status, so that the originating instances knows it has successfully
				// delivered the Delete (we just can't act on it)
				res.status(200).end();
			} else {
				console.log(`Could not retrieve actor ${req.body.actor} to validate HTTP signature for`, req.body);
				res.status(401).end("Could not retrieve actor to validate HTTP signature");
			}
		} else if (!actor.publicKey || !actor.publicKey.publicKeyPem || !validate(req, actor.publicKey.publicKeyPem)) {
			console.log(`Could not validate HTTP signature for ${req.body.actor}`);
			res.status(401).end("Could not validate HTTP signature");
		} else {
			next();
		}
	}
};

function validate(req: Request, publicKeyPem: string): boolean {
	const signature = parseSignature(req.header("signature")!);
	const usedHeaders = signature.get("headers")!.split(/\s/);
	const signingString = usedHeaders.map(header => {
		const value = header === "(request-target)" ? `${req.method} ${req.path}`.toLowerCase() : req.header(header);
		return `${header}: ${value}`;
	}).join("\n");
	const verifier = crypto.createVerify("sha256");
	verifier.update(signingString);
	verifier.end();
	return verifier.verify(publicKeyPem, signature.get("signature")!, "base64");
}

function parseSignature(signature: string): Map<string, string> {
	const map = new Map<string, string>();
	map.set("headers", "date");
	for (const part of signature.split(",")) {
		const index = part.indexOf("=");
		const key = part.substring(0, index);
		const value = part.substring(index + 1);
		const unquoted = value.replace(/^"+|"+$/g, ""); // strip leading and trailing quotes
		map.set(key, unquoted);
	}
	return map;
}
