Rhys Sullivangoogle oauth works2e328ab8 months agoHistory
104 lines · 3.0 KB · TypeScript
RawDownload
import { createServer } from "node:http"; import * as Effect from "effect/Effect"; const DEFAULT_LOOPBACK_TIMEOUT_MS = 10 * 60_000; export type OauthLoopbackRedirectServer = {  redirectUri: string;  close: Effect.Effect<void, Error, never>;}; const closeServer = (  server: ReturnType<typeof createServer>,): Promise<void> =>  new Promise<void>((resolve, reject) => {    server.close((error) => {      if (error) {        reject(error);        return;      }      resolve();    });  }); export const startOauthLoopbackRedirectServer = (input: {  completionUrl: string;  publicHost?: "localhost" | "127.0.0.1";  listenHost?: "127.0.0.1";  timeoutMs?: number;}): Effect.Effect<OauthLoopbackRedirectServer, Error, never> =>  Effect.tryPromise({    try: () =>      new Promise<OauthLoopbackRedirectServer>((resolve, reject) => {    const publicHost = input.publicHost ?? "127.0.0.1";    const listenHost = input.listenHost ?? "127.0.0.1";    const completionUrl = new URL(input.completionUrl);    const server = createServer((request, response) => {      try {        const requestUrl = new URL(          request.url ?? "/",          `http://${publicHost}`,        );        const redirectUrl = new URL(completionUrl.toString());         for (const [key, value] of requestUrl.searchParams.entries()) {          redirectUrl.searchParams.set(key, value);        }         response.statusCode = 302;        response.setHeader("cache-control", "no-store");        response.setHeader("location", redirectUrl.toString());        response.end();      } catch (cause) {        const error = cause instanceof Error ? cause : new Error(String(cause));        response.statusCode = 500;        response.setHeader("content-type", "text/plain; charset=utf-8");        response.end(`OAuth redirect failed: ${error.message}`);      } finally {      void closeServer(server).catch(() => {});      }    });     let timeout: NodeJS.Timeout | null = null;    const cleanup = (): Promise<void> => {      if (timeout !== null) {        clearTimeout(timeout);        timeout = null;      }      return closeServer(server).catch(() => undefined);    };     server.once("error", (cause) => {      reject(        cause instanceof Error ? cause : new Error(String(cause)),      );    });     server.listen(0, listenHost, () => {      const address = server.address();      if (!address || typeof address === "string") {        reject(new Error("Failed to resolve OAuth loopback port"));        return;      }       timeout = setTimeout(() => {        void cleanup();      }, input.timeoutMs ?? DEFAULT_LOOPBACK_TIMEOUT_MS);      if (typeof timeout.unref === "function") {        timeout.unref();      }       resolve({        redirectUri: `http://${publicHost}:${address.port}`,        close: Effect.tryPromise({          try: cleanup,          catch: (cause) =>            cause instanceof Error ? cause : new Error(String(cause)),        }),      });    });      }),    catch: (cause) =>      cause instanceof Error ? cause : new Error(String(cause)),  });