Repository: octokit/oauth-app.js
Pull request #257 (2021-08-03 08:38:09)

Enables CORS for HTTP endpoints
This PR enables CORS support for HTTP endpoints (except for `getLogin` and `getCallback`).




Patch:
diff --git a/src/middleware/handle-request.ts b/src/middleware/handle-request.ts
index 3b576b4..ee1da21 100644
--- a/src/middleware/handle-request.ts
+++ b/src/middleware/handle-request.ts
@@ -9,6 +9,18 @@ export async function handleRequest(
   { pathPrefix = "/api/github/oauth" }: HandlerOptions,
   request: OctokitRequest
 ): Promise<OctokitResponse | null> {
+  if (request.method === "OPTIONS") {
+    return {
+      status: 200,
+      headers: {
+        "access-control-allow-origin": "*",
+        "access-control-allow-methods": "*",
+        "access-control-allow-headers":
+          "Content-Type, User-Agent, Authorization",
+      },
+    };
+  }
+
   // request.url may include ?query parameters which we don't want for `route`
   // hence the workaround using new URL()
   const { pathname } = new URL(request.url as string, "http://localhost");
@@ -37,7 +49,10 @@ export async function handleRequest(
   } catch (error) {
     return {
       status: 400,
-      headers: { "content-type": "application/json" },
+      headers: {
+        "content-type": "application/json",
+        "access-control-allow-origin": "*",
+      },
       text: JSON.stringify({
         error: "[@octokit/oauth-app] request error",
       }),
@@ -117,7 +132,10 @@ export async function handleRequest(
 
       return {
         status: 201,
-        headers: { "content-type": "application/json" },
+        headers: {
+          "content-type": "application/json",
+          "access-control-allow-origin": "*",
+        },
         text: JSON.stringify({ token, scopes }),
       };
     }
@@ -137,7 +155,10 @@ export async function handleRequest(
 
       return {
         status: 200,
-        headers: { "content-type": "application/json" },
+        headers: {
+          "content-type": "application/json",
+          "access-control-allow-origin": "*",
+        },
         text: JSON.stringify(result),
       };
     }
@@ -157,7 +178,10 @@ export async function handleRequest(
 
       return {
         status: 200,
-        headers: { "content-type": "application/json" },
+        headers: {
+          "content-type": "application/json",
+          "access-control-allow-origin": "*",
+        },
         text: JSON.stringify(result),
       };
     }
@@ -183,7 +207,10 @@ export async function handleRequest(
 
       return {
         status: 200,
-        headers: { "content-type": "application/json" },
+        headers: {
+          "content-type": "application/json",
+          "access-control-allow-origin": "*",
+        },
         text: JSON.stringify(result),
       };
     }
@@ -204,7 +231,10 @@ export async function handleRequest(
 
       return {
         status: 200,
-        headers: { "content-type": "application/json" },
+        headers: {
+          "content-type": "application/json",
+          "access-control-allow-origin": "*",
+        },
         text: JSON.stringify(result),
       };
     }
@@ -222,7 +252,10 @@ export async function handleRequest(
         token,
       });
 
-      return { status: 204 };
+      return {
+        status: 204,
+        headers: { "access-control-allow-origin": "*" },
+      };
     }
 
     // route === routes.deleteGrant
@@ -238,11 +271,17 @@ export async function handleRequest(
       token,
     });
 
-    return { status: 204 };
+    return {
+      status: 204,
+      headers: { "access-control-allow-origin": "*" },
+    };
   } catch (error) {
     return {
       status: 400,
-      headers: { "content-type": "application/json" },
+      headers: {
+        "content-type": "application/json",
+        "access-control-allow-origin": "*",
+      },
       text: JSON.stringify({ error: error.message }),
     };
   }


Test patch:
diff --git a/test/cloudflare-handler.test.ts b/test/cloudflare-handler.test.ts
index e971819..b67334c 100644
--- a/test/cloudflare-handler.test.ts
+++ b/test/cloudflare-handler.test.ts
@@ -15,6 +15,19 @@ describe("createCloudflareHandler(app)", () => {
     delete (global as any).Response;
   });
 
+  it("allow pre-flight requests", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+    });
+    const handleRequest = createCloudflareHandler(app);
+    const request = new Request("/api/github/oauth/token", {
+      method: "OPTIONS",
+    });
+    const response = await handleRequest(request);
+    expect(response.status).toStrictEqual(200);
+  });
+
   it("GET /api/github/oauth/login", async () => {
     const app = new OAuthApp({
       clientId: "0123",
diff --git a/test/node-middleware.test.ts b/test/node-middleware.test.ts
index 7f51bf6..85fdd81 100644
--- a/test/node-middleware.test.ts
+++ b/test/node-middleware.test.ts
@@ -8,6 +8,26 @@ import { OAuthApp, createNodeMiddleware } from "../src/";
 const express = require("express");
 
 describe("createNodeMiddleware(app)", () => {
+  it("allow pre-flight requests", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+    });
+
+    const server = createServer(createNodeMiddleware(app)).listen();
+    // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
+    const { port } = server.address();
+
+    const response = await fetch(
+      `http://localhost:${port}/api/github/oauth/token`,
+      { method: "OPTIONS" }
+    );
+
+    server.close();
+
+    expect(response.status).toEqual(200);
+  });
+
   it("GET /api/github/oauth/login", async () => {
     const app = new OAuthApp({
       clientId: "0123",