Skip to content

Commit 77a4dd4

Browse files
authored
Fix CORS on redirect responses and add Vary: Origin (#62)
1 parent 6e7ba2d commit 77a4dd4

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

handleRequest.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ it("should redirect non-wasm plugins to asset URL", async () => {
335335
expect(response.headers.get("location")).toEqual(
336336
"https://plugins.dprint.dev/dprint/dprint-plugin-prettier/0.7.0/asset/plugin.json",
337337
);
338+
expect(response.headers.get("access-control-allow-origin")).toEqual("https://dprint.dev");
338339
});
339340

340341
it("should not redirect wasm plugins", async () => {
@@ -354,6 +355,7 @@ it("should redirect non-allowed org asset to GitHub", async () => {
354355
expect(response.headers.get("location")).toEqual(
355356
"https://github.com/someone/some-repo/releases/download/0.1.0/file.zip",
356357
);
358+
expect(response.headers.get("access-control-allow-origin")).toEqual("https://dprint.dev");
357359
});
358360

359361
it("should return 404 for asset not found", async () => {

handleRequest.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function createRequestHandler() {
3535
const assetResult = tryResolveAssetUrl(url);
3636
if (assetResult != null) {
3737
if (!assetResult.shouldCache) {
38-
return Response.redirect(assetResult.githubUrl, 302);
38+
return createRedirectResponse(request, assetResult.githubUrl);
3939
}
4040
return servePlugin(request, assetResult.githubUrl, ctx);
4141
}
@@ -47,7 +47,7 @@ export function createRequestHandler() {
4747
// plugin.json files resolve correctly
4848
const assetPath = githubUrlToAssetPath(githubUrl);
4949
if (assetPath != null) {
50-
return Response.redirect(`${url.origin}${assetPath}`, 302);
50+
return createRedirectResponse(request, `${url.origin}${assetPath}`);
5151
}
5252
}
5353
return servePlugin(request, githubUrl, ctx);
@@ -124,6 +124,7 @@ export function createRequestHandler() {
124124
headers: {
125125
"content-type": result.contentType,
126126
"Access-Control-Allow-Origin": getAccessControlAllowOrigin(request),
127+
"Vary": "Origin",
127128
},
128129
status: result.status,
129130
});
@@ -209,6 +210,20 @@ export async function resolvePluginOrSchemaUrl(url: URL) {
209210
return await tryResolveSchemaUrl(url);
210211
}
211212

213+
// builds a redirect that also carries the CORS header, because for
214+
// cross-origin fetches the browser runs the CORS check on the redirect
215+
// response itself — not just the final response it points at
216+
function createRedirectResponse(request: Request, location: string) {
217+
return new Response(null, {
218+
status: 302,
219+
headers: {
220+
"location": location,
221+
"Access-Control-Allow-Origin": getAccessControlAllowOrigin(request),
222+
"Vary": "Origin",
223+
},
224+
});
225+
}
226+
212227
function getAccessControlAllowOrigin(request: Request) {
213228
const origin = request.headers.get("origin");
214229
return origin != null && isLocalHostname(new URL(origin).hostname)
@@ -225,6 +240,7 @@ function createJsonResponse(text: string, request: Request) {
225240
headers: {
226241
"content-type": contentTypes.json,
227242
"Access-Control-Allow-Origin": getAccessControlAllowOrigin(request),
243+
"Vary": "Origin",
228244
},
229245
status: 200,
230246
});

0 commit comments

Comments
 (0)