mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
refactor(cors): replace manual CORS middleware with hono/cors
Fixes a bug where routes returning raw `new Response()` (RSS, Atom, entries) were not receiving CORS headers — hono/cors applies headers after next(), covering all response paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import worker from "./index";
|
||||
import { createMockEnv } from "./test/setup";
|
||||
import type { Env } from "./types";
|
||||
|
||||
const env = createMockEnv();
|
||||
|
||||
function req(path: string, init: RequestInit = {}): Request {
|
||||
return new Request(`https://test.getmynews.app${path}`, init);
|
||||
}
|
||||
|
||||
describe("CORS middleware", () => {
|
||||
it("adds CORS headers for an allowed origin", async () => {
|
||||
const res = await worker.fetch(
|
||||
req("/rss/some-feed", { headers: { Origin: "https://getmynews.app" } }),
|
||||
env as unknown as Env,
|
||||
);
|
||||
expect(res.headers.get("Access-Control-Allow-Origin")).toBe(
|
||||
"https://getmynews.app",
|
||||
);
|
||||
});
|
||||
|
||||
it("omits CORS headers for an unknown origin", async () => {
|
||||
const res = await worker.fetch(
|
||||
req("/rss/some-feed", { headers: { Origin: "https://evil.com" } }),
|
||||
env as unknown as Env,
|
||||
);
|
||||
expect(res.headers.get("Access-Control-Allow-Origin")).toBeNull();
|
||||
});
|
||||
|
||||
it("handles OPTIONS preflight for an allowed origin with 204", async () => {
|
||||
const res = await worker.fetch(
|
||||
req("/rss/some-feed", {
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
Origin: "https://getmynews.app",
|
||||
"Access-Control-Request-Method": "GET",
|
||||
},
|
||||
}),
|
||||
env as unknown as Env,
|
||||
);
|
||||
expect(res.status).toBe(204);
|
||||
expect(res.headers.get("Access-Control-Allow-Origin")).toBe(
|
||||
"https://getmynews.app",
|
||||
);
|
||||
});
|
||||
});
|
||||
+10
-18
@@ -1,4 +1,5 @@
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { handle as handleInbound } from "./routes/inbound";
|
||||
import { handle as handleRSS } from "./routes/rss";
|
||||
import { handle as handleAtom } from "./routes/atom";
|
||||
@@ -9,7 +10,6 @@ import { hubRouter } from "./routes/hub";
|
||||
import { handleCloudflareEmail } from "./lib/cloudflare-email";
|
||||
import { Env } from "./types";
|
||||
|
||||
// Define allowed origins for CORS
|
||||
const ALLOWED_ORIGINS = ["https://getmynews.app", "https://www.getmynews.app"];
|
||||
|
||||
// Fallback ForwardEmail.net IP addresses in case API fetch fails
|
||||
@@ -84,23 +84,15 @@ async function getForwardEmailIps(): Promise<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
// CORS middleware
|
||||
app.use("*", async (c, next) => {
|
||||
const origin = c.req.header("Origin");
|
||||
if (origin && ALLOWED_ORIGINS.includes(origin)) {
|
||||
c.header("Access-Control-Allow-Origin", origin);
|
||||
c.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
c.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
c.header("Access-Control-Max-Age", "86400");
|
||||
}
|
||||
|
||||
// Handle preflight requests
|
||||
if (c.req.method === "OPTIONS") {
|
||||
return c.body(null, 204);
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
app.use(
|
||||
"*",
|
||||
cors({
|
||||
origin: ALLOWED_ORIGINS,
|
||||
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allowHeaders: ["Content-Type", "Authorization"],
|
||||
maxAge: 86400,
|
||||
}),
|
||||
);
|
||||
|
||||
// Group routes by functionality
|
||||
const api = new Hono();
|
||||
|
||||
@@ -55,7 +55,11 @@ describe("handleCloudflareEmail", () => {
|
||||
JSON.stringify({}),
|
||||
);
|
||||
|
||||
await handleCloudflareEmail(makeMessage(), env as any, {} as any);
|
||||
await handleCloudflareEmail(
|
||||
makeMessage(),
|
||||
env as any,
|
||||
{ waitUntil: () => {} } as any,
|
||||
);
|
||||
|
||||
const metadata = await env.EMAIL_STORAGE.get(
|
||||
`feed:${VALID_FEED_ID}:metadata`,
|
||||
@@ -67,14 +71,18 @@ describe("handleCloudflareEmail", () => {
|
||||
|
||||
it("does not throw when feed does not exist", async () => {
|
||||
await expect(
|
||||
handleCloudflareEmail(makeMessage(), env as any, {} as any),
|
||||
handleCloudflareEmail(
|
||||
makeMessage(),
|
||||
env as any,
|
||||
{ waitUntil: () => {} } as any,
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not throw when email is malformed", async () => {
|
||||
const msg = makeMessage({ rawText: "not a valid email" });
|
||||
await expect(
|
||||
handleCloudflareEmail(msg, env as any, {} as any),
|
||||
handleCloudflareEmail(msg, env as any, { waitUntil: () => {} } as any),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -84,7 +92,11 @@ describe("handleCloudflareEmail", () => {
|
||||
JSON.stringify({ allowed_senders: ["sender@example.com"] }),
|
||||
);
|
||||
|
||||
await handleCloudflareEmail(makeMessage(), env as any, {} as any);
|
||||
await handleCloudflareEmail(
|
||||
makeMessage(),
|
||||
env as any,
|
||||
{ waitUntil: () => {} } as any,
|
||||
);
|
||||
|
||||
const metadata = await env.EMAIL_STORAGE.get(
|
||||
`feed:${VALID_FEED_ID}:metadata`,
|
||||
@@ -99,7 +111,11 @@ describe("handleCloudflareEmail", () => {
|
||||
JSON.stringify({ allowed_senders: ["other@example.com"] }),
|
||||
);
|
||||
|
||||
await handleCloudflareEmail(makeMessage(), env as any, {} as any);
|
||||
await handleCloudflareEmail(
|
||||
makeMessage(),
|
||||
env as any,
|
||||
{ waitUntil: () => {} } as any,
|
||||
);
|
||||
|
||||
const metadata = await env.EMAIL_STORAGE.get(
|
||||
`feed:${VALID_FEED_ID}:metadata`,
|
||||
|
||||
Reference in New Issue
Block a user