Files
Cursor Agent 5fe6469d4a Separate Loop Library website code into loop-library/
Move the website (site shell, Worker, scripts, audits) under a dedicated
loop-library/ directory so it is clearly separated from the installable Loopy
skill, which stays in skills/loopy/. Update CI, .gitignore, the repository
check script, and maintainer docs to the new paths.

Co-authored-by: mberman84 <mberman84@users.noreply.github.com>
2026-06-27 14:45:53 +00:00

653 lines
17 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
FormGuard,
handleRequest,
} from "../src/index.js";
const SITE_ORIGIN = "https://signals.forwardfuture.com";
const WORKER_ORIGIN = "https://loop-library-forms.mberman84.workers.dev";
class MemoryStorage {
values = new Map();
alarmAt = null;
async deleteAll() {
this.values.clear();
}
async deleteAlarm() {
this.alarmAt = null;
}
async delete(key) {
return this.values.delete(key);
}
async get(key) {
return this.values.get(key);
}
async getAlarm() {
return this.alarmAt;
}
async put(key, value) {
this.values.set(key, structuredClone(value));
}
async setAlarm(scheduledTime) {
this.alarmAt = Number(scheduledTime);
}
}
class MemoryGuardNamespace {
objects = new Map();
idFromName(name) {
return name;
}
get(id) {
if (!this.objects.has(id)) {
this.objects.set(
id,
new FormGuard({ storage: new MemoryStorage() }),
);
}
const guard = this.objects.get(id);
return {
fetch(input, init) {
return guard.fetch(new Request(input, init));
},
};
}
}
function makeEnv(options = {}) {
return {
ALLOWED_ORIGINS: `${SITE_ORIGIN},http://localhost:4173`,
FORM_GUARD: new MemoryGuardNamespace(),
HERENOW_API_KEY: "test-here-now-key",
HERENOW_SITE_SLUG: "test-loop-library",
TURNSTILE_RATE_LIMITER: {
async limit({ key }) {
options.rateLimitCalls?.push(key);
return { success: options.verificationAllowed !== false };
},
},
TURNSTILE_HOSTNAMES: "signals.forwardfuture.com,localhost",
TURNSTILE_SECRET_KEY: "test-turnstile-secret",
TURNSTILE_SITE_KEY: "test-turnstile-site-key",
};
}
function makeDependencies(options = {}) {
const calls = {
siteData: [],
turnstile: [],
};
let siteDataFailuresRemaining = options.siteDataFailures || 0;
return {
calls,
dependencies: {
async fetch(input, init = {}) {
const url = String(input);
if (url.includes("/turnstile/v0/siteverify")) {
const body = new URLSearchParams(init.body);
const token = body.get("response");
const action =
token?.startsWith("weekly") ? "weekly_signup" : "submit_loop";
calls.turnstile.push({
action,
idempotencyKey: body.get("idempotency_key"),
remoteIp: body.get("remoteip"),
token,
});
if (token === "invalid-token") {
return Response.json(
{
success: false,
"error-codes": ["invalid-input-response"],
},
{ status: 200 },
);
}
if (token === "wrong-action-token") {
return Response.json({
success: true,
action: "wrong_action",
hostname: "signals.forwardfuture.com",
});
}
if (token === "wrong-hostname-token") {
return Response.json({
success: true,
action,
hostname: "phishing.example.com",
});
}
return Response.json({
success: true,
action,
hostname: "signals.forwardfuture.com",
});
}
if (url.startsWith("https://here.now/api/v1/publishes/")) {
calls.siteData.push({
authorization: new Headers(init.headers).get("Authorization"),
body: JSON.parse(init.body),
idempotencyKey: new Headers(init.headers).get("Idempotency-Key"),
url,
});
if (siteDataFailuresRemaining > 0) {
siteDataFailuresRemaining -= 1;
return Response.json({ error: "Unavailable" }, { status: 503 });
}
return Response.json({ ok: true }, { status: 201 });
}
throw new Error(`Unexpected fetch: ${url}`);
},
},
};
}
function makeRequest(path, body, options = {}) {
const headers = {
"CF-Connecting-IP": options.ip || "203.0.113.10",
"Content-Type": "application/json",
};
if (options.origin !== null) {
headers.Origin = options.origin || SITE_ORIGIN;
}
return new Request(`${WORKER_ORIGIN}${path}`, {
method: options.method || "POST",
headers,
body: JSON.stringify(body),
});
}
function testUuid(index) {
return `00000000-0000-4000-8000-${String(index).padStart(12, "0")}`;
}
function suggestionBody(overrides = {}) {
return {
honeypot: "",
idempotency_key: testUuid(1),
payload: {
instructions: "Run the checks until every result passes.",
loop_title: "The verified submission loop",
name: "Test Contributor",
source_url: "https://example.com/source",
x_handle: "test_builder",
},
permission: true,
turnstile_token: "suggestion-token",
...overrides,
};
}
function weeklyBody(overrides = {}) {
return {
honeypot: "",
idempotency_key: testUuid(2),
payload: {
email: "Reader@Example.com",
},
turnstile_token: "weekly-token",
...overrides,
};
}
test("rejects requests from origins outside the allowlist", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest("/suggestions", suggestionBody(), { origin: null }),
env,
undefined,
dependencies,
);
assert.equal(response.status, 403);
assert.equal(calls.turnstile.length, 0);
assert.equal(calls.siteData.length, 0);
});
test("returns public Turnstile configuration to allowed origins", async () => {
const env = makeEnv();
const response = await handleRequest(
new Request(`${WORKER_ORIGIN}/config`, {
headers: { Origin: SITE_ORIGIN },
}),
env,
);
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.turnstileSiteKey, env.TURNSTILE_SITE_KEY);
assert.deepEqual(body.actions, {
suggestions: "submit_loop",
weeklySignups: "weekly_signup",
});
assert.equal(response.headers.get("Access-Control-Allow-Origin"), SITE_ORIGIN);
});
test("validates Turnstile before writing a loop suggestion", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest("/suggestions", suggestionBody()),
env,
undefined,
dependencies,
);
assert.equal(response.status, 201);
assert.equal(calls.turnstile.length, 1);
assert.equal(calls.turnstile[0].action, "submit_loop");
assert.equal(calls.turnstile[0].remoteIp, "203.0.113.10");
assert.equal(calls.siteData.length, 1);
assert.equal(
calls.siteData[0].url,
"https://here.now/api/v1/publishes/test-loop-library/data/suggestions",
);
assert.equal(
calls.siteData[0].authorization,
"Bearer test-here-now-key",
);
assert.equal(
calls.siteData[0].idempotencyKey,
testUuid(1),
);
assert.deepEqual(calls.siteData[0].body, {
instructions: "Run the checks until every result passes.",
loop_title: "The verified submission loop",
name: "Test Contributor",
source_url: "https://example.com/source",
x_handle: "@test_builder",
});
});
test("rejects malformed optional X handles without writing", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
payload: {
instructions: "Run the checks until every result passes.",
loop_title: "The verified submission loop",
x_handle: "bad handle",
},
}),
),
env,
undefined,
dependencies,
);
const body = await response.json();
assert.equal(response.status, 400);
assert.equal(body.code, "invalid_x_handle");
assert.equal(calls.turnstile.length, 0);
assert.equal(calls.siteData.length, 0);
});
test("rejects invalid or mismatched Turnstile tokens without writing", async () => {
for (const token of ["invalid-token", "wrong-action-token"]) {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({ turnstile_token: token }),
),
env,
undefined,
dependencies,
);
assert.equal(response.status, 400);
assert.equal(calls.siteData.length, 0);
}
});
test("rejects Turnstile tokens verified for an unlisted hostname", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({ turnstile_token: "wrong-hostname-token" }),
),
env,
undefined,
dependencies,
);
const body = await response.json();
assert.equal(response.status, 400);
assert.equal(body.code, "verification_failed");
assert.equal(calls.turnstile.length, 1);
assert.equal(calls.siteData.length, 0);
});
test("rate limits invalid-token floods before calling Siteverify", async () => {
const rateLimitCalls = [];
const env = makeEnv({
rateLimitCalls,
verificationAllowed: false,
});
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({ turnstile_token: "invalid-token" }),
),
env,
undefined,
dependencies,
);
const body = await response.json();
assert.equal(response.status, 429);
assert.equal(response.headers.get("Retry-After"), "60");
assert.equal(
response.headers.get("Access-Control-Expose-Headers"),
"Retry-After",
);
assert.equal(body.code, "rate_limited");
assert.deepEqual(rateLimitCalls, ["suggestions:203.0.113.10"]);
assert.equal(calls.turnstile.length, 0);
assert.equal(calls.siteData.length, 0);
});
test("normalizes and stores weekly signups only after verification", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest("/weekly-signups", weeklyBody()),
env,
undefined,
dependencies,
);
assert.equal(response.status, 201);
assert.equal(calls.turnstile[0].action, "weekly_signup");
assert.equal(calls.siteData.length, 1);
assert.deepEqual(calls.siteData[0].body, {
email: "reader@example.com",
});
assert.match(calls.siteData[0].url, /data\/weekly_signups$/);
});
test("honeypot submissions receive a fake success without external calls", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
honeypot: "Spam Incorporated",
turnstile_token: "",
}),
),
env,
undefined,
dependencies,
);
assert.equal(response.status, 202);
assert.equal(calls.turnstile.length, 0);
assert.equal(calls.siteData.length, 0);
});
test("enforces hourly limits after valid Turnstile checks", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
for (let index = 0; index < 3; index += 1) {
const response = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
idempotency_key: testUuid(100 + index),
payload: {
instructions: `Run distinct verification sequence ${index}.`,
loop_title: `Rate limit loop ${index}`,
},
turnstile_token: `suggestion-token-${index}`,
}),
),
env,
undefined,
dependencies,
);
assert.equal(response.status, 201);
}
const blocked = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
idempotency_key: testUuid(103),
payload: {
instructions: "This request should exceed the hourly allowance.",
loop_title: "Rate limit loop blocked",
},
turnstile_token: "suggestion-token-blocked",
}),
),
env,
undefined,
dependencies,
);
assert.equal(blocked.status, 429);
assert(Number(blocked.headers.get("Retry-After")) > 0);
assert.equal(calls.siteData.length, 3);
});
test("suppresses duplicate content after verification", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const first = await handleRequest(
makeRequest("/suggestions", suggestionBody()),
env,
undefined,
dependencies,
);
const duplicate = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
idempotency_key: testUuid(3),
turnstile_token: "suggestion-token-2",
}),
),
env,
undefined,
dependencies,
);
assert.equal(first.status, 201);
assert.equal(duplicate.status, 202);
assert.equal(calls.turnstile.length, 2);
assert.equal(calls.siteData.length, 1);
});
test("releases duplicate reservations when Site Data is unavailable", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies({
siteDataFailures: 1,
});
const first = await handleRequest(
makeRequest("/suggestions", suggestionBody()),
env,
undefined,
dependencies,
);
const retry = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
idempotency_key: testUuid(4),
turnstile_token: "suggestion-token-retry",
}),
),
env,
undefined,
dependencies,
);
assert.equal(first.status, 502);
assert.equal(retry.status, 201);
assert.equal(calls.siteData.length, 2);
});
test("rejects reuse of an idempotency key for a changed record", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const first = await handleRequest(
makeRequest("/suggestions", suggestionBody()),
env,
undefined,
dependencies,
);
const conflict = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({
payload: {
instructions: "Run the checks until every result passes.",
loop_title: "The verified submission loop",
name: "A different contributor",
source_url: "https://example.com/another-source",
},
turnstile_token: "suggestion-token-conflict",
}),
{ ip: "203.0.113.11" },
),
env,
undefined,
dependencies,
);
const body = await conflict.json();
assert.equal(first.status, 201);
assert.equal(conflict.status, 409);
assert.equal(body.code, "idempotency_conflict");
assert.equal(calls.siteData.length, 1);
});
test("replays the same idempotent request across an IP change", async () => {
const env = makeEnv();
const { calls, dependencies } = makeDependencies();
const first = await handleRequest(
makeRequest("/suggestions", suggestionBody()),
env,
undefined,
dependencies,
);
const replay = await handleRequest(
makeRequest(
"/suggestions",
suggestionBody({ turnstile_token: "suggestion-token-replay" }),
{ ip: "203.0.113.12" },
),
env,
undefined,
dependencies,
);
assert.equal(first.status, 201);
assert.equal(replay.status, 202);
assert.equal(calls.siteData.length, 1);
});
test("Durable Object alarms physically remove expired guard state", async () => {
const originalNow = Date.now;
let now = 1_800_000_000_000;
Date.now = () => now;
try {
const requestHash = "a".repeat(64);
const cases = [
{
path: "/rate",
body: {
dailyLimit: 10,
form: "suggestions",
hourlyLimit: 3,
idempotencyKey: testUuid(901),
requestHash,
},
expiresAt: () => now + 24 * 60 * 60 * 1000,
},
{
path: "/dedupe/reserve",
body: {
idempotencyKey: testUuid(902),
ttlMs: 1000,
},
expiresAt: () => now + 1000,
},
{
path: "/idempotency/bind",
body: {
form: "suggestions",
idempotencyKey: testUuid(903),
requestHash,
ttlMs: 1000,
},
expiresAt: () => now + 1000,
},
];
for (const entry of cases) {
const storage = new MemoryStorage();
const guard = new FormGuard({ storage });
const expiresAt = entry.expiresAt();
const response = await guard.fetch(
new Request(`https://form-guard${entry.path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(entry.body),
}),
);
assert.equal(response.status, 200);
assert.equal(storage.alarmAt, expiresAt);
assert(storage.values.size > 0);
now = expiresAt + 1;
await guard.alarm();
assert.equal(storage.values.size, 0);
assert.equal(storage.alarmAt, null);
now += 1000;
}
} finally {
Date.now = originalNow;
}
});