~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-fedproxy

fedproxy and ingress modules for atlibs: SSH public keys on your PDS, did:key ingress; pages and web tiles

0
0
main
141 lines · 6.9 KB Raw History
// node --test web/ingress/ingress-cubit.test.mjs  (after `cargo wasm`)
// The page cubit over the real ingress module, the real didkey and subscribeXrpc, with a fake relay
// behind the WebSocket class: registers, serves, gets dropped (1011), re-registers, serves again.
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { registerHooks } from "node:module";
import { test } from "node:test";

registerHooks({
	resolve(specifier, context, next) {
		const lib = specifier.startsWith("atlibs/") && new URL(`../../../atlibs/javascript/src/${specifier.slice("atlibs/".length)}`, import.meta.url).href;
		return next(lib || specifier, context);
	},
});
const { IngressCubit } = await import("./ingress-cubit.js");
const { load } = await import("../../../atlibs/rust/src/harness.mjs");

const WASM = new URL("../../target/wasm32-unknown-unknown/release/ingress.wasm", import.meta.url);
const { run } = await load(WASM);
const manifest = JSON.parse(await readFile(new URL("../../modules/ingress/module.json", import.meta.url), "utf8"));
const T = "com.fedproxy.temp.xrpc.subscribe";
const nonce = () => {
	const bytes = crypto.getRandomValues(new Uint8Array(64));
	bytes[0] = 0; // not a JWS character, so didkey.sign takes it
	return Buffer.from(bytes).toString("base64");
};

// The relay side of each subscribe socket. The first connection is dropped with 1011 after it
// answers one request; the second answers one request and the test stops the run.
function relayWorld() {
	const sockets = [];
	const responses = [];
	class FakeRelay {
		readyState = 0;
		constructor(url) {
			this.url = new URL(url);
			this.n = sockets.push(this);
			setTimeout(() => {
				this.readyState = 1;
				this.onopen?.();
				this.push({ $type: `${T}#registered`, subdomain: this.url.searchParams.get("did").replaceAll(":", "-").toLowerCase() });
				this.push({ $type: `${T}#request`, requestId: `hello-${this.n}`, method: "GET", path: "/xrpc/com.example.ingress.hello", params: { name: `socket${this.n}` }, headers: {} });
			}, 5);
		}
		push(frame) {
			this.onmessage?.({ data: JSON.stringify(frame) });
		}
		send(text) {
			const frame = JSON.parse(text);
			responses.push({ socket: this.n, ...frame });
			if (frame.requestId === "hello-1") setTimeout(() => this.close(1011, "subscriber unresponsive for 90s"), 5);
		}
		close(code = 1005, reason = "") {
			if (this.readyState > 1) return;
			this.readyState = 3;
			this.onclose?.({ code, reason });
		}
	}
	return { FakeRelay, sockets, responses };
}

