/** * OAuth redirect port helpers — extracted from auth.ts to break the * auth.ts ↔ xaaIdpLogin.ts circular dependency. */ import { createServer } from 'http' import { getPlatform } from '../../utils/platform.js' // Windows dynamic port range 49152-65535 is reserved const REDIRECT_PORT_RANGE = getPlatform() === 'windows' ? { min: 39152, max: 49151 } : { min: 49152, max: 65535 } const REDIRECT_PORT_FALLBACK = 3118 /** * Builds a redirect URI on localhost with the given port and a fixed `/callback` path. * * RFC 8252 Section 7.3 (OAuth for Native Apps): loopback redirect URIs match any * port as long as the path matches. */ export function buildRedirectUri( port: number = REDIRECT_PORT_FALLBACK, ): string { return `http://localhost:${port}/callback` } function getMcpOAuthCallbackPort(): number | undefined { const port = parseInt(process.env.MCP_OAUTH_CALLBACK_PORT || '', 10) return port > 0 ? port : undefined } /** * Finds an available port in the specified range for OAuth redirect * Uses random selection for better security */ export async function findAvailablePort(): Promise { // First, try the configured port if specified const configuredPort = getMcpOAuthCallbackPort() if (configuredPort) { return configuredPort } const { min, max } = REDIRECT_PORT_RANGE const range = max - min + 1 const maxAttempts = Math.min(range, 100) // Don't try forever for (let attempt = 0; attempt < maxAttempts; attempt++) { const port = min + Math.floor(Math.random() * range) try { await new Promise((resolve, reject) => { const testServer = createServer() testServer.once('error', reject) testServer.listen(port, () => { testServer.close(() => resolve()) }) }) return port } catch { // Port in use, try another random port continue } } // If random selection failed, try the fallback port try { await new Promise((resolve, reject) => { const testServer = createServer() testServer.once('error', reject) testServer.listen(REDIRECT_PORT_FALLBACK, () => { testServer.close(() => resolve()) }) }) return REDIRECT_PORT_FALLBACK } catch { throw new Error(`No available ports for OAuth redirect`) } }