// node --test examples/node-hono/test.mjs (needs `npm install` here, `cargo wasm`, `cargo host`; skips without hono) import assert from "node:assert/strict"; import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { test } from "node:test"; import { atlibs, scopeFixture } from "../../rust/host/conformance.mjs"; import { contextOptions, createNodeContext } from "../../javascript/src/server/index.js"; const hono = await import("hono").catch(() => null); test("a Hono app mounts clientMetadataHandler and runs a staged module", { skip: !hono && "npm install in examples/node-hono" }, async () => { const { createApp } = await import("./server.mjs"); const dir = await mkdtemp(join(tmpdir(), "atlibs-hono-")); await atlibs(["stage", "--target", "server", "--out", dir, "compose", "vibescrobble"]); const ctx = createNodeContext(contextOptions(join(dir, "state"))); const app = await createApp(join(dir, "stage.json"), { ctx, call: async () => ({}), origin: "https://site.example" }); try { const doc = await (await app.fetch(new Request("https://site.example/oauth/vibescrobble/client-metadata.json"))).json(); assert.equal(doc.client_id, "https://site.example/oauth/vibescrobble/client-metadata.json"); assert.equal((await app.fetch(new Request("https://site.example/oauth/nope/client-metadata.json"))).status, 404); const body = JSON.stringify({ call: { input: { n: 4 } } }); const run = (url, headers = {}) => app.fetch(new Request(url, { method: "POST", body, headers: { "content-type": "application/json", ...headers } })); const ran = await (await run("https://site.example/run/compose/even", { origin: "https://site.example" })).json(); assert.deepEqual(ran.result, { allow: true, violations: [] }); assert.equal((await run("http://127.0.0.1:8788/run/compose/even")).status, 200, "loopback, no Origin (curl)"); // review 3c H1: a cross-site page (no-cors text/plain, or another Origin) and DNS rebinding (Host) are refused. assert.equal((await run("https://site.example/run/compose/even", { "content-type": "text/plain" })).status, 415); assert.equal((await run("https://site.example/run/compose/even", { origin: "https://evil.example" })).status, 403); assert.equal((await run("http://127.0.0.1:8788/run/compose/even", { origin: "https://evil.example" })).status, 403); assert.equal((await run("http://evil.example:8788/run/compose/even")).status, 403); assert.equal((await run("https://site.example/run/compose/even", { "content-type": "application/json; charset=utf-8" })).status, 200); assert.equal((await app.fetch(new Request("https://site.example/.well-known/did.json"))).status, 404, "no session record declares app"); } finally { ctx.close(); } }); test("a session record that declares app: the server serves the app's did:web and its read-only repo on its origin", { skip: !hono && "npm install in examples/node-hono" }, async () => { const { createApp } = await import("./server.mjs"); const { readFile, writeFile } = await import("node:fs/promises"); const dir = await mkdtemp(join(tmpdir(), "atlibs-hono-app-")); await atlibs(["stage", "--target", "server", "--out", dir, "vibescrobble"]); const stage = JSON.parse(await readFile(join(dir, "stage.json"), "utf8")); const record = stage.sessions.vibescrobble.record; const template = new URL(record.clientMetadata.client_id).origin; record.app = { records: [{ collection: "dev.cocore.app.registration", rkey: "self", value: { $type: "dev.cocore.app.registration", name: "vibes", returnUrls: [`${template}/done`], createdAt: "2026-11-10T00:00:00.000Z" } }], wellKnown: { "cocore-app.json": { did: `did:web:${new URL(template).host}` } } }; await writeFile(join(dir, "stage.json"), JSON.stringify(stage)); const ctx = createNodeContext(contextOptions(join(dir, "state"))); try { const app = await createApp(join(dir, "stage.json"), { ctx, call: async () => ({}), origin: "https://site.example" }); const get = async (path) => (await app.fetch(new Request(`https://site.example${path}`))).json(); assert.equal((await get("/.well-known/did.json")).id, "did:web:site.example"); assert.deepEqual(await get("/.well-known/cocore-app.json"), { did: "did:web:site.example" }); assert.deepEqual((await get("/xrpc/com.atproto.repo.getRecord?repo=did:web:site.example&collection=dev.cocore.app.registration&rkey=self")).value.returnUrls, ["https://site.example/done"]); const again = await createApp(join(dir, "stage.json"), { ctx, call: async () => ({}), origin: "https://site.example" }); assert.deepEqual((await (await again.fetch(new Request("https://site.example/.well-known/did.json"))).json()).verificationMethod, (await get("/.well-known/did.json")).verificationMethod, "the key is kept in the state dir"); } finally { ctx.close(); } }); const oauthInstalled = await import("../../javascript/src/oauth-server/index.js").then(() => true, () => false); test("a Hono app signs users in (javascript/src/oauth-server) and runs each request as its own user", { skip: (!hono && "npm install in examples/node-hono") || (!oauthInstalled && "npm install in javascript/src/oauth-server") }, async () => { const { createApp } = await import("./server.mjs"); const { oauthServer } = await import("../../javascript/src/oauth-server/index.js"); const { browser, fakeAtproto } = await import("../../javascript/src/oauth-server/fake.mjs"); const ORIGIN = "https://site.example"; const dir = await mkdtemp(join(tmpdir(), "atlibs-hono-oauth-")); await atlibs(["stage", "--target", "server", "--out", dir, "statusphere"]); let app; const fake = await fakeAtproto({ fetchClient: (url) => app.fetch(new Request(url)) }); const ctx = createNodeContext(contextOptions(join(dir, "state"))); try { const oauth = await oauthServer(ctx, { origin: ORIGIN, scope: "atproto transition:generic", fetch: (i, n) => fetch(i, n), handleResolver: fake.base, plcDirectoryUrl: fake.base, allowHttp: true }); app = await createApp(join(dir, "stage.json"), { ctx, call: async () => Promise.reject(new Error("no operator account")), origin: ORIGIN, oauth }); const handles = {}; for (const user of ["alice", "bob"]) { const b = browser((url) => (url.startsWith(ORIGIN) ? (req) => app.fetch(req) : fetch)); await b.go(`${ORIGIN}/oauth/login?handle=${user}.test`); handles[user] = b.cookie(ORIGIN, "atlibs_session"); assert.ok(handles[user], `${user} signed in`); } const run = (handle) => app.fetch(new Request(`${ORIGIN}/run/statusphere/set_status`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${handle}` }, body: JSON.stringify({ status: "🙂", now: new Date().toISOString() }) })).then((r) => r.json()); const ran = await Promise.all([handles.alice, handles.bob, handles.alice, handles.bob].map(run)); ran.forEach((r, i) => assert.ok(r.result?.uri?.startsWith(`at://${fake.did(i % 2 ? "bob" : "alice")}/`), JSON.stringify(r))); assert.ok(fake.writes.every((w) => w.sub === w.repo)); assert.equal((await app.fetch(new Request(`${ORIGIN}/run/statusphere/set_status`, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer forged" }, body: "{}" }))).status, 401); assert.equal((await app.fetch(new Request(`${ORIGIN}/oauth/logout`, { method: "POST", headers: { authorization: `Bearer ${handles.bob}`, origin: "https://evil.example" } }))).status, 403, "a cross-site sign-out"); // two modules under the same sign-in (granted transition:generic): each run is held to its own module's session (stage.json) const scopes = await scopeFixture(); await atlibs(["stage", "--target", "server", "--out", join(scopes, "staged"), join(scopes, "comp.json")]); const scoped = await createApp(join(scopes, "staged", "stage.json"), { ctx, call: async () => Promise.reject(new Error("no operator account")), origin: ORIGIN, oauth }); const create = (module, collection) => scoped.fetch(new Request(`${ORIGIN}/run/${module}/caller`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${handles.alice}` }, body: JSON.stringify({ nsid: "com.atproto.repo.createRecord", input: { repo: fake.did("alice"), collection, record: {} } }) })).then((r) => r.json()); fake.writes.length = 0; assert.equal((await create("a", "com.example.a")).result.q.ok, true); assert.equal((await create("a", "com.example.b")).result.q.error, "a may not call com.atproto.repo.createRecord with sessions sa"); assert.equal((await create("b", "com.example.a")).result.q.error, "b may not call com.atproto.repo.createRecord with sessions sb"); assert.deepEqual(fake.writes.map((w) => w.collection), ["com.example.a"]); } finally { ctx.close(); await fake.close(); } });