import assert from 'node:assert/strict' import http from 'node:http' import { after, before, test } from 'node:test' import { createLichenNoteProxyHandler } from '../../src/server/lichen-note-proxy.mjs' const noteUri = 'at://did:plc:abcdefghijklmnopqrstuvwx/wiki.lichen.note/3m4abc1234567' let upstream, upstreamUrl before(async () => { upstream = http.createServer((req, res) => { assert.equal(req.url, `/xrpc/wiki.lichen.getNote?uri=${encodeURIComponent(noteUri)}`) assert.equal(req.headers.authorization, undefined) assert.equal(req.headers.cookie, undefined) res.writeHead(400, { 'content-type': 'application/json' }) res.end('{"error":"NotFound","message":"Note not found"}') }) await new Promise(resolve => upstream.listen(0, '127.0.0.1', resolve)) upstreamUrl = `http://127.0.0.1:${upstream.address().port}` }) after(() => new Promise(resolve => upstream.close(resolve))) test('requires HTTPS except for explicitly enabled loopback tests', () => { assert.throws(() => createLichenNoteProxyHandler({ appviewUrl: 'http://example.com' }), /HTTPS/) assert.throws(() => createLichenNoteProxyHandler({ appviewUrl: 'http://127.0.0.1:1234' }), /HTTPS/) assert.throws(() => createLichenNoteProxyHandler({ appviewUrl: 'https://user:pass@example.com' }), /HTTPS/) assert.throws(() => createLichenNoteProxyHandler({ appviewUrl: 'http://192.168.1.1', allowLoopbackTest: true }), /HTTPS/) }) test('proxies only validated wiki-list DIDs and never forwards browser credentials', async t => { let calls = 0 const handler = createLichenNoteProxyHandler({ fetch: async (url, init) => { calls++ assert.equal(url.href, 'https://lichen.wiki/xrpc/wiki.lichen.listWikis?did=did%3Aplc%3Aalice') assert.equal(init.headers.authorization, undefined) assert.equal(init.headers.cookie, undefined) return new Response('{"wikis":[]}', { headers: { 'content-type': 'application/json' } }) } }) const server = http.createServer(async (req, res) => { if (!await handler(req, res)) res.writeHead(404).end() }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) t.after(() => new Promise(resolve => server.close(resolve))) const base = `http://127.0.0.1:${server.address().port}/api/lichen/wikis` const response = await fetch(`${base}?did=did:plc:alice`, { headers: { cookie: 'session=private', authorization: 'Bearer private' } }) assert.equal(response.status, 200) assert.deepEqual(await response.json(), { wikis: [] }) for (const query of ['', '?did=https://example.com', '?did=did:plc:alice&url=https://example.com']) { assert.equal((await fetch(base + query)).status, 400) } assert.equal(calls, 1) }) test('proxies a validated note URI, preserving upstream 400 NotFound and body', async t => { const handler = createLichenNoteProxyHandler({ appviewUrl: upstreamUrl, allowLoopbackTest: true }) const server = http.createServer(async (req, res) => { if (!await handler(req, res)) res.writeHead(404).end() }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) t.after(() => new Promise(resolve => server.close(resolve))) const response = await fetch(`http://127.0.0.1:${server.address().port}/api/lichen/note?uri=${encodeURIComponent(noteUri)}`) assert.equal(response.status, 400) assert.equal(response.headers.get('cache-control'), 'no-store') assert.equal(await response.text(), '{"error":"NotFound","message":"Note not found"}') }) test('rejects arbitrary or malformed note targets without contacting upstream', async t => { const handler = createLichenNoteProxyHandler({ appviewUrl: upstreamUrl, allowLoopbackTest: true }) const server = http.createServer(async (req, res) => { if (!await handler(req, res)) res.writeHead(404).end() }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) t.after(() => new Promise(resolve => server.close(resolve))) const base = `http://127.0.0.1:${server.address().port}/api/lichen/note?uri=` for (const uri of ['https://example.com/x', 'at://did:plc:abc/app.bsky.feed.post/x', 'at://did:plc:abc/wiki.lichen.note/a?x=1']) { const response = await fetch(base + encodeURIComponent(uri)) assert.equal(response.status, 400) } })