snailtrix/matrix.js

173 lines
4.8 KiB
JavaScript
Raw Permalink Normal View History

2025-10-18 18:31:10 +02:00
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"));