diff --git a/README.md b/README.md
index c3898ed7d..5d7cd8d2d 100644
--- a/README.md
+++ b/README.md
@@ -935,32 +935,6 @@ require("http").createServer(middleware).listen(3000);
All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/github/oauth"`
-
-
-
-
- options.onUnhandledRequest
- |
-
- function
- |
-
-
-Defaults to
-
-```js
-function onUnhandledRequest(request, response) {
- response.writeHead(404, {
- "content-type": "application/json",
- });
- response.end(
- JSON.stringify({
- error: `Unknown route: ${request.method} ${request.url}`,
- })
- );
-}
-```
-
|
@@ -1025,121 +999,6 @@ addEventListener("fetch", (event) => {
All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/github/oauth"`
-
-
-
-
- options.onUnhandledRequest
- |
-
- function
- |
- Defaults to
-
-```js
-function onUnhandledRequest(request) {
- return new Response(
- JSON.stringify({
- error: `Unknown route: ${request.method} ${request.url}`,
- }),
- {
- status: 404,
- headers: { "content-type": "application/json" },
- }
- );
-}
-```
-
- |
-
-
-
-
-### `createAWSLambdaAPIGatewayV2Handler(app, options)`
-
-Event handler for AWS Lambda using API Gateway V2 HTTP integration.
-
-```js
-// worker.js
-import {
- OAuthApp,
- createAWSLambdaAPIGatewayV2Handler,
-} from "@octokit/oauth-app";
-const app = new OAuthApp({
- clientType: "oauth-app",
- clientId: "1234567890abcdef1234",
- clientSecret: "1234567890abcdef1234567890abcdef12345678",
-});
-
-export const handler = createAWSLambdaAPIGatewayV2Handler(app, {
- pathPrefix: "/api/github/oauth",
-});
-
-// can now receive user authorization callbacks at /api/github/oauth/callback
-```
-
-
-
-
-
- name
- |
-
- type
- |
-
- description
- |
-
-
-
-
-
- app
- |
-
- OAuthApp instance
- |
-
- Required.
- |
-
-
-
- options.pathPrefix
- |
-
- string
- |
-
-
-All exposed paths will be prefixed with the provided prefix. Defaults to `"/api/github/oauth"`
-
- |
-
-
-
- options.onUnhandledRequest
- |
-
- function
- |
- Defaults to returns:
-
-```js
-function onUnhandledRequest(request) {
- return
- {
- status: 404,
- headers: { "content-type": "application/json" },
- body: JSON.stringify({
- error: `Unknown route: [METHOD] [URL]`,
- })
- }
- );
-}
-```
-
|
diff --git a/package.json b/package.json
index 50901ac08..46c888ef4 100644
--- a/package.json
+++ b/package.json
@@ -26,8 +26,6 @@
"@octokit/core": "^4.0.0",
"@octokit/oauth-authorization-url": "^5.0.0",
"@octokit/oauth-methods": "^2.0.0",
- "@types/aws-lambda": "^8.10.83",
- "fromentries": "^1.3.1",
"universal-user-agent": "^6.0.0"
},
"devDependencies": {
diff --git a/src/index.ts b/src/index.ts
index a129bb2ba..34b9a28f7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -50,12 +50,19 @@ import type {
Options,
State,
} from "./types";
+
+// types required by external handlers (aws-lambda, etc)
+export type {
+ HandlerOptions,
+ OctokitRequest,
+ OctokitResponse,
+} from "./middleware/types";
+
+// generic handlers
+export { handleRequest } from "./middleware/handle-request";
+
export { createNodeMiddleware } from "./middleware/node/index";
-export {
- createCloudflareHandler,
- createWebWorkerHandler,
-} from "./middleware/web-worker/index";
-export { createAWSLambdaAPIGatewayV2Handler } from "./middleware/aws-lambda/api-gateway-v2";
+export { createWebWorkerHandler } from "./middleware/web-worker/index";
type Constructor = new (...args: any[]) => T;
diff --git a/src/middleware/README.md b/src/middleware/README.md
index d08c0ba08..541fbf9d3 100644
--- a/src/middleware/README.md
+++ b/src/middleware/README.md
@@ -11,7 +11,6 @@ middleware
├── types.ts
├── node/
├── web-worker/ (Cloudflare Workers & Deno)
-└── deno/ (to be implemented)
```
## Generic HTTP Handler
diff --git a/src/middleware/aws-lambda/api-gateway-v2-parse-request.ts b/src/middleware/aws-lambda/api-gateway-v2-parse-request.ts
deleted file mode 100644
index 7817c0d3e..000000000
--- a/src/middleware/aws-lambda/api-gateway-v2-parse-request.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { OctokitRequest } from "../types";
-import type { APIGatewayProxyEventV2 } from "aws-lambda";
-
-export function parseRequest(request: APIGatewayProxyEventV2): OctokitRequest {
- const { method } = request.requestContext.http;
- let url = request.rawPath;
- const { stage } = request.requestContext;
- if (url.startsWith("/" + stage)) url = url.substring(stage.length + 1);
- if (request.rawQueryString) url += "?" + request.rawQueryString;
- const headers = request.headers as Record;
- const text = async () => request.body || "";
- return { method, url, headers, text };
-}
diff --git a/src/middleware/aws-lambda/api-gateway-v2-send-response.ts b/src/middleware/aws-lambda/api-gateway-v2-send-response.ts
deleted file mode 100644
index 073a790e2..000000000
--- a/src/middleware/aws-lambda/api-gateway-v2-send-response.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { OctokitResponse } from "../types";
-import type { APIGatewayProxyStructuredResultV2 } from "aws-lambda";
-
-export function sendResponse(
- octokitResponse: OctokitResponse
-): APIGatewayProxyStructuredResultV2 {
- return {
- statusCode: octokitResponse.status,
- headers: octokitResponse.headers,
- body: octokitResponse.text,
- };
-}
diff --git a/src/middleware/aws-lambda/api-gateway-v2.ts b/src/middleware/aws-lambda/api-gateway-v2.ts
deleted file mode 100644
index 873c9ae05..000000000
--- a/src/middleware/aws-lambda/api-gateway-v2.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { parseRequest } from "./api-gateway-v2-parse-request";
-import { sendResponse } from "./api-gateway-v2-send-response";
-import { handleRequest } from "../handle-request";
-import { onUnhandledRequestDefault } from "../on-unhandled-request-default";
-import { HandlerOptions } from "../types";
-import { OAuthApp } from "../../index";
-import { Options, ClientType } from "../../types";
-import type {
- APIGatewayProxyEventV2,
- APIGatewayProxyStructuredResultV2,
-} from "aws-lambda";
-
-async function onUnhandledRequestDefaultAWSAPIGatewayV2(
- event: APIGatewayProxyEventV2
-): Promise {
- const request = parseRequest(event);
- const response = onUnhandledRequestDefault(request);
- return sendResponse(response);
-}
-
-export function createAWSLambdaAPIGatewayV2Handler(
- app: OAuthApp>,
- {
- pathPrefix,
- onUnhandledRequest = onUnhandledRequestDefaultAWSAPIGatewayV2,
- }: HandlerOptions & {
- onUnhandledRequest?: (
- event: APIGatewayProxyEventV2
- ) => Promise;
- } = {}
-) {
- return async function (event: APIGatewayProxyEventV2) {
- const request = parseRequest(event);
- const response = await handleRequest(app, { pathPrefix }, request);
- return response ? sendResponse(response) : onUnhandledRequest(event);
- };
-}
diff --git a/src/middleware/handle-request.ts b/src/middleware/handle-request.ts
index 671133a3a..98983347a 100644
--- a/src/middleware/handle-request.ts
+++ b/src/middleware/handle-request.ts
@@ -1,14 +1,12 @@
import { OAuthApp } from "../index";
import { HandlerOptions, OctokitRequest, OctokitResponse } from "./types";
import { ClientType, Options } from "../types";
-// @ts-ignore - requires esModuleInterop flag
-import fromEntries from "fromentries";
export async function handleRequest(
app: OAuthApp>,
{ pathPrefix = "/api/github/oauth" }: HandlerOptions,
request: OctokitRequest
-): Promise {
+): Promise {
if (request.method === "OPTIONS") {
return {
status: 200,
@@ -39,12 +37,18 @@ export async function handleRequest(
// handle unknown routes
if (!Object.values(routes).includes(route)) {
- return null;
+ return {
+ status: 404,
+ headers: { "content-type": "application/json" },
+ text: JSON.stringify({
+ error: `Unknown route: ${request.method} ${request.url}`,
+ }),
+ };
}
let json: any;
try {
- const text = await request.text();
+ const text = request.text;
json = text ? JSON.parse(text) : {};
} catch (error) {
return {
@@ -59,7 +63,7 @@ export async function handleRequest(
};
}
const { searchParams } = new URL(request.url as string, "http://localhost");
- const query = fromEntries(searchParams) as {
+ const query = Object.fromEntries(searchParams) as {
state?: string;
scopes?: string;
code?: string;
@@ -69,7 +73,7 @@ export async function handleRequest(
error_description?: string;
error_url?: string;
};
- const headers = request.headers as { authorization?: string };
+ const headers = (request.headers || {}) as { authorization?: string };
try {
if (route === routes.getLogin) {
diff --git a/src/middleware/node/index.ts b/src/middleware/node/index.ts
index 9bc05f71f..50ae8e150 100644
--- a/src/middleware/node/index.ts
+++ b/src/middleware/node/index.ts
@@ -6,51 +6,23 @@ type ServerResponse = any;
import { parseRequest } from "./parse-request";
import { sendResponse } from "./send-response";
-import { onUnhandledRequestDefault } from "../on-unhandled-request-default";
import { handleRequest } from "../handle-request";
import { OAuthApp } from "../../index";
import { HandlerOptions } from "../types";
import { ClientType, Options } from "../../types";
-function onUnhandledRequestDefaultNode(
- request: IncomingMessage,
- response: ServerResponse
-) {
- const octokitRequest = parseRequest(request);
- const octokitResponse = onUnhandledRequestDefault(octokitRequest);
- sendResponse(octokitResponse, response);
-}
-
export function createNodeMiddleware(
app: OAuthApp>,
- {
- pathPrefix,
- onUnhandledRequest = onUnhandledRequestDefaultNode,
- }: HandlerOptions & {
- onUnhandledRequest?: (
- request: IncomingMessage,
- response: ServerResponse
- ) => void;
- } = {}
+ options: HandlerOptions = {}
) {
return async function (
request: IncomingMessage,
response: ServerResponse,
next?: Function
) {
- const octokitRequest = parseRequest(request);
- const octokitResponse = await handleRequest(
- app,
- { pathPrefix },
- octokitRequest
- );
-
- if (octokitResponse) {
- sendResponse(octokitResponse, response);
- } else if (typeof next === "function") {
- next();
- } else {
- onUnhandledRequest(request, response);
- }
+ const octokitRequest = await parseRequest(request);
+ const octokitResponse = await handleRequest(app, options, octokitRequest);
+ if (octokitResponse.status === 404 && next) return next();
+ sendResponse(octokitResponse, response);
};
}
diff --git a/src/middleware/node/parse-request.ts b/src/middleware/node/parse-request.ts
index 5df274a26..c2f32d614 100644
--- a/src/middleware/node/parse-request.ts
+++ b/src/middleware/node/parse-request.ts
@@ -5,17 +5,16 @@ type IncomingMessage = any;
import { OctokitRequest } from "../types";
-export function parseRequest(request: IncomingMessage): OctokitRequest {
+export async function parseRequest(
+ request: IncomingMessage
+): Promise {
const { method, url, headers } = request;
- async function text() {
- const text = await new Promise((resolve, reject) => {
- let bodyChunks: Uint8Array[] = [];
- request
- .on("error", reject)
- .on("data", (chunk: Uint8Array) => bodyChunks.push(chunk))
- .on("end", () => resolve(Buffer.concat(bodyChunks).toString()));
- });
- return text;
- }
+ const text = await new Promise((resolve, reject) => {
+ let bodyChunks: Uint8Array[] = [];
+ request
+ .on("error", reject)
+ .on("data", (chunk: Uint8Array) => bodyChunks.push(chunk))
+ .on("end", () => resolve(Buffer.concat(bodyChunks).toString()));
+ });
return { method, url, headers, text };
}
diff --git a/src/middleware/node/send-response.ts b/src/middleware/node/send-response.ts
index d753044b5..5ca636345 100644
--- a/src/middleware/node/send-response.ts
+++ b/src/middleware/node/send-response.ts
@@ -1,6 +1,6 @@
// remove type imports from http for Deno compatibility
// see https://github.com/octokit/octokit.js/issues/2075#issuecomment-817361886
-// import { IncomingMessage, ServerResponse } from "http";
+// import { ServerResponse } from "http";
type ServerResponse = any;
import { OctokitResponse } from "../types";
diff --git a/src/middleware/on-unhandled-request-default.ts b/src/middleware/on-unhandled-request-default.ts
deleted file mode 100644
index 14dd11757..000000000
--- a/src/middleware/on-unhandled-request-default.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { OctokitRequest, OctokitResponse } from "./types";
-
-export function onUnhandledRequestDefault(
- request: OctokitRequest
-): OctokitResponse {
- return {
- status: 404,
- headers: { "content-type": "application/json" },
- text: JSON.stringify({
- error: `Unknown route: ${request.method} ${request.url}`,
- }),
- };
-}
diff --git a/src/middleware/types.ts b/src/middleware/types.ts
index f259376d8..80affd4c5 100644
--- a/src/middleware/types.ts
+++ b/src/middleware/types.ts
@@ -1,8 +1,8 @@
export type OctokitRequest = {
method: string;
url: string;
- headers: Record;
- text: () => Promise;
+ headers?: Record;
+ text?: string;
};
export type OctokitResponse = {
diff --git a/src/middleware/web-worker/index.ts b/src/middleware/web-worker/index.ts
index 68511336a..d7ab1fe4b 100644
--- a/src/middleware/web-worker/index.ts
+++ b/src/middleware/web-worker/index.ts
@@ -1,47 +1,17 @@
import { parseRequest } from "./parse-request";
import { sendResponse } from "./send-response";
import { handleRequest } from "../handle-request";
-import { onUnhandledRequestDefault } from "../on-unhandled-request-default";
-import { OAuthApp } from "../../index";
-import { HandlerOptions } from "../types";
-import { ClientType, Options } from "../../types";
-
-async function onUnhandledRequestDefaultWebWorker(
- request: Request
-): Promise {
- const octokitRequest = parseRequest(request);
- const octokitResponse = onUnhandledRequestDefault(octokitRequest);
- return sendResponse(octokitResponse);
-}
+import type { OAuthApp } from "../../index";
+import type { HandlerOptions } from "../types";
+import type { ClientType, Options } from "../../types";
export function createWebWorkerHandler>(
app: OAuthApp,
- {
- pathPrefix,
- onUnhandledRequest = onUnhandledRequestDefaultWebWorker,
- }: HandlerOptions & {
- onUnhandledRequest?: (request: Request) => Response | Promise;
- } = {}
+ options: HandlerOptions = {}
) {
return async function (request: Request): Promise {
- const octokitRequest = parseRequest(request);
- const octokitResponse = await handleRequest(
- app,
- { pathPrefix },
- octokitRequest
- );
- return octokitResponse
- ? sendResponse(octokitResponse)
- : await onUnhandledRequest(request);
+ const octokitRequest = await parseRequest(request);
+ const octokitResponse = await handleRequest(app, options, octokitRequest);
+ return sendResponse(octokitResponse);
};
}
-
-/** @deprecated */
-export function createCloudflareHandler(
- ...args: Parameters
-) {
- args[0].octokit.log.warn(
- "[@octokit/oauth-app] `createCloudflareHandler` is deprecated, use `createWebWorkerHandler` instead"
- );
- return createWebWorkerHandler(...args);
-}
diff --git a/src/middleware/web-worker/parse-request.ts b/src/middleware/web-worker/parse-request.ts
index b11ed3272..b770feb11 100644
--- a/src/middleware/web-worker/parse-request.ts
+++ b/src/middleware/web-worker/parse-request.ts
@@ -1,12 +1,12 @@
import { OctokitRequest } from "../types";
-export function parseRequest(request: Request): OctokitRequest {
+export async function parseRequest(request: Request): Promise {
// @ts-ignore Worker environment supports fromEntries/entries.
const headers = Object.fromEntries(request.headers.entries());
return {
method: request.method,
url: request.url,
headers,
- text: () => request.text(),
+ text: await request.text(),
};
}
diff --git a/test/aws-lambda-api-gateway-v2.test.ts b/test/aws-lambda-api-gateway-v2.test.ts
deleted file mode 100644
index 3209f1902..000000000
--- a/test/aws-lambda-api-gateway-v2.test.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { OAuthApp, createAWSLambdaAPIGatewayV2Handler } from "../src/";
-import { URL } from "url";
-import { APIGatewayProxyEventV2 } from "aws-lambda";
-
-describe("createAWSLambdaAPIGatewayV2Handler(app)", () => {
- it("supports oauth app", async () => {
- const app = new OAuthApp({
- clientType: "oauth-app",
- clientId: "0123",
- clientSecret: "0123secret",
- });
- createAWSLambdaAPIGatewayV2Handler(app);
- });
-
- it("supports github app", async () => {
- const app = new OAuthApp({
- clientType: "github-app",
- clientId: "0123",
- clientSecret: "0123secret",
- });
- createAWSLambdaAPIGatewayV2Handler(app);
- });
-
- it("fail-over to default unhandled request handler", async () => {
- const appMock = {};
- const handleRequest = createAWSLambdaAPIGatewayV2Handler(
- appMock as unknown as OAuthApp
- );
-
- const response = await handleRequest({
- requestContext: { http: { method: "GET" }, stage: "prod" },
- rawPath: "/prod/unknown",
- } as APIGatewayProxyEventV2);
-
- expect(response.statusCode).toBe(404);
- });
-
- it("allow pre-flight requests", async () => {
- const app = new OAuthApp({ clientId: "0123", clientSecret: "0123secret" });
- const handleRequest = createAWSLambdaAPIGatewayV2Handler(app);
-
- const response = await handleRequest({
- requestContext: { http: { method: "OPTIONS" }, stage: "prod" },
- rawPath: "/prod/api/github/oauth/token",
- } as APIGatewayProxyEventV2);
-
- expect(response.statusCode).toStrictEqual(200);
- expect(response.headers!["access-control-allow-origin"]).toBe("*");
- expect(response.headers!["access-control-allow-methods"]).toBe("*");
- expect(response.headers!["access-control-allow-headers"]).toBe(
- "Content-Type, User-Agent, Authorization"
- );
- });
-
- it("supports $default stage", async () => {
- const app = new OAuthApp({ clientId: "0123", clientSecret: "0123secret" });
- const handleRequest = createAWSLambdaAPIGatewayV2Handler(app);
-
- const response = await handleRequest({
- requestContext: { http: { method: "GET" }, stage: "$default" },
- rawPath: "/api/github/oauth/login",
- } as APIGatewayProxyEventV2);
-
- expect(response.statusCode).toBe(302);
- const url = new URL(response.headers!.location as string);
- expect(url.origin).toBe("https://github.com");
- expect(url.pathname).toBe("/login/oauth/authorize");
- expect(url.searchParams.get("client_id")).toBe("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toBeNull();
- });
-
- it("supports named stage", async () => {
- const app = new OAuthApp({ clientId: "0123", clientSecret: "0123secret" });
- const handleRequest = createAWSLambdaAPIGatewayV2Handler(app);
-
- const response = await handleRequest({
- requestContext: { http: { method: "GET" }, stage: "prod" },
- rawPath: "/prod/api/github/oauth/login",
- } as APIGatewayProxyEventV2);
-
- expect(response.statusCode).toBe(302);
- const url = new URL(response.headers!.location as string);
- expect(url.origin).toBe("https://github.com");
- expect(url.pathname).toBe("/login/oauth/authorize");
- expect(url.searchParams.get("client_id")).toBe("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toBeNull();
- });
-
- it("passes query string to generic request handler correctly", async () => {
- const app = new OAuthApp({ clientId: "0123", clientSecret: "0123secret" });
- const handleRequest = createAWSLambdaAPIGatewayV2Handler(app);
-
- const response = await handleRequest({
- requestContext: { http: { method: "GET" }, stage: "prod" },
- rawPath: "/prod/api/github/oauth/login",
- rawQueryString: "state=mystate123&scopes=one,two,three",
- } as APIGatewayProxyEventV2);
-
- expect(response.statusCode).toBe(302);
- const url = new URL(response.headers!.location as string);
- expect(url.origin).toBe("https://github.com");
- expect(url.pathname).toBe("/login/oauth/authorize");
- expect(url.searchParams.get("client_id")).toBe("0123");
- expect(url.searchParams.get("state")).toBe("mystate123");
- expect(url.searchParams.get("scope")).toBe("one,two,three");
- });
-});
diff --git a/test/deprecations.test.ts b/test/deprecations.test.ts
deleted file mode 100644
index 7d9c9e871..000000000
--- a/test/deprecations.test.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { URL } from "url";
-import * as nodeFetch from "node-fetch";
-import fromEntries from "fromentries";
-import { createCloudflareHandler, OAuthApp } from "../src";
-import { Octokit } from "@octokit/core";
-
-describe("deprecations", () => {
- beforeAll(() => {
- Object.fromEntries ||= fromEntries;
- (global as any).Request = nodeFetch.Request;
- (global as any).Response = nodeFetch.Response;
- });
-
- afterAll(() => {
- delete (global as any).Request;
- delete (global as any).Response;
- });
-
- it("createCloudflareHandler works but logs out deprecation message", async () => {
- const warn = jest.fn().mockResolvedValue(undefined);
- const handleRequest = createCloudflareHandler(
- new OAuthApp({
- clientType: "github-app",
- clientId: "client_id_123",
- clientSecret: "client_secret_456",
- Octokit: Octokit.defaults({
- log: {
- debug: () => undefined,
- info: () => undefined,
- warn,
- error: () => undefined,
- },
- }),
- })
- );
-
- expect(warn.mock.calls.length).toEqual(1);
- expect(warn.mock.calls[0][0]).toEqual(
- "[@octokit/oauth-app] `createCloudflareHandler` is deprecated, use `createWebWorkerHandler` instead"
- );
-
- const request = new Request("/api/github/oauth/login");
- const { status, headers } = await handleRequest(request);
-
- expect(status).toEqual(302);
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("client_id_123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toEqual(null);
- });
-});
diff --git a/test/handle-request.test.ts b/test/handle-request.test.ts
new file mode 100644
index 000000000..eef04fa39
--- /dev/null
+++ b/test/handle-request.test.ts
@@ -0,0 +1,654 @@
+import { URL } from "url";
+import * as nodeFetch from "node-fetch";
+import { handleRequest, OAuthApp } from "../src";
+
+describe("handle request", () => {
+ beforeAll(() => {
+ (global as any).Request = nodeFetch.Request;
+ (global as any).Response = nodeFetch.Response;
+ });
+
+ afterAll(() => {
+ delete (global as any).Request;
+ delete (global as any).Response;
+ });
+
+ it("support both oauth-app and github-app", () => {
+ const oauthApp = new OAuthApp({
+ clientType: "oauth-app",
+ clientId: "0123",
+ clientSecret: "0123secret",
+ });
+ handleRequest(oauthApp, {}, { method: "GET", url: "" });
+
+ const githubApp = new OAuthApp({
+ clientType: "github-app",
+ clientId: "0123",
+ clientSecret: "0123secret",
+ });
+ handleRequest(githubApp, {}, { method: "GET", url: "" });
+ });
+
+ it("allow pre-flight requests", async () => {
+ const app = new OAuthApp({
+ clientId: "0123",
+ clientSecret: "0123secret",
+ });
+ const request = {
+ method: "OPTIONS",
+ url: "/api/github/oauth/token",
+ };
+
+ const response = await handleRequest(app, {}, request);
+ expect(response).toBeTruthy();
+ expect(response!.status).toStrictEqual(200);
+ });
+
+ it("GET /api/github/oauth/login", async () => {
+ const app = new OAuthApp({
+ clientId: "0123",
+ clientSecret: "0123secret",
+ });
+ const response = await handleRequest(
+ app,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/login",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(302);
+ const url = new URL(response!.headers!["location"] as string);
+ expect(url).toMatchObject({
+ origin: "https://github.com",
+ pathname: "/login/oauth/authorize",
+ });
+ expect(url.searchParams.get("client_id")).toEqual("0123");
+ expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+ expect(url.searchParams.get("scope")).toEqual(null);
+ });
+
+ it("GET /api/github/oauth/login with defaultScopes (#110)", async () => {
+ const app = new OAuthApp({
+ clientId: "0123",
+ clientSecret: "0123secret",
+ defaultScopes: ["repo"],
+ });
+ const response = await handleRequest(
+ app,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/login",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(302);
+ const url = new URL(response!.headers!["location"] as string);
+ expect(url).toMatchObject({
+ origin: "https://github.com",
+ pathname: "/login/oauth/authorize",
+ });
+ expect(url.searchParams.get("client_id")).toEqual("0123");
+ expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+ expect(url.searchParams.get("scope")).toEqual("repo");
+ });
+
+ it("GET /api/github/oauth/login?state=mystate123&scopes=one,two,three", async () => {
+ const app = new OAuthApp({
+ clientId: "0123",
+ clientSecret: "0123secret",
+ });
+ const response = await handleRequest(
+ app,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/login?state=mystate123&scopes=one,two,three",
+ }
+ );
+
+ const request = new Request(
+ "/api/github/oauth/login?state=mystate123&scopes=one,two,three"
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(302);
+ const url = new URL(response!.headers!["location"] as string);
+ expect(url).toMatchObject({
+ origin: "https://github.com",
+ pathname: "/login/oauth/authorize",
+ });
+ expect(url.searchParams.get("client_id")).toEqual("0123");
+ expect(url.searchParams.get("state")).toEqual("mystate123");
+ expect(url.searchParams.get("scope")).toEqual("one,two,three");
+ });
+
+ it("GET /api/github/oauth/callback?code=012345&state=mystate123", async () => {
+ const appMock = {
+ createToken: jest.fn().mockResolvedValue({
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ token: "token123",
+ },
+ }),
+ };
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/callback?code=012345&state=state123",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(200);
+ expect(response!.text).toMatch(/token123/);
+
+ expect(appMock.createToken.mock.calls.length).toEqual(1);
+ expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
+ code: "012345",
+ });
+ });
+
+ it("POST /api/github/oauth/token", async () => {
+ const appMock = {
+ createToken: jest.fn().mockResolvedValue({
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
+ }),
+ };
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/api/github/oauth/token",
+ headers: { "content-type": "application/json" },
+ text: JSON.stringify({
+ code: "012345",
+ redirectUrl: "http://example.com",
+ }),
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(201);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ authentication: { type: "token", tokenType: "oauth" },
+ });
+
+ expect(appMock.createToken.mock.calls.length).toEqual(1);
+ expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
+ code: "012345",
+ redirectUrl: "http://example.com",
+ });
+ });
+
+ it("GET /api/github/oauth/token", async () => {
+ const appMock = {
+ checkToken: jest.fn().mockResolvedValue({
+ data: { id: 1 },
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
+ }),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/token",
+ headers: { authorization },
+ }
+ );
+
+ expect(response).toBeTruthy();
+
+ expect(response!.status).toEqual(200);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ data: { id: 1 },
+ authentication: { type: "token", tokenType: "oauth" },
+ });
+
+ expect(appMock.checkToken.mock.calls.length).toEqual(1);
+ expect(appMock.checkToken.mock.calls[0][0]).toStrictEqual({ token });
+ });
+
+ it("POST /api/github/oauth/token/scoped", async () => {
+ const appMock = {
+ scopeToken: jest.fn().mockResolvedValue({
+ data: { id: 1 },
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
+ }),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/api/github/oauth/token/scoped",
+ headers: { authorization },
+ text: JSON.stringify({
+ target: "octokit",
+ repositories: ["oauth-methods.js"],
+ permissions: { issues: "write" },
+ }),
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(200);
+ expect(JSON.parse(response!.text!)).toMatchInlineSnapshot(`
+ Object {
+ "authentication": Object {
+ "tokenType": "oauth",
+ "type": "token",
+ },
+ "data": Object {
+ "id": 1,
+ },
+ }
+ `);
+
+ expect(appMock.scopeToken.mock.calls.length).toEqual(1);
+ expect(appMock.scopeToken.mock.calls[0][0]).toMatchInlineSnapshot(`
+ Object {
+ "permissions": Object {
+ "issues": "write",
+ },
+ "repositories": Array [
+ "oauth-methods.js",
+ ],
+ "target": "octokit",
+ "token": "${token}",
+ }
+ `);
+ });
+
+ it("PATCH /api/github/oauth/refresh-token", async () => {
+ const appMock = {
+ refreshToken: jest.fn().mockResolvedValue({
+ data: { id: 1 },
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
+ }),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "PATCH",
+ url: "/api/github/oauth/refresh-token",
+ headers: { authorization },
+ text: JSON.stringify({ refreshToken: "r1.refreshtoken123" }),
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(200);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ data: { id: 1 },
+ authentication: { type: "token", tokenType: "oauth" },
+ });
+
+ expect(appMock.refreshToken.mock.calls.length).toEqual(1);
+ expect(appMock.refreshToken.mock.calls[0][0]).toStrictEqual({
+ refreshToken: "r1.refreshtoken123",
+ });
+ });
+
+ it("PATCH /api/github/oauth/token", async () => {
+ const appMock = {
+ resetToken: jest.fn().mockResolvedValue({
+ data: { id: 1 },
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
+ }),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "PATCH",
+ url: "/api/github/oauth/token",
+ headers: { authorization },
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(200);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ data: { id: 1 },
+ authentication: { type: "token", tokenType: "oauth" },
+ });
+
+ expect(appMock.resetToken.mock.calls.length).toEqual(1);
+ expect(appMock.resetToken.mock.calls[0][0]).toStrictEqual({ token });
+ });
+
+ it("DELETE /api/github/oauth/token", async () => {
+ const appMock = {
+ deleteToken: jest.fn().mockResolvedValue(undefined),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "DELETE",
+ url: "/api/github/oauth/token",
+ headers: { authorization },
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(204);
+ expect(appMock.deleteToken.mock.calls.length).toEqual(1);
+ expect(appMock.deleteToken.mock.calls[0][0]).toStrictEqual({ token });
+ });
+
+ it("DELETE /api/github/oauth/grant", async () => {
+ const appMock = {
+ deleteAuthorization: jest.fn().mockResolvedValue(undefined),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "DELETE",
+ url: "/api/github/oauth/grant",
+ headers: { authorization },
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(204);
+ expect(appMock.deleteAuthorization.mock.calls.length).toEqual(1);
+ expect(appMock.deleteAuthorization.mock.calls[0][0]).toStrictEqual({
+ token,
+ });
+ });
+
+ it("POST /unknown", async () => {
+ const response = await handleRequest(
+ {} as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/unknown",
+ }
+ );
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(404);
+ expect(JSON.parse(response!.text!)).toEqual({
+ error: "Unknown route: POST /unknown",
+ });
+ });
+
+ it("GET /api/github/oauth/callback without code", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/callback",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "code" parameter is required',
+ });
+ });
+
+ it("GET /api/github/oauth/callback with error", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/callback?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https://docs.github.com/en/developers/apps/troubleshooting-authorization-request-errors/%23redirect-uri-mismatch&state=xyz",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error:
+ "[@octokit/oauth-app] redirect_uri_mismatch The redirect_uri MUST match the registered callback URL for this application.",
+ });
+ });
+
+ it("POST /api/github/oauth/token without state or code", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/api/github/oauth/token",
+ headers: { "Content-Type": "application/json" },
+ text: JSON.stringify({}),
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "code" parameter is required',
+ });
+ });
+
+ it("POST /api/github/oauth/token with non-JSON request body", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/api/github/oauth/token",
+ headers: {},
+ text: "foo",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: "[@octokit/oauth-app] request error",
+ });
+ });
+
+ it("GET /api/github/oauth/token without Authorization header", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "GET",
+ url: "/api/github/oauth/token",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("PATCH /api/github/oauth/token without authorization header", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "PATCH",
+ url: "/api/github/oauth/token",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("POST /api/github/oauth/token/scoped without authorization header", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "POST",
+ url: "/api/github/oauth/token/scoped",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("PATCH /api/github/oauth/refresh-token without authorization header", async () => {
+ const appMock = {
+ refreshToken: jest.fn().mockResolvedValue({
+ ok: true,
+ }),
+ };
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "PATCH",
+ url: "/api/github/oauth/refresh-token",
+ headers: {},
+ text: JSON.stringify({
+ refreshToken: "r1.refreshtoken123",
+ }),
+ }
+ );
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("PATCH /api/github/oauth/refresh-token without refreshToken", async () => {
+ const appMock = {
+ refreshToken: jest.fn().mockResolvedValue({
+ ok: true,
+ }),
+ };
+ const token = "token123";
+ const authorization = `token ${token}`;
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "PATCH",
+ url: "/api/github/oauth/refresh-token",
+ headers: { authorization },
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: "[@octokit/oauth-app] refreshToken must be sent in request body",
+ });
+ });
+
+ it("DELETE /api/github/oauth/token without authorization header", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "DELETE",
+ url: "/api/github/oauth/token",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("DELETE /api/github/oauth/grant without authorization header", async () => {
+ const appMock = {};
+ const response = await handleRequest(
+ appMock as unknown as OAuthApp,
+ {},
+ {
+ method: "DELETE",
+ url: "/api/github/oauth/grant",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(400);
+ expect(JSON.parse(response!.text!)).toStrictEqual({
+ error: '[@octokit/oauth-app] "Authorization" header is required',
+ });
+ });
+
+ it("web worker handler with options.pathPrefix", async () => {
+ const response = await handleRequest(
+ new OAuthApp({
+ clientId: "0123",
+ clientSecret: "0123secret",
+ }),
+ { pathPrefix: "/test" },
+ {
+ method: "GET",
+ url: "/test/login",
+ }
+ );
+
+ expect(response).toBeTruthy();
+ expect(response!.status).toEqual(302);
+ });
+});
diff --git a/test/node-middleware.test.ts b/test/node-middleware.test.ts
index 28b2827ae..9c663dbf2 100644
--- a/test/node-middleware.test.ts
+++ b/test/node-middleware.test.ts
@@ -2,726 +2,20 @@ import { createServer } from "http";
import { URL } from "url";
import fetch from "node-fetch";
-import { createNodeMiddleware, OAuthApp } from "../src/";
+import { createNodeMiddleware, OAuthApp } from "../src";
// import without types
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",
- 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 { status, headers } = await fetch(
- `http://localhost:${port}/api/github/oauth/login`,
- {
- redirect: "manual",
- }
- );
-
- server.close();
-
- expect(status).toEqual(302);
-
- const url = new URL(headers.get("location") as string);
-
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toEqual(null);
- });
-
- it("GET /api/github/oauth/login with defaultScopes (#110)", async () => {
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- defaultScopes: ["repo"],
- });
-
- 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 { status, headers } = await fetch(
- `http://localhost:${port}/api/github/oauth/login`,
- {
- redirect: "manual",
- }
- );
-
- server.close();
-
- expect(status).toEqual(302);
-
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toEqual("repo");
- });
-
- it("GET /api/github/oauth/login?state=mystate123&scopes=one,two,three", 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 { status, headers } = await fetch(
- `http://localhost:${port}/api/github/oauth/login?state=mystate123&scopes=one,two,three`,
- {
- redirect: "manual",
- }
- );
-
- server.close();
-
- expect(status).toEqual(302);
-
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
-
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toEqual("mystate123");
- expect(url.searchParams.get("scope")).toEqual("one,two,three");
- });
-
- it("GET /api/github/oauth/callback?code=012345&state=mystate123", async () => {
- const appMock = {
- createToken: jest.fn().mockResolvedValue({
- authentication: {
- type: "token",
- tokenType: "oauth",
- token: "token123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/callback?code=012345&state=state123`
- );
-
- server.close();
-
- expect(response.status).toEqual(200);
- expect(await response.text()).toMatch(/token123/);
-
- expect(appMock.createToken.mock.calls.length).toEqual(1);
- expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
- code: "012345",
- });
- });
-
- it("POST /api/github/oauth/token", async () => {
- const appMock = {
- createToken: jest.fn().mockResolvedValue({
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "POST",
- body: JSON.stringify({
- code: "012345",
- redirectUrl: "http://example.com",
- }),
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(201);
- expect(await response.json()).toStrictEqual({
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.createToken.mock.calls.length).toEqual(1);
- expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
- code: "012345",
- redirectUrl: "http://example.com",
- });
- });
-
- it("GET /api/github/oauth/token", async () => {
- const appMock = {
- checkToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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`,
- {
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.checkToken.mock.calls.length).toEqual(1);
- expect(appMock.checkToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("PATCH /api/github/oauth/token", async () => {
- const appMock = {
- resetToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "PATCH",
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.resetToken.mock.calls.length).toEqual(1);
- expect(appMock.resetToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("POST /api/github/oauth/token/scoped", async () => {
- const appMock = {
- scopeToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/scoped`,
- {
- method: "POST",
- headers: {
- authorization: "token token123",
- },
- body: JSON.stringify({
- target: "octokit",
- repositories: ["oauth-methods.js"],
- permissions: { issues: "write" },
- }),
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toMatchInlineSnapshot(`
- Object {
- "authentication": Object {
- "tokenType": "oauth",
- "type": "token",
- },
- "data": Object {
- "id": 1,
- },
- }
- `);
-
- expect(appMock.scopeToken.mock.calls.length).toEqual(1);
- expect(appMock.scopeToken.mock.calls[0][0]).toMatchInlineSnapshot(`
- Object {
- "permissions": Object {
- "issues": "write",
- },
- "repositories": Array [
- "oauth-methods.js",
- ],
- "target": "octokit",
- "token": "token123",
- }
- `);
- });
-
- it("PATCH /api/github/oauth/refresh-token", async () => {
- const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/refresh-token`,
- {
- method: "PATCH",
- headers: {
- authorization: "token token123",
- },
- body: JSON.stringify({
- refreshToken: "r1.refreshtoken123",
- }),
- }
- );
-
- server.close();
-
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
- expect(response.status).toEqual(200);
-
- expect(appMock.refreshToken.mock.calls.length).toEqual(1);
- expect(appMock.refreshToken.mock.calls[0][0]).toStrictEqual({
- refreshToken: "r1.refreshtoken123",
- });
- });
- it("PATCH /api/github/oauth/token", async () => {
- const appMock = {
- resetToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "PATCH",
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.resetToken.mock.calls.length).toEqual(1);
- expect(appMock.resetToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("DELETE /api/github/oauth/token", async () => {
- const appMock = {
- deleteToken: jest.fn().mockResolvedValue(undefined),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "DELETE",
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(204);
-
- expect(appMock.deleteToken.mock.calls.length).toEqual(1);
- expect(appMock.deleteToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("DELETE /api/github/oauth/grant", async () => {
- const appMock = {
- deleteAuthorization: jest.fn().mockResolvedValue(undefined),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/grant`,
- {
- method: "DELETE",
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(204);
-
- expect(appMock.deleteAuthorization.mock.calls.length).toEqual(1);
- expect(appMock.deleteAuthorization.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("POST /unrelated", async () => {
- expect.assertions(4);
-
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- });
-
- const server = createServer(
- createNodeMiddleware(app, {
- onUnhandledRequest: (request, response) => {
- expect(request.method).toEqual("POST");
- expect(request.url).toEqual("/unrelated");
-
- // test that the request has not yet been consumed with .on("data")
- expect(request.complete).toEqual(false);
-
- response.writeHead(200);
- response.end();
- },
- })
- ).listen();
- // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
- const { port } = server.address();
-
- const { status, headers } = await fetch(
- `http://localhost:${port}/unrelated`,
- {
- method: "POST",
- body: JSON.stringify({ ok: true }),
- headers: {
- "content-type": "application/json",
- },
- }
- );
-
- server.close();
-
- expect(status).toEqual(200);
- });
-
- // errors
-
- it("GET /unknown", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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}/unknown`);
-
- server.close();
-
- expect(response.status).toEqual(404);
- });
-
- it("GET /api/github/oauth/callback without code", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/callback`
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "code" parameter is required',
- });
- });
-
- it("GET /api/github/oauth/callback with error", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/callback?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https://docs.github.com/en/developers/apps/troubleshooting-authorization-request-errors/%23redirect-uri-mismatch&state=xyz`
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error:
- "[@octokit/oauth-app] redirect_uri_mismatch The redirect_uri MUST match the registered callback URL for this application.",
- });
- });
-
- it("POST /api/github/oauth/token without state or code", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "POST",
- body: JSON.stringify({}),
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "code" parameter is required',
- });
- });
-
- it("POST /api/github/oauth/token with non-JSON request body", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "POST",
- body: "foo",
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: "[@octokit/oauth-app] request error",
- });
- });
-
- it("GET /api/github/oauth/token without Authorization header", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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`,
- {
- headers: {},
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/token without authorization header", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "PATCH",
- headers: {},
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("POST /api/github/oauth/token/scoped without authorization header", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/scoped`,
- {
- method: "POST",
- headers: {},
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/refresh-token without authorization header", async () => {
+ it("POST /api/github/oauth/token", async () => {
const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- ok: true,
+ createToken: jest.fn().mockResolvedValue({
+ authentication: {
+ type: "token",
+ tokenType: "oauth",
+ clientSecret: "secret123",
+ },
}),
};
@@ -732,101 +26,27 @@ describe("createNodeMiddleware(app)", () => {
const { port } = server.address();
const response = await fetch(
- `http://localhost:${port}/api/github/oauth/refresh-token`,
+ `http://localhost:${port}/api/github/oauth/token`,
{
- method: "PATCH",
+ method: "POST",
body: JSON.stringify({
- refreshToken: "r1.refreshtoken123",
+ code: "012345",
+ redirectUrl: "http://example.com",
}),
}
);
server.close();
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/refresh-token without refreshToken", async () => {
- const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- ok: true,
- }),
- };
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/refresh-token`,
- {
- method: "PATCH",
- headers: {
- authorization: "token token123",
- },
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: "[@octokit/oauth-app] refreshToken must be sent in request body",
- });
- });
-
- it("DELETE /api/github/oauth/token without authorization header", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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: "DELETE",
- headers: {},
- }
- );
-
- server.close();
-
- expect(response.status).toEqual(400);
+ expect(response.status).toEqual(201);
expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
+ authentication: { type: "token", tokenType: "oauth" },
});
- });
-
- it("DELETE /api/github/oauth/grant without authorization header", async () => {
- const appMock = {};
-
- const server = createServer(
- createNodeMiddleware(appMock as unknown as OAuthApp)
- ).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/grant`,
- {
- method: "DELETE",
- headers: {},
- }
- );
- server.close();
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
+ expect(appMock.createToken.mock.calls.length).toEqual(1);
+ expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
+ code: "012345",
+ redirectUrl: "http://example.com",
});
});
@@ -929,13 +149,13 @@ describe("createNodeMiddleware(app)", () => {
const app = express();
app.use(
- "/test",
+ "/foo",
createNodeMiddleware(
new OAuthApp({
clientId: "0123",
clientSecret: "0123secret",
}),
- { pathPrefix: "/test" }
+ { pathPrefix: "/bar" }
)
);
app.all("*", (_request: any, response: any) =>
@@ -946,7 +166,7 @@ describe("createNodeMiddleware(app)", () => {
const { port } = server.address();
- const { status } = await fetch(`http://localhost:${port}/test/test/login`, {
+ const { status } = await fetch(`http://localhost:${port}/foo/bar/login`, {
redirect: "manual",
});
diff --git a/test/web-worker-handler.test.ts b/test/web-worker-handler.test.ts
index ba26da54c..2ce44a594 100644
--- a/test/web-worker-handler.test.ts
+++ b/test/web-worker-handler.test.ts
@@ -1,16 +1,9 @@
import { URL } from "url";
import * as nodeFetch from "node-fetch";
-import fromEntries from "fromentries";
-import {
- createCloudflareHandler,
- createWebWorkerHandler,
- OAuthApp,
-} from "../src";
-import { Octokit } from "@octokit/core";
+import { createWebWorkerHandler, OAuthApp } from "../src";
describe("createWebWorkerHandler(app)", () => {
beforeAll(() => {
- Object.fromEntries ||= fromEntries;
(global as any).Request = nodeFetch.Request;
(global as any).Response = nodeFetch.Response;
});
@@ -20,129 +13,6 @@ describe("createWebWorkerHandler(app)", () => {
delete (global as any).Response;
});
- it("support both oauth-app and github-app", () => {
- const oauthApp = new OAuthApp({
- clientType: "oauth-app",
- clientId: "0123",
- clientSecret: "0123secret",
- });
- createWebWorkerHandler(oauthApp);
-
- const githubApp = new OAuthApp({
- clientType: "github-app",
- clientId: "0123",
- clientSecret: "0123secret",
- });
- createWebWorkerHandler(githubApp);
- });
-
- it("allow pre-flight requests", async () => {
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- });
- const handleRequest = createWebWorkerHandler(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",
- clientSecret: "0123secret",
- });
- const handleRequest = createWebWorkerHandler(app);
-
- const request = new Request("/api/github/oauth/login");
- const { status, headers } = await handleRequest(request);
-
- expect(status).toEqual(302);
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toEqual(null);
- });
-
- it("GET /api/github/oauth/login with defaultScopes (#110)", async () => {
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- defaultScopes: ["repo"],
- });
- const handleRequest = createWebWorkerHandler(app);
-
- const request = new Request("/api/github/oauth/login");
- const { status, headers } = await handleRequest(request);
-
- expect(status).toEqual(302);
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toMatch(/^\w+$/);
- expect(url.searchParams.get("scope")).toEqual("repo");
- });
-
- it("GET /api/github/oauth/login?state=mystate123&scopes=one,two,three", async () => {
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- });
- const handleRequest = createWebWorkerHandler(app);
-
- const request = new Request(
- "/api/github/oauth/login?state=mystate123&scopes=one,two,three"
- );
- const { status, headers } = await handleRequest(request);
-
- expect(status).toEqual(302);
- const url = new URL(headers.get("location") as string);
- expect(url).toMatchObject({
- origin: "https://github.com",
- pathname: "/login/oauth/authorize",
- });
- expect(url.searchParams.get("client_id")).toEqual("0123");
- expect(url.searchParams.get("state")).toEqual("mystate123");
- expect(url.searchParams.get("scope")).toEqual("one,two,three");
- });
-
- it("GET /api/github/oauth/callback?code=012345&state=mystate123", async () => {
- const appMock = {
- createToken: jest.fn().mockResolvedValue({
- authentication: {
- type: "token",
- tokenType: "oauth",
- token: "token123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request(
- "/api/github/oauth/callback?code=012345&state=state123"
- );
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(200);
- expect(await response.text()).toMatch(/token123/);
-
- expect(appMock.createToken.mock.calls.length).toEqual(1);
- expect(appMock.createToken.mock.calls[0][0]).toStrictEqual({
- code: "012345",
- });
- });
-
it("POST /api/github/oauth/token", async () => {
const appMock = {
createToken: jest.fn().mockResolvedValue({
@@ -177,500 +47,4 @@ describe("createWebWorkerHandler(app)", () => {
redirectUrl: "http://example.com",
});
});
-
- it("GET /api/github/oauth/token", async () => {
- const appMock = {
- checkToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- headers: {
- authorization: "token token123",
- },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.checkToken.mock.calls.length).toEqual(1);
- expect(appMock.checkToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("PATCH /api/github/oauth/token", async () => {
- const appMock = {
- resetToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "PATCH",
- headers: { authorization: "token token123" },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.resetToken.mock.calls.length).toEqual(1);
- expect(appMock.resetToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("POST /api/github/oauth/token/scoped", async () => {
- const appMock = {
- scopeToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token/scoped", {
- method: "POST",
- headers: { authorization: "token token123" },
- body: JSON.stringify({
- target: "octokit",
- repositories: ["oauth-methods.js"],
- permissions: { issues: "write" },
- }),
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(200);
- expect(response.status).toEqual(200);
- expect(await response.json()).toMatchInlineSnapshot(`
- Object {
- "authentication": Object {
- "tokenType": "oauth",
- "type": "token",
- },
- "data": Object {
- "id": 1,
- },
- }
- `);
-
- expect(appMock.scopeToken.mock.calls.length).toEqual(1);
- expect(appMock.scopeToken.mock.calls[0][0]).toMatchInlineSnapshot(`
- Object {
- "permissions": Object {
- "issues": "write",
- },
- "repositories": Array [
- "oauth-methods.js",
- ],
- "target": "octokit",
- "token": "token123",
- }
- `);
- });
-
- it("PATCH /api/github/oauth/refresh-token", async () => {
- const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/refresh-token", {
- method: "PATCH",
- headers: { authorization: "token token123" },
- body: JSON.stringify({ refreshToken: "r1.refreshtoken123" }),
- });
- const response = await handleRequest(request);
-
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
- expect(response.status).toEqual(200);
-
- expect(appMock.refreshToken.mock.calls.length).toEqual(1);
- expect(appMock.refreshToken.mock.calls[0][0]).toStrictEqual({
- refreshToken: "r1.refreshtoken123",
- });
- });
-
- it("PATCH /api/github/oauth/token", async () => {
- const appMock = {
- resetToken: jest.fn().mockResolvedValue({
- data: { id: 1 },
- authentication: {
- type: "token",
- tokenType: "oauth",
- clientSecret: "secret123",
- },
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "PATCH",
- headers: { authorization: "token token123" },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(200);
- expect(await response.json()).toStrictEqual({
- data: { id: 1 },
- authentication: { type: "token", tokenType: "oauth" },
- });
-
- expect(appMock.resetToken.mock.calls.length).toEqual(1);
- expect(appMock.resetToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("DELETE /api/github/oauth/token", async () => {
- const appMock = {
- deleteToken: jest.fn().mockResolvedValue(undefined),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "DELETE",
- headers: { authorization: "token token123" },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(204);
-
- expect(appMock.deleteToken.mock.calls.length).toEqual(1);
- expect(appMock.deleteToken.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("DELETE /api/github/oauth/grant", async () => {
- const appMock = {
- deleteAuthorization: jest.fn().mockResolvedValue(undefined),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/grant", {
- method: "DELETE",
- headers: { authorization: "token token123" },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(204);
-
- expect(appMock.deleteAuthorization.mock.calls.length).toEqual(1);
- expect(appMock.deleteAuthorization.mock.calls[0][0]).toStrictEqual({
- token: "token123",
- });
- });
-
- it("POST /unrelated", async () => {
- expect.assertions(4);
-
- const app = new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- });
- const handleRequest = createWebWorkerHandler(app, {
- onUnhandledRequest: async (request: Request) => {
- expect(request.method).toEqual("POST");
- expect(request.url).toEqual("/unrelated");
- const text = await request.text();
- expect(text).toEqual('{"ok":true}');
- return new Response(null, { status: 200 });
- },
- });
-
- const request = new Request("/unrelated", {
- method: "POST",
- body: JSON.stringify({ ok: true }),
- headers: {
- "content-type": "application/json",
- },
- });
- const { status } = await handleRequest(request);
-
- expect(status).toEqual(200);
- });
-
- // // errors
-
- it("GET /unknown", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/unknown");
- const response = await handleRequest(request);
- expect(response.status).toEqual(404);
- });
-
- it("GET /api/github/oauth/callback without code", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/callback");
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "code" parameter is required',
- });
- });
-
- it("GET /api/github/oauth/callback with error", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request(
- "/api/github/oauth/callback?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https://docs.github.com/en/developers/apps/troubleshooting-authorization-request-errors/%23redirect-uri-mismatch&state=xyz"
- );
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error:
- "[@octokit/oauth-app] redirect_uri_mismatch The redirect_uri MUST match the registered callback URL for this application.",
- });
- });
-
- it("POST /api/github/oauth/token without state or code", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "POST",
- body: JSON.stringify({}),
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "code" parameter is required',
- });
- });
-
- it("POST /api/github/oauth/token with non-JSON request body", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "POST",
- body: "foo",
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: "[@octokit/oauth-app] request error",
- });
- });
-
- it("GET /api/github/oauth/token without Authorization header", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- headers: {},
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/token without authorization header", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "PATCH",
- headers: {},
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("POST /api/github/oauth/token/scoped without authorization header", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token/scoped", {
- method: "POST",
- headers: {},
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/refresh-token without authorization header", async () => {
- const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- ok: true,
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/refresh-token", {
- method: "PATCH",
- body: JSON.stringify({
- refreshToken: "r1.refreshtoken123",
- }),
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("PATCH /api/github/oauth/refresh-token without refreshToken", async () => {
- const appMock = {
- refreshToken: jest.fn().mockResolvedValue({
- ok: true,
- }),
- };
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/refresh-token", {
- method: "PATCH",
- headers: {
- authorization: "token token123",
- },
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: "[@octokit/oauth-app] refreshToken must be sent in request body",
- });
- });
-
- it("DELETE /api/github/oauth/token without authorization header", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/token", {
- method: "DELETE",
- headers: {},
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("DELETE /api/github/oauth/grant without authorization header", async () => {
- const appMock = {};
- const handleRequest = createWebWorkerHandler(
- appMock as unknown as OAuthApp
- );
-
- const request = new Request("/api/github/oauth/grant", {
- method: "DELETE",
- headers: {},
- });
- const response = await handleRequest(request);
-
- expect(response.status).toEqual(400);
- expect(await response.json()).toStrictEqual({
- error: '[@octokit/oauth-app] "Authorization" header is required',
- });
- });
-
- it("web worker handler with options.pathPrefix", async () => {
- const handleRequest = createWebWorkerHandler(
- new OAuthApp({
- clientId: "0123",
- clientSecret: "0123secret",
- }),
- { pathPrefix: "/test" }
- );
-
- const request = new Request("/test/login", { redirect: "manual" });
- const { status } = await handleRequest(request);
-
- expect(status).toEqual(302);
- });
});