Last active
July 1, 2025 12:48
-
-
Save vanpelt/87932a5707509aa6a090027bfd8da5a1 to your computer and use it in GitHub Desktop.
Smart OAuth Proxy for Cloudflare Container Terminal - Auto-detects and forwards OAuth callbacks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* Smart OAuth Proxy for Cloudflare Container Terminal | |
* | |
* This proxy automatically detects OAuth callback ports and forwards them | |
* to your containerized terminal, enabling seamless authentication for | |
* CLI tools like Gemini that require localhost callbacks. | |
* | |
* Usage: npx https://gist.githubusercontent.com/[gist-url]/smart-oauth-proxy.js [container-url] | |
* Example: npx https://bit.ly/cf-oauth-proxy http://localhost:8787 | |
*/ | |
const http = require('http'); | |
const https = require('https'); | |
const url = require('url'); | |
const net = require('net'); | |
const CONTAINER_URL = process.argv[2] || 'http://localhost:8787'; | |
const SANDBOX_SLUG = process.argv[3] || 'demo'; | |
// Active servers by port | |
const activeServers = {}; | |
let portCheckInterval; | |
function log(message, color = 'reset') { | |
const colors = { | |
red: '\x1b[31m', | |
green: '\x1b[32m', | |
yellow: '\x1b[33m', | |
blue: '\x1b[34m', | |
magenta: '\x1b[35m', | |
cyan: '\x1b[36m', | |
reset: '\x1b[0m' | |
}; | |
console.log(`${colors[color]}${message}${colors.reset}`); | |
} | |
function makeHttpRequest(targetUrl, req, res) { | |
const originalPort = req.headers['x-original-port']; | |
log(`🔄 Forwarding OAuth callback to container (target port: ${originalPort})`, 'blue'); | |
log(`🔍 Original request URL: ${req.url}`, 'cyan'); | |
// Forward to the container's oauth2callback endpoint | |
// req.url already includes the path and query string, so just append it | |
const containerOAuthUrl = `${CONTAINER_URL}/api/sandbox/${SANDBOX_SLUG}${req.url}`; | |
log(`🔍 Forwarding to: ${containerOAuthUrl}`, 'cyan'); | |
const containerUrl = url.parse(containerOAuthUrl); | |
const protocol = containerUrl.protocol === 'https:' ? https : http; | |
const proxyReq = protocol.request({ | |
hostname: containerUrl.hostname, | |
port: containerUrl.port, | |
path: containerUrl.path, | |
method: req.method, | |
headers: { | |
...req.headers, | |
'x-forwarded-for': req.connection.remoteAddress, | |
'x-forwarded-proto': 'http', | |
'x-original-port': originalPort, | |
'x-target-port': originalPort // Tell container which port to forward to | |
} | |
}, (proxyRes) => { | |
log(`✅ Container responded: ${proxyRes.statusCode}`, 'green'); | |
// Forward response headers | |
Object.keys(proxyRes.headers).forEach(key => { | |
res.setHeader(key, proxyRes.headers[key]); | |
}); | |
res.statusCode = proxyRes.statusCode; | |
proxyRes.pipe(res); | |
proxyRes.on('end', () => { | |
log(`📤 OAuth callback completed for port ${originalPort}`, 'green'); | |
}); | |
}); | |
proxyReq.on('error', (err) => { | |
log(`❌ Proxy error: ${err.message}`, 'red'); | |
res.statusCode = 500; | |
res.end(`OAuth proxy error: ${err.message}`); | |
}); | |
// Forward request body if present | |
req.pipe(proxyReq); | |
} | |
function createProxyServer(port) { | |
if (activeServers[port]) { | |
return; // Already listening on this port | |
} | |
const server = http.createServer((req, res) => { | |
// Add port info to headers for tracking | |
req.headers['x-original-port'] = port.toString(); | |
log(`📥 OAuth callback received on port ${port}: ${req.method} ${req.url}`, 'cyan'); | |
makeHttpRequest(CONTAINER_URL, req, res); | |
}); | |
server.listen(port, 'localhost', () => { | |
log(`✅ OAuth proxy listening on localhost:${port}`, 'green'); | |
activeServers[port] = server; | |
}); | |
server.on('error', (err) => { | |
if (err.code === 'EADDRINUSE') { | |
log(`⚠️ Port ${port} already in use (probably by OAuth client) - that's perfect!`, 'yellow'); | |
} else { | |
log(`❌ Server error on port ${port}: ${err.message}`, 'red'); | |
} | |
delete activeServers[port]; | |
}); | |
return server; | |
} | |
async function pollContainerForPortInfo() { | |
try { | |
const response = await new Promise((resolve, reject) => { | |
const reqUrl = `${CONTAINER_URL}/api/sandbox/${SANDBOX_SLUG}/oauth-port-info`; | |
const urlObj = url.parse(reqUrl); | |
const protocol = urlObj.protocol === 'https:' ? https : http; | |
const req = protocol.request({ | |
hostname: urlObj.hostname, | |
port: urlObj.port, | |
path: urlObj.path, | |
method: 'GET' | |
}, (res) => { | |
let data = ''; | |
res.on('data', chunk => data += chunk); | |
res.on('end', () => { | |
try { | |
resolve(JSON.parse(data)); | |
} catch (e) { | |
log(`❌ Failed to parse JSON response: ${data}`, 'red'); | |
resolve(null); | |
} | |
}); | |
}); | |
req.on('error', (err) => { | |
log(`❌ Request error: ${err.message}`, 'red'); | |
resolve(null); | |
}); | |
req.setTimeout(1000, () => { | |
log(`⏰ Request timeout`, 'yellow'); | |
resolve(null); | |
}); | |
req.end(); | |
}); | |
if (response && response.port) { | |
const port = parseInt(response.port); | |
if (!activeServers[port] && port >= 1024 && port <= 65535) { | |
log(`📡 Container reported OAuth port: ${port}`, 'blue'); | |
createProxyServer(port); | |
} | |
} else { | |
log(`📡 Polling container... (no port detected yet)`, 'cyan'); | |
} | |
} catch (e) { | |
log(`❌ Polling error: ${e.message}`, 'red'); | |
} | |
} | |
function startMonitoring() { | |
log('🔍 Starting container polling...', 'yellow'); | |
// Initial port detection | |
pollContainerForPortInfo(); | |
// Periodic monitoring - only poll container for port info | |
portCheckInterval = setInterval(() => { | |
pollContainerForPortInfo(); | |
}, 2000); | |
} | |
function shutdown() { | |
log('\n🛑 Shutting down OAuth proxy...', 'yellow'); | |
if (portCheckInterval) { | |
clearInterval(portCheckInterval); | |
} | |
Object.values(activeServers).forEach(server => { | |
server.close(); | |
}); | |
log('✅ OAuth proxy stopped', 'green'); | |
process.exit(0); | |
} | |
function printInstructions() { | |
console.log(''); | |
log('🔐 Smart OAuth Proxy for Cloudflare Container Terminal', 'blue'); | |
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'blue'); | |
console.log(''); | |
log(`📡 Container URL: ${CONTAINER_URL}`, 'cyan'); | |
log(`🏷️ Sandbox: ${SANDBOX_SLUG}`, 'cyan'); | |
console.log(''); | |
log('This proxy polls the container for OAuth port info and forwards', 'yellow'); | |
log('callbacks to your container, enabling authentication for CLI tools like:', 'yellow'); | |
console.log(''); | |
log('• Gemini CLI (gemini auth login)', 'green'); | |
log('• GitHub CLI (gh auth login)', 'green'); | |
log('• Google Cloud CLI (gcloud auth login)', 'green'); | |
log('• And other OAuth-enabled CLI tools', 'green'); | |
console.log(''); | |
log('🚀 Ready! Run your OAuth commands in the container terminal.', 'green'); | |
log(' The proxy will automatically handle the callbacks.', 'green'); | |
console.log(''); | |
log('Press Ctrl+C to stop the proxy', 'yellow'); | |
console.log(''); | |
} | |
// Main execution | |
if (require.main === module) { | |
printInstructions(); | |
startMonitoring(); | |
// Graceful shutdown | |
process.on('SIGINT', shutdown); | |
process.on('SIGTERM', shutdown); | |
} | |
module.exports = { createProxyServer }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment