mirror of
				https://codeberg.org/doesnm/snailtrix.git
				synced 2025-10-18 19:00:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			172 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const express = require("express");
 | 
						|
const ed = require("@noble/ed25519");
 | 
						|
const { sha512 } = require("@noble/hashes/sha2.js");
 | 
						|
ed.hashes.sha512 = sha512;
 | 
						|
const config = require("./config.json");
 | 
						|
const fs = require("node:fs");
 | 
						|
const { parse: parseUrl } = require("node:url");
 | 
						|
const dns = require("node:dns");
 | 
						|
const fetch = require("sync-fetch");
 | 
						|
const app = express();
 | 
						|
const deasync = require("deasync");
 | 
						|
const keysToBase64 = (keys) => {
 | 
						|
	return {
 | 
						|
		secretKey: new Buffer(keys.secretKey)
 | 
						|
			.toString("base64")
 | 
						|
			.replace("+", "-")
 | 
						|
			.replace("/", "_")
 | 
						|
			.replace(/=+$/, ""),
 | 
						|
		publicKey: new Buffer(keys.publicKey)
 | 
						|
			.toString("base64")
 | 
						|
			.replace("+", "-")
 | 
						|
			.replace("/", "_")
 | 
						|
			.replace(/=+$/, ""),
 | 
						|
	};
 | 
						|
};
 | 
						|
const base64ToKeys = (keys) => {
 | 
						|
	const fixBase64 = (str) =>
 | 
						|
		str
 | 
						|
			.replace("-", "+")
 | 
						|
			.replace("_", "/")
 | 
						|
			.padEnd(str.length + ((4 - (str.length % 4)) % 4), "=");
 | 
						|
	return {
 | 
						|
		secretKey: new Uint8Array(Buffer.from(fixBase64(keys.secretKey), "base64")),
 | 
						|
		publicKey: new Uint8Array(Buffer.from(fixBase64(keys.publicKey), "base64")),
 | 
						|
	};
 | 
						|
};
 | 
						|
 | 
						|
if (!config.secretKey) {
 | 
						|
	const keys = ed.keygen();
 | 
						|
	config.secretKey = keys.secretKey;
 | 
						|
	config.publicKey = keys.publicKey;
 | 
						|
	fs.writeFileSync(
 | 
						|
		"config.json",
 | 
						|
		JSON.stringify({ ...config, ...keysToBase64(config) }),
 | 
						|
	);
 | 
						|
}
 | 
						|
