Skip to content

Commit 5381385

Browse files
committed
Implement CORS handling in tldraw-sync-worker
- Introduced a set of functions to manage CORS headers and validate allowed origins. - Replaced the previous CORS setup with a more flexible approach, allowing specific origins and Vercel preview URLs. - Updated the router to enforce CORS checks and handle preflight requests for OPTIONS method. - Enhanced the /connect and /unfurl routes to include CORS headers in responses.
1 parent 1ea9d26 commit 5381385

1 file changed

Lines changed: 69 additions & 9 deletions

File tree

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,93 @@
11
import { handleUnfurlRequest } from "cloudflare-workers-unfurl";
2-
import { AutoRouter, cors, error, IRequest } from "itty-router";
2+
import { AutoRouter, error, IRequest } from "itty-router";
33
import { Environment } from "./types";
44

55
// make sure our sync durable object is made available to cloudflare
66
export { TldrawDurableObject } from "./TldrawDurableObject";
77

8-
// we use itty-router (https://itty.dev/) to handle routing. in this example we turn on CORS because
9-
// we're hosting the worker separately to the client. you should restrict this to your own domain.
10-
const { preflight, corsify } = cors({ origin: "*" });
8+
const ALLOWED_ORIGINS = [
9+
"https://roamresearch.com",
10+
"http://localhost:3000",
11+
"app://obsidian.md",
12+
];
13+
14+
const isVercelPreviewUrl = (origin: string): boolean =>
15+
/^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin);
16+
17+
const isAllowedOrigin = (origin: string): boolean =>
18+
ALLOWED_ORIGINS.includes(origin) ||
19+
ALLOWED_ORIGINS.some((allowedOrigin) => origin.startsWith(allowedOrigin)) ||
20+
isVercelPreviewUrl(origin);
21+
22+
const setCorsHeaders = ({
23+
request,
24+
response,
25+
}: {
26+
request: IRequest;
27+
response: Response;
28+
}): Response => {
29+
const origin = request.headers.get("origin");
30+
if (origin && isAllowedOrigin(origin)) {
31+
response.headers.set("Access-Control-Allow-Origin", origin);
32+
response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
33+
response.headers.set(
34+
"Access-Control-Allow-Headers",
35+
"Content-Type, Authorization, x-vercel-protection-bypass",
36+
);
37+
}
38+
return response;
39+
};
40+
41+
const handlePreflight = (request: IRequest): Response => {
42+
const origin = request.headers.get("origin");
43+
if (!origin || !isAllowedOrigin(origin)) {
44+
return error(403, "Origin not allowed");
45+
}
46+
47+
return new Response(null, {
48+
status: 204,
49+
headers: {
50+
"Access-Control-Allow-Origin": origin,
51+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
52+
"Access-Control-Allow-Headers":
53+
"Content-Type, Authorization, x-vercel-protection-bypass",
54+
"Access-Control-Max-Age": "86400",
55+
},
56+
});
57+
};
58+
59+
const enforceAllowedOrigin = (request: IRequest): Response | void => {
60+
if (request.method === "OPTIONS") return;
61+
const origin = request.headers.get("origin");
62+
if (origin && !isAllowedOrigin(origin)) {
63+
return error(403, "Origin not allowed");
64+
}
65+
};
66+
1167
const router = AutoRouter<IRequest, [env: Environment, ctx: ExecutionContext]>({
12-
before: [preflight],
13-
finally: [corsify],
68+
before: [enforceAllowedOrigin],
1469
catch: (e) => {
1570
console.error(e);
1671
return error(e);
1772
},
1873
})
74+
.options("*", handlePreflight)
1975
// requests to /connect are routed to the Durable Object, and handle realtime websocket syncing
20-
.get("/connect/:roomId", (request, env) => {
76+
.get("/connect/:roomId", async (request, env) => {
2177
const id = env.TLDRAW_DURABLE_OBJECT.idFromName(request.params.roomId);
2278
const room = env.TLDRAW_DURABLE_OBJECT.get(id);
23-
return room.fetch(request.url, {
79+
const response = await room.fetch(request.url, {
2480
headers: request.headers,
2581
body: request.body,
2682
});
83+
return setCorsHeaders({ request, response });
2784
})
2885

2986
// bookmarks need to extract metadata from pasted URLs:
30-
.get("/unfurl", handleUnfurlRequest);
87+
.get("/unfurl", async (request) => {
88+
const response = await handleUnfurlRequest(request);
89+
return setCorsHeaders({ request, response });
90+
});
3191

3292
// export our router for cloudflare
3393
export default router;

0 commit comments

Comments
 (0)