test("the browser host keeps its did:key in secret settings and re-registers after the relay drops it", async () => {
	const settings = new Map();
	const fetched = [];
	const ctx = {
		settings: { get: async (k) => settings.get(k) ?? null, set: async (k, v) => void settings.set(k, v) },
		wasm: { run: async ({ entry, input }) => run(entry, input) },
		http: {
			fetch: async (url, init) => {
				fetched.push(url);
				assert.equal(url, "https://xrpc.fedproxy.com/xrpc/com.fedproxy.temp.xrpc.getRegistrationNonce");
				assert.match(init.headers.authorization, /^Bearer /);
				return new Response(JSON.stringify({ nonce: nonce() }));
			},
		},
	};
	const { FakeRelay, sockets, responses } = relayWorld();
	const module = Promise.resolve({ module: new Uint8Array(await readFile(WASM)), calls: manifest.calls, didkey: manifest.didkey });
	const id = "http://localhost/modules/ingress.wasm";
	const cubit = new IngressCubit({ ctx, module, id, WebSocket: FakeRelay });

	await cubit.init();
	assert.equal(cubit.state.status, "idle");
	assert.match(cubit.state.did, /^did:key:zDn/);
	assert.equal(cubit.state.url, `https://${cubit.state.did.replaceAll(":", "-").toLowerCase()}.xrpc.fedproxy.com/`);
	assert.ok(settings.get("didkeys")[id].d, "the private JWK is in the didkeys setting");
	const again = new IngressCubit({ ctx, module, id, WebSocket: FakeRelay });
	await again.init();
	assert.equal(again.state.did, cubit.state.did, "same identity, same did");

	const running = cubit.start({ lifetimeMs: "30000" });
	const until = async (check) => {
		for (let i = 0; i < 400 && !check(); i++) await new Promise((r) => setTimeout(r, 10));
		assert.ok(check());
	};
	await until(() => responses.some((r) => r.requestId === "hello-2"));
	cubit.stop();
	const ran = await running;

	assert.equal(ran.error, "aborted");
	assert.equal(cubit.state.status, "done");
	assert.deepEqual(responses.map((r) => [r.socket, r.requestId, r.status, r.body.message]), [
		[1, "hello-1", 200, "hello socket1"],
		[2, "hello-2", 200, "hello socket2"],
	]);
	assert.equal(sockets.length, 2);
	assert.equal(fetched.length, 2, "a fresh nonce per registration");
	assert.equal(new URL(sockets[1].url).searchParams.get("did"), cubit.state.did);
	assert.ok(sockets[1].url.searchParams.get("service_auth"), "service-auth rides in the query string");
	assert.equal(cubit.state.drops, 1);
	assert.ok(cubit.state.log.some((line) => /relay closed: .*1011/.test(line)), cubit.state.log.join("\n"));
	assert.ok(cubit.state.log.some((line) => /timer reconnect/.test(line)));
});

test("a module with no didkey services is refused before any key is made", async () => {
	const ctx = { settings: { get: async () => null, set: async () => {} }, wasm: { run: async ({ entry, input }) => run(entry, input) }, http: { fetch: async () => new Response("{}") } };
	const cubit = new IngressCubit({ ctx, module: Promise.resolve({ module: new Uint8Array(), calls: [], didkey: { services: [] } }), id: "x" });
	await cubit.init();
	assert.equal(cubit.state.status, "error");
	assert.match(cubit.state.error, /didkey services/);
});

// review 3a L6: a link can name any module record; &serve starts one only after the user confirms.
test("&serve starts a module named by at:// only when confirmed; the local module at once", async () => {
	const ctx = { settings: { get: async () => null, set: async () => {} } };
	const module = Promise.resolve({ module: new Uint8Array(), calls: [], didkey: { services: ["https://xrpc.fedproxy.com"] } });
	const asked = [];
	const confirm = (answer) => (message) => (asked.push(message), answer);
	const remote = new IngressCubit({ ctx, module, id: "at://did:plc:attacker/com.fedfork.exec.wasm.module/x" });
	assert.equal(await remote.mayAutoServe(confirm(false)), false);
	assert.equal(await remote.mayAutoServe(confirm(true)), true);
	assert.match(asked[0], /at:\/\/did:plc:attacker\/com\.fedfork\.exec\.wasm\.module\/x[\s\S]*https:\/\/xrpc\.fedproxy\.com/);
	asked.length = 0;
	assert.equal(await new IngressCubit({ ctx, module, id: "http://localhost/modules/ingress.wasm" }).mayAutoServe(confirm(false)), true);
	assert.deepEqual(asked, []);
	// ?module=<handle>/<name>: the loaded record's at:// URI is the identity, so it still asks
	const uri = "at://did:plc:attacker/com.fedfork.exec.wasm.module/x";
	const byHandle = new IngressCubit({ ctx, module: module.then((m) => ({ ...m, id: uri })), id: "attacker.example/x" });
	assert.equal(await byHandle.mayAutoServe(confirm(false)), false);
	assert.match(asked[0], /Serve at:\/\/did:plc:attacker\//);
});