import http from "http"; import getPort from "get-port"; import open from "open"; import url from "url"; import { getFormattedError } from "../utils/errror"; import listen from "async-listen"; import { UserCancellationError } from "../errors/UserCancellationError"; import { API_BASE } from "../constants"; const DASHBOARD_BASE = API_BASE; const LOGIN_ROUTE = `/cli/auth-callback`; //lol make sure you dont accidentally flip and cause infinite loop >< const PortRange = { low: 4000, high: 5000 }; function getPortsFromPortRange(portRanges: { low: number; high: number }) { const { low, high } = portRanges; if (low > high) throw new Error( getFormattedError({ coreMessage: "Internal Error, port ranges 'low' is greater than 'high', this must have been a misconfiguration", humanMessage: "This is an error on our end, plz file an issue or reach out on discord :)", }) ); const ports = []; for (let port = low; port <= high; port++) { ports.push(port); } return ports; } const openBrowser = ({ mode, port }: { port: number; mode: string }) => { // the local server url the login flow will redirect back to const redirectUrl = `http://localhost:${port}/cli/auth-callback`; // the remote server url that will handle the login flow const loginUrl = `${DASHBOARD_BASE}/${LOGIN_ROUTE}?mode=${mode}&redirectUrl=${encodeURIComponent( redirectUrl )}`; open(loginUrl).catch(() => { console.error( `Unable to open the browser, please visit the following url to complete the login process:${loginUrl}` ); }); }; export const browserAuth = async (): Promise< { token: string } | { error: string } > => { const port = await getPort({ port: getPortsFromPortRange(PortRange) }); const server = http.createServer(); await listen(server, port, "localhost"); const authPromise = () => { return new Promise<{ token: string }>((resolve, reject) => { server.on("request", (req, res) => { //mkae sure we have the correct url if (!req.url || !req.url.startsWith("/cli/auth-callback?")) { return; } // Set CORS headers for all responses res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS"); res.setHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" ); if (req.method === "OPTIONS") { res.writeHead(200); res.end(); } else if (req.method === "GET") { const parsedUrl = url.parse(req.url as string, true); const queryParams = parsedUrl.query; if (queryParams.cancelled) { res.writeHead(200); res.end(); reject( new UserCancellationError({ coreMessage: "Login process cancelled by user.", }) ); } else if (queryParams.token) { res.writeHead(200); res.end(); resolve(queryParams as { token: string }); } else { res.writeHead(405); res.end(); reject(); } } else { res.writeHead(405); res.end(); } }); }); }; try { openBrowser({ mode: "cli", port }); const authData = await authPromise(); return authData; } catch (error) { if (error instanceof UserCancellationError) { return { error: "User cancelled the authentication flow", }; } return { error: "Authentication Failed! please try again later, or reach out on discord :)", }; } finally { server.close(); } };