const delegation = (name) => {
 | 
						|
	const res = fetch(`https://${name}/.well-known/matrix/server`);
 | 
						|
	if (res.status === 200) {
 | 
						|
		const json = res.json();
 | 
						|
		if (!json["m.server"].includes(":")) {
 | 
						|
			return delegation(json["m.server"]);
 | 
						|
		}
 | 
						|
		return { domain: json["m.server"], host: json["m.server"] };
 | 
						|
	} else {
 | 
						|
		const resolve = deasync(dns.resolveSrv);
 | 
						|
		try {
 | 
						|
			const res = resolve(`_matrix-fed._tcp.${name}`)[0];
 | 
						|
			return {
 | 
						|
				domain: `${res.name}:${res.port}`,
 | 
						|
				host: name || res.name,
 | 
						|
			};
 | 
						|
		} catch (_e) {
 | 
						|
			try {
 | 
						|
				const res = resolve(`_matrix._tcp.${name}`)[0];
 | 
						|
				return { domain: `${res.name}:${res.port}`, host: name || res.name };
 | 
						|
			} catch (_e) {
 | 
						|
				return {
 | 
						|
					domain: `${name}:8448`,
 | 
						|
					host: name || res.name,
 | 
						|
				};
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
};
 | 
						|
const makeSignedRequest = (origin, method, url) => {
 | 
						|
	const parsed = parseUrl(url);
 | 
						|
	let payload = {
 | 
						|
		method: method,
 | 
						|
		uri: parsed.path,
 | 
						|
		destination: parsed.hostname,
 | 
						|
		origin: origin,
 | 
						|
	};
 | 
						|
	payload = Object.fromEntries(
 | 
						|
		Object.entries(payload).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)),
 | 
						|
	);
 | 
						|
	const delegate = delegation(parsed.hostname);
 | 
						|
	const keys = base64ToKeys(config);
 | 
						|
	const signature = ed.sign(
 | 
						|
		new TextEncoder().encode(JSON.stringify(payload)),
 | 
						|
		keys.secretKey,
 | 
						|
	);
 | 
						|
	console.log(
 | 
						|
		`X-Matrix origin="${origin}",destination="${parsed.hostname}",key="ed25519:${config.publicKey.slice(0, 5)}",sig="${new Buffer(signature).toString("base64").replace("+", "-").replace("/", "_").replace(/=+$/, "")}"`,
 | 
						|
	);
 | 
						|
	return fetch(
 | 
						|
		`https://${delegate.domain}${parsed.path}`,
 | 
						|
		{
 | 
						|
			headers: {
 | 
						|
				Host: delegate.host,
 | 
						|
				Authorization: `X-Matrix origin="${origin}",destination="${parsed.hostname}",key="ed25519:${config.publicKey.slice(0, 5)}",sig="${new Buffer(signature).toString("base64").replace("+", "-").replace("/", "_").replace(/=+$/, "")}"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	);
 | 
						|
};
 | 
						|
 | 
						|
app.get("/.well-known/matrix/server", (req, res) => {
 | 
						|
	return res.json({
 | 
						|
		"m.server": req.host.includes(":") ? req.host : `${req.host}:443`,
 | 
						|
	});
 | 
						|
});
 | 
						|
app.get("/_matrix/federation/v1/version", (_req, res) => {
 | 
						|
	res.json({
 | 
						|
		server: {
 | 
						|
			name: "idk",
 | 
						|
			version: "0.0.0.0.0.0.0.1",
 | 
						|
		},
 | 
						|
	});
 | 
						|
});
 | 
						|
app.get("/_matrix/key/v2/server", (req, res) => {
 | 
						|
	const payload = {
 | 
						|
		old_verify_keys: {},
 | 
						|
		server_name: req.host.replace(":443", ""),
 | 
						|
		valid_until_ts: Number.MAX_SAFE_INTEGER,
 | 
						|
		verify_keys: {},
 | 
						|
	};
 | 
						|
	payload.verify_keys[`ed25519:${config.publicKey.slice(0, 5)}`] = {
 | 
						|
		key: config.publicKey,
 | 
						|
	};
 | 
						|
	const keys = base64ToKeys(config);
 | 
						|
	const signature = ed.sign(
 | 
						|
		new TextEncoder().encode(
 | 
						|
			JSON.stringify(
 | 
						|
				Object.fromEntries(
 | 
						|
					Object.entries(payload).sort(([keyA], [keyB]) =>
 | 
						|
						keyA.localeCompare(keyB),
 | 
						|
					),
 | 
						|
				),
 | 
						|
			),
 | 
						|
		),
 | 
						|
		keys.secretKey,
 | 
						|
	);
 | 
						|
	payload.signatures = {};
 | 
						|
	payload.signatures[req.host.replace(":443", "")] = {};
 | 
						|
	payload.signatures[req.host.replace(":443", "")][
 | 
						|
		`ed25519:${config.publicKey.slice(0, 5)}`
 | 
						|
	] = new Buffer(signature)
 | 
						|
		.toString("base64")
 | 
						|
		.replace("+", "-")
 | 
						|
		.replace("/", "_")
 | 
						|
		.replace(/=+$/, "");
 | 
						|
	res.json(payload);
 | 
						|
});
 | 
						|
app.get("/_test/version", (req, res) => {
 | 
						|
	const result = delegation(req.query.domain);
 | 
						|
	console.log(result);
 | 
						|
	res.json(
 | 
						|
		fetch(`https://${result.domain}/_matrix/federation/v1/version`, {
 | 
						|
			headers: { Host: result.host },
 | 
						|
		}).json(),
 | 
						|
	);
 | 
						|
});
 | 
						|
app.get("/_test/alias", (req, res) => {
 | 
						|
	res.json(
 | 
						|
		makeSignedRequest(
 | 
						|
			req.host.replace(":443", ""),
 | 
						|
			"GET",
 | 
						|
			"https://yjsryd6gyhgl5eyikakf2xe5o4.srv.us/_matrix/federation/v1/query/directory?room_alias=%23ok%3Aorehus.club",
 | 
						|
		).json(),
 | 
						|
	);
 | 
						|
});
 | 
						|
app.listen(3000, () => console.log("Listening at 3000"));
 |