From e425ea903cee4c18037b73b0dd9b546d4bcc2025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 16:12:42 -0400 Subject: [PATCH] Expose plugin signing keys API --- cloudflare/src/bindings.ts | 6 ++- cloudflare/src/index.ts | 57 ++++++++++++++++++++- cloudflare/tests/index.test.ts | 93 ++++++++++++++++++++++++++++++++++ cloudflare/tsconfig.test.json | 2 +- 4 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 cloudflare/tests/index.test.ts diff --git a/cloudflare/src/bindings.ts b/cloudflare/src/bindings.ts index ce3e656..34c692e 100644 --- a/cloudflare/src/bindings.ts +++ b/cloudflare/src/bindings.ts @@ -1,4 +1,8 @@ +export interface ElyKvNamespace { + get(key: string): Promise; +} + export interface Env { - ELY_KV: KVNamespace; + ELY_KV: ElyKvNamespace; ELY_ENVIRONMENT: string; } diff --git a/cloudflare/src/index.ts b/cloudflare/src/index.ts index 55cf6e4..249d3b5 100644 --- a/cloudflare/src/index.ts +++ b/cloudflare/src/index.ts @@ -1,7 +1,60 @@ import type { Env } from "./bindings.js"; +import { + SigningKeysSchemaError, + parsePublicSigningKeysDocument, + publicSigningKeysKvKey, +} from "./signing_keys.js"; export default { - fetch(_request: Request, _env: Env): Response { - return new Response("Not Found", { status: 404 }); + fetch(request: Request, env: Env): Promise { + return handleRequest(request, env); }, }; + +export async function handleRequest(request: Request, env: Env): Promise { + const url = new URL(request.url); + if (url.pathname === "/api/plugins/signing-keys") { + return handlePublicSigningKeys(request, env); + } + + return jsonResponse({ error: "not_found" }, 404); +} + +async function handlePublicSigningKeys(request: Request, env: Env): Promise { + if (request.method !== "GET") { + return jsonResponse({ error: "method_not_allowed" }, 405, { Allow: "GET" }); + } + + const kvKey = publicSigningKeysKvKey(env.ELY_ENVIRONMENT); + const value = await env.ELY_KV.get(kvKey); + if (value === null) { + return jsonResponse({ error: "public_signing_keys_unavailable" }, 503); + } + + try { + const document = parsePublicSigningKeysDocument(value); + return jsonResponse(document, 200, { + "Cache-Control": "public, max-age=300, stale-while-revalidate=60", + }); + } catch (error) { + if (error instanceof SigningKeysSchemaError) { + return jsonResponse({ error: "public_signing_keys_invalid" }, 500); + } + throw error; + } +} + +function jsonResponse( + body: unknown, + status: number, + headers: Record = {}, +): Response { + return new Response(JSON.stringify(body), { + status, + headers: { + "Content-Type": "application/json; charset=utf-8", + "X-Content-Type-Options": "nosniff", + ...headers, + }, + }); +} diff --git a/cloudflare/tests/index.test.ts b/cloudflare/tests/index.test.ts new file mode 100644 index 0000000..f8330a3 --- /dev/null +++ b/cloudflare/tests/index.test.ts @@ -0,0 +1,93 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import type { Env } from "../src/bindings.js"; +import { handleRequest } from "../src/index.js"; +import { publicSigningKeysKvKey } from "../src/signing_keys.js"; + +const PUBLIC_KEY = "a".repeat(64); + +describe("worker routes", () => { + it("returns public plugin signing keys from KV", async () => { + const env = testEnv( + JSON.stringify({ + version: 1, + keys: [{ key_id: "elydora-alpha-plugins", public_key: PUBLIC_KEY }], + }), + ); + + const response = await handleRequest( + new Request("https://elydora.test/api/plugins/signing-keys"), + env, + ); + + assert.equal(response.status, 200); + assert.equal(response.headers.get("content-type"), "application/json; charset=utf-8"); + assert.equal(response.headers.get("x-content-type-options"), "nosniff"); + assert.equal( + response.headers.get("cache-control"), + "public, max-age=300, stale-while-revalidate=60", + ); + assert.deepEqual(await response.json(), { + version: 1, + keys: [{ key_id: "elydora-alpha-plugins", public_key: PUBLIC_KEY }], + }); + }); + + it("rejects unsupported methods on public signing keys", async () => { + const response = await handleRequest( + new Request("https://elydora.test/api/plugins/signing-keys", { method: "POST" }), + testEnv("{}"), + ); + + assert.equal(response.status, 405); + assert.equal(response.headers.get("allow"), "GET"); + assert.deepEqual(await response.json(), { error: "method_not_allowed" }); + }); + + it("returns service unavailable when public signing keys are missing", async () => { + const response = await handleRequest( + new Request("https://elydora.test/api/plugins/signing-keys"), + testEnv(null), + ); + + assert.equal(response.status, 503); + assert.deepEqual(await response.json(), { error: "public_signing_keys_unavailable" }); + }); + + it("returns a generic server error for malformed public signing keys", async () => { + const response = await handleRequest( + new Request("https://elydora.test/api/plugins/signing-keys"), + testEnv("{"), + ); + + assert.equal(response.status, 500); + assert.deepEqual(await response.json(), { error: "public_signing_keys_invalid" }); + }); + + it("returns JSON not found for unknown routes", async () => { + const response = await handleRequest( + new Request("https://elydora.test/api/sync/status"), + testEnv(null), + ); + + assert.equal(response.status, 404); + assert.deepEqual(await response.json(), { error: "not_found" }); + }); +}); + +function testEnv(publicSigningKeys: string | null): Env { + const values = new Map(); + if (publicSigningKeys !== null) { + values.set(publicSigningKeysKvKey("local"), publicSigningKeys); + } + + return { + ELY_ENVIRONMENT: "local", + ELY_KV: { + get(key: string): Promise { + return Promise.resolve(values.get(key) ?? null); + }, + }, + }; +} diff --git a/cloudflare/tsconfig.test.json b/cloudflare/tsconfig.test.json index bedc87a..6d8e3ed 100644 --- a/cloudflare/tsconfig.test.json +++ b/cloudflare/tsconfig.test.json @@ -13,5 +13,5 @@ "verbatimModuleSyntax": true, "rootDir": "." }, - "include": ["src/signing_keys.ts", "tests/**/*.ts"] + "include": ["src/**/*.ts", "tests/**/*.ts"] }