import { inject, injectable } from "@codemation/core"; import { ApplicationRequestError } from "../../application/ApplicationRequestError"; import { ApplicationTokens } from "../../applicationTokens"; import type { AppConfig } from "../../presentation/config/AppConfig"; @injectable() export class OAuth2RedirectUriResolver { constructor( @inject(ApplicationTokens.AppConfig) private readonly appConfig: AppConfig, ) {} resolve(requestOrigin: string): string { const rawBase = this.appConfig.env.CODEMATION_PUBLIC_BASE_URL?.trim() || requestOrigin.trim(); if (!rawBase) { throw new Error("Unable to resolve the public base URL for OAuth2 redirect URI generation."); } const baseUrl = this.ensureAbsoluteUrl(rawBase); try { const callback = new URL("/api/oauth2/callback", this.normalizeBaseUrl(baseUrl)); const loopbackHostnames = new Set(["127.0.0.1", "[::1]"]); if (loopbackHostnames.has(callback.hostname)) { callback.hostname = "localhost"; } return callback.toString(); } catch { throw new ApplicationRequestError( 500, `Invalid public base URL for OAuth2 redirect URI generation: "${rawBase}". Use a full URL (e.g. http://localhost:3000) for CODEMATION_PUBLIC_BASE_URL or ensure the request has a valid Host / forwarded headers.`, ); } } private ensureAbsoluteUrl(raw: string): string { const segments = raw .split(",") .map((s) => s.trim()) .filter((s) => s.length > 0); let candidate = segments[0] ?? raw.trim(); if (!candidate) { throw new Error("Unable to resolve the public base URL for OAuth2 redirect URI generation."); } if (!/^https?:\/\//i.test(candidate)) { candidate = `http://${candidate}`; } let parsed: URL; try { parsed = new URL(candidate); } catch { throw new ApplicationRequestError( 500, `Invalid public base URL for OAuth2 redirect URI generation: "${raw}". Use a single full URL (e.g. http://localhost:3000) for CODEMATION_PUBLIC_BASE_URL.`, ); } if (parsed.hostname === "http" || parsed.hostname === "https") { throw new ApplicationRequestError( 500, `Invalid OAuth2 public base URL (hostname "${parsed.hostname}"). Set CODEMATION_PUBLIC_BASE_URL to one full URL with a real host, e.g. http://localhost:3000 — not "http,http" or other typos.`, ); } return candidate; } private normalizeBaseUrl(baseUrl: string): string { return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; } }