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

johnandersen777.bsky.social/atlibs

0
0
main
169 lines · 6.6 KB Raw History

Node servers

javascript/src/server runs modules in Node 25 or later with built-ins only (node:sqlite, node:crypto, node:worker_threads, fetch). Everything is fetch-style (Request in, Response out), so any framework mounts it; examples/node-hono uses Hono.

Run a module

createNodeContext gives a state dir (sealed secrets, the records ledger) and a wasm runner. hostCall({ ctx }) with no session makes public reads only.

import { localModule, runModule } from "./javascript/src/run-module/index.js";
import { createNodeContext, fileFetch, hostCall } from "./javascript/src/server/index.js";

const ctx = createNodeContext({ id: "my-server", stateDir: ".state/my-server" });
const root = `file://${process.cwd()}/`;
const module = await localModule(new URL("target/wasm32-unknown-unknown/release/compose.wasm", root), new URL("examples/rust/compose/module.json", root), fileFetch);
const ran = await runModule(ctx, module, "lookup", { n: 21 }, hostCall({ ctx }));
console.log(JSON.stringify(ran));
{"entry":"lookup","result":{"ack":null,"caller":null,"doubled":42},"events":1,"calls":[]}

A published module: loadModule(ctx, "bobvmbuilder.bsky.social/hello") (or <did>/<name>, an at:// URI; @<pin> checks it).

From the command line

The Node CLI runs one module, from a record or a local build, and prints the same JSON as atlibs run.

node javascript/src/server/cli.mjs run target/wasm32-unknown-unknown/release/compose.wasm examples/rust/compose/module.json lookup '{"n":2}'
		"doubled": 4

It signs in only with an app password (--app-password, Server sessions); for a user's OAuth session use atlibs login and atlibs run --as.

A Hono server

examples/node-hono/server.mjs serves staged modules: each session's OAuth client metadata, user sign-in, and POST /run/<module>/<entry>. Install its two dependencies once (and those of the optional sign-in, javascript/src/oauth-server):

(cd examples/node-hono && npm install)
(cd javascript/src/oauth-server && npm install)

Stage the modules, start it, run one:

atlibs stage --target server --out staged examples/rust/compose examples/rust/statusphere > /dev/null
ATLIBS_STAGE=staged/stage.json node examples/node-hono/server.mjs &
curl -s -X POST localhost:8788/run/compose/lookup -H 'content-type: application/json' -d '{"n":2}'; echo
curl -s localhost:8788/oauth/statusphere/client-metadata.json | jq -r .client_id
{"entry":"lookup","result":{"ack":null,"caller":null,"doubled":4},"events":1,"calls":[]}
http://localhost:8788/oauth/statusphere/client-metadata.json

PORT, ATLIBS_STAGE, ATLIBS_SERVER_STATE and ATLIBS_ORIGIN (the public origin) configure it. It listens on 127.0.0.1; put a reverse proxy or tunnel in front (Server sessions).

Mount the client metadata

clientMetadataHandler(stage, { origin }) answers GET <path><session>/client-metadata.json and returns undefined for anything else, so your routes still run.

examples/node-hono/server.mjs

	const metadata = clientMetadataHandler(stage, { origin });
	const app = new Hono();
…
	app.all("/oauth/*", async (c) => {
		const doc = c.req.method === "GET" && metadata(c.req.raw);
		if (doc) return doc;

With ATLIBS_ORIGIN and a session record that declares app, it also serves the app's own identity and read-only repo first (appRepoHandler, App identity).

Run a module per request

Each request runs as its signed-in user (or the server's own account, or signed out), held to the module's own session scope by sessionCall.

examples/node-hono/server.mjs

		// who this run acts as: the request's user with their own session, else the server's default
		let session;
		try {
			session = await oauth?.sessionOf(c.req.raw);
		} catch (error) {
			return c.json({ error: "AuthRequired", message: error.message }, error.status ?? 401);
		}
		const wasm = await realpath(join(dir, staged.wasm));
		// id: the wasm's path, as cli.mjs gives a local module (the same ledger owner in a shared state dir)
		const module = { ...(await localModule(pathToFileURL(wasm), pathToFileURL(join(dir, staged.spec)), fileFetch)), id: wasm };
		const runDid = session?.did ?? did;
		// signed in (a user, or the operator account): only what the module's own session allows (stage.json is the policy)
		const runCall = runDid ? sessionCall(stage, c.req.param("module"), session ? hostCall({ ctx, xrpc: session.xrpc, pds: session.pds }) : call) : call;
		return c.json(await runModule(ctx, module, c.req.param("entry"), { ...(runDid && { did: runDid }), ...input }, runCall, { provides: [] }));

Refuse cross-site runs

runRefusal is the same guard atlibs serve applies: a Host that is loopback or the configured origin's, no foreign Origin, and content-type: application/json (which a cross-site form cannot send without a preflight).

curl -s -X POST localhost:8788/run/compose/lookup -H 'Origin: https://evil.example' -H 'content-type: application/json' -d '{"n":2}'; echo
{"error":"Forbidden","message":"Origin https://evil.example may not run modules here"}

Accept calls from modules: service auth

A module calling your XRPC service with service: "https://you.example" sends a service-auth JWT. verifyServiceAuth returns the caller's DID or throws (answer 401); it checks the audience, method, expiry, the issuer's signing key, and replays.

import { createDidKey } from "./javascript/src/didkey/index.js";
import { verifyServiceAuth } from "./javascript/src/serviceauth/index.js";

// a caller: a did:key mints a token for one method at one audience
const caller = await createDidKey();
const token = await caller.serviceAuth({ aud: "did:web:you.example", lxm: "com.example.hello" });

// your route; resolve is only here to skip the network for a did:key
const resolve = async () => ({ verificationMethod: [{ id: "#atproto", publicKeyMultibase: caller.did.slice("did:key:".length) }] });
const who = await verifyServiceAuth({ audiences: ["did:web:you.example"] }, `Bearer ${token}`, "com.example.hello", { resolve });
console.log(who === caller.did);
await verifyServiceAuth({ audiences: ["did:web:you.example"] }, `Bearer ${token}`, "com.example.hello", { resolve }).catch((e) => console.log(e.message));
true
the token was already used

Test it

node --test examples/node-hono/test.mjs 2>&1 | grep -E '^ℹ (pass|fail)'
ℹ pass …
ℹ fail 0

Previous: Sign in from a page | Next: Rust servers