Add Cloudflare signing key contract

This commit is contained in:
2026-05-07 23:58:36 -04:00
parent 90c5110d6c
commit 1013a22915
12 changed files with 1854 additions and 1 deletions
+26
View File
@@ -43,3 +43,29 @@ jobs:
- name: Audit source file size - name: Audit source file size
run: scripts/audit_source_lines.sh run: scripts/audit_source_lines.sh
cloudflare:
name: Cloudflare worker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node
uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: cloudflare/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: cloudflare
- name: Check worker types
run: npm run check
working-directory: cloudflare
- name: Test worker contracts
run: npm test
working-directory: cloudflare
+2
View File
@@ -1,4 +1,6 @@
/target/ /target/
/cloudflare/dist/
/cloudflare/node_modules/
/references/ /references/
.agents/ .agents/
.claude/ .claude/
+1593
View File
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@elydora/cloudflare-worker",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"check": "tsc --noEmit -p tsconfig.json",
"test": "tsc -p tsconfig.test.json --outDir dist && node --test dist/tests/*.test.js"
},
"devDependencies": {
"@cloudflare/workers-types": "4.20260508.1",
"@types/node": "24.12.3",
"typescript": "6.0.3",
"wrangler": "4.90.0"
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface Env {
ELY_KV: KVNamespace;
ELY_ENVIRONMENT: string;
}
+7
View File
@@ -0,0 +1,7 @@
import type { Env } from "./bindings.js";
export default {
fetch(_request: Request, _env: Env): Response {
return new Response("Not Found", { status: 404 });
},
};
+106
View File
@@ -0,0 +1,106 @@
const KEY_ID_PATTERN = /^[a-z0-9._-]{3,128}$/;
const PUBLIC_KEY_PATTERN = /^[a-f0-9]{64}$/;
const KV_NAMESPACE_PREFIX = "ely";
const PUBLIC_SIGNING_KEYS_NAMESPACE = "public_signing_keys";
export interface PublicSigningKey {
key_id: string;
public_key: string;
}
export interface PublicSigningKeysDocument {
version: 1;
keys: PublicSigningKey[];
}
export class SigningKeysSchemaError extends Error {
constructor(message: string) {
super(message);
this.name = "SigningKeysSchemaError";
}
}
export function publicSigningKeysKvKey(environment: string): string {
const normalizedEnvironment = normalizedEnvironmentName(environment);
return `${KV_NAMESPACE_PREFIX}:${normalizedEnvironment}:${PUBLIC_SIGNING_KEYS_NAMESPACE}`;
}
export function parsePublicSigningKeysDocument(value: string): PublicSigningKeysDocument {
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch {
throw new SigningKeysSchemaError("public signing keys document must be valid JSON");
}
if (!isRecord(parsed)) {
throw new SigningKeysSchemaError("public signing keys document must be an object");
}
if (parsed.version !== 1) {
throw new SigningKeysSchemaError("public signing keys document version must be 1");
}
if (!Array.isArray(parsed.keys) || parsed.keys.length === 0) {
throw new SigningKeysSchemaError("public signing keys document must contain keys");
}
return {
version: 1,
keys: parsePublicSigningKeys(parsed.keys),
};
}
function parsePublicSigningKeys(values: unknown[]): PublicSigningKey[] {
const keys: PublicSigningKey[] = [];
const seenKeyIds = new Set<string>();
for (const value of values) {
const key = parsePublicSigningKey(value);
if (seenKeyIds.has(key.key_id)) {
throw new SigningKeysSchemaError(`duplicate public signing key id: ${key.key_id}`);
}
seenKeyIds.add(key.key_id);
keys.push(key);
}
return keys;
}
function parsePublicSigningKey(value: unknown): PublicSigningKey {
if (!isRecord(value)) {
throw new SigningKeysSchemaError("public signing key must be an object");
}
const keyId = stringField(value, "key_id");
const publicKey = stringField(value, "public_key").toLowerCase();
if (!KEY_ID_PATTERN.test(keyId)) {
throw new SigningKeysSchemaError(`invalid public signing key id: ${keyId}`);
}
if (!PUBLIC_KEY_PATTERN.test(publicKey)) {
throw new SigningKeysSchemaError(`invalid public signing key value for ${keyId}`);
}
return { key_id: keyId, public_key: publicKey };
}
function normalizedEnvironmentName(value: string): string {
const environment = value.trim();
if (!/^[a-z0-9._-]{3,64}$/.test(environment)) {
throw new SigningKeysSchemaError(`invalid environment name: ${value}`);
}
return environment;
}
function stringField(value: Record<string, unknown>, field: string): string {
const fieldValue = value[field];
if (typeof fieldValue !== "string" || fieldValue.trim() === "") {
throw new SigningKeysSchemaError(`${field} must be a non-empty string`);
}
return fieldValue.trim();
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
+59
View File
@@ -0,0 +1,59 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
SigningKeysSchemaError,
parsePublicSigningKeysDocument,
publicSigningKeysKvKey,
} from "../src/signing_keys.js";
const PUBLIC_KEY = "a".repeat(64);
describe("public signing keys", () => {
it("builds an environment-prefixed KV key", () => {
assert.equal(publicSigningKeysKvKey("production"), "ely:production:public_signing_keys");
});
it("parses a valid key document", () => {
const document = parsePublicSigningKeysDocument(
JSON.stringify({
version: 1,
keys: [{ key_id: "elydora-alpha-plugins", public_key: PUBLIC_KEY.toUpperCase() }],
}),
);
assert.deepEqual(document, {
version: 1,
keys: [{ key_id: "elydora-alpha-plugins", public_key: PUBLIC_KEY }],
});
});
it("rejects duplicate key ids", () => {
assert.throws(
() =>
parsePublicSigningKeysDocument(
JSON.stringify({
version: 1,
keys: [
{ key_id: "elydora-alpha-plugins", public_key: PUBLIC_KEY },
{ key_id: "elydora-alpha-plugins", public_key: "b".repeat(64) },
],
}),
),
SigningKeysSchemaError,
);
});
it("rejects malformed public keys", () => {
assert.throws(
() =>
parsePublicSigningKeysDocument(
JSON.stringify({
version: 1,
keys: [{ key_id: "elydora-alpha-plugins", public_key: "abcd" }],
}),
),
SigningKeysSchemaError,
);
});
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": false,
"verbatimModuleSyntax": true,
"rootDir": "."
},
"include": ["src/**/*.ts"]
}
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": false,
"verbatimModuleSyntax": true,
"rootDir": "."
},
"include": ["src/signing_keys.ts", "tests/**/*.ts"]
}
+6
View File
@@ -0,0 +1,6 @@
name = "ely-browser-cloud"
main = "src/index.ts"
compatibility_date = "2026-05-08"
[vars]
ELY_ENVIRONMENT = "local"
+1 -1
View File
@@ -10,6 +10,6 @@ while IFS= read -r -d '' file; do
echo "${file}: ${lines} lines exceeds ${max_lines}" echo "${file}: ${lines} lines exceeds ${max_lines}"
status=1 status=1
fi fi
done < <(find crates -name '*.rs' -print0) done < <(find crates cloudflare/src cloudflare/tests \( -name '*.rs' -o -name '*.ts' \) -print0)
exit "${status}" exit "${status}"