From 2cefb14500be3f42e08274671917454fb58e788a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 1 Feb 2026 17:51:10 +0000 Subject: [PATCH] 2 --- backend/src/routes/proxy.js | 284 +++++++++++++++++++++++++++--------- frontend/src/pages/_app.tsx | 188 ++++++++++-------------- 2 files changed, 293 insertions(+), 179 deletions(-) diff --git a/backend/src/routes/proxy.js b/backend/src/routes/proxy.js index c4de94d..79eb80b 100644 --- a/backend/src/routes/proxy.js +++ b/backend/src/routes/proxy.js @@ -2,39 +2,132 @@ const express = require('express'); const router = express.Router(); const axios = require('axios'); const Helpers = require('../helpers'); +const qs = require('qs'); -router.all('/', Helpers.wrapAsync(async (req, res) => { - const targetUrl = req.query.url; - if (!targetUrl) return res.status(400).send('URL is required'); +const proxyHandler = Helpers.wrapAsync(async (req, res) => { + let targetUrl = req.query.url; + + // 1. Handle additional query parameters + // If we have ?url=https://site.com&q=foo, we want to fetch https://site.com?q=foo + if (targetUrl) { + try { + const urlObj = new URL(targetUrl); + Object.keys(req.query).forEach(key => { + if (key !== 'url') { + urlObj.searchParams.set(key, req.query[key]); + } + }); + targetUrl = urlObj.href; + } catch (e) { + // Might be relative, handle below + } + } + + // 2. Handle Path-based URLs (e.g. /api/proxy/https://google.com) + if (!targetUrl || !targetUrl.startsWith('http')) { + const originalUrl = req.originalUrl; + const marker = '/api/proxy/'; + if (originalUrl.includes(marker)) { + const remainder = originalUrl.split(marker)[1]; + if (remainder && remainder.startsWith('http')) { + targetUrl = remainder; + } else if (remainder && req.headers.referer) { + // 3. Smart Path Resolution via Referer + try { + const refUrl = new URL(req.headers.referer); + const parentTarget = refUrl.searchParams.get('url'); + if (parentTarget) { + targetUrl = new URL(remainder, parentTarget).href; + } + } catch (e) {} + } + } + } + + if (!targetUrl || !targetUrl.startsWith('http')) { + console.warn('[Proxy] No valid target URL found for:', req.originalUrl); + // If we can't find a URL, but we have a referer, maybe the browser followed a relative link + // that the proxy didn't catch. Try to redirect them back into the proxy. + if (req.headers.referer && req.headers.referer.includes('/api/proxy')) { + try { + const refUrl = new URL(req.headers.referer); + const parentTarget = refUrl.searchParams.get('url'); + if (parentTarget) { + const absoluteUrl = new URL(req.url.replace('/api/proxy', ''), parentTarget).href; + return res.redirect(`/api/proxy?url=${encodeURIComponent(absoluteUrl)}`); + } + } catch (e) {} + } + + return res.status(400).send(` +
+

URL Required

+

The stealth proxy needs a target URL to work.

+
+ ${req.originalUrl} +
+

Try entering a full URL in the address bar, e.g., https://google.com

+ +
+ `); + } try { const method = req.method; - const headers = { ...req.headers }; + const headers = {}; - // Remove host and other headers that might cause issues - delete headers.host; - delete headers.origin; - delete headers.referer; + // Essential headers to forward + const forwardHeaders = [ + 'accept', 'accept-language', 'accept-encoding', 'user-agent', 'content-type', 'range', 'authorization' + ]; - // Set a common User-Agent - headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'; + forwardHeaders.forEach(h => { + if (req.headers[h]) { + headers[h] = req.headers[h]; + } + }); - const response = await axios({ + // Ensure a realistic User-Agent + if (!headers['user-agent']) { + headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'; + } + + // Strip Referer and Origin to avoid target site security blocks + delete headers['referer']; + delete headers['origin']; + + console.log(`[Proxy] ${method} -> ${targetUrl}`); + + const axiosConfig = { url: targetUrl, method: method, headers: headers, - data: req.body, - params: req.params, responseType: 'arraybuffer', - validateStatus: () => true, + validateStatus: () => true, maxRedirects: 10, - timeout: 15000, - }); + timeout: 30000, + decompress: true + }; - const contentType = response.headers['content-type'] || ''; + if (method !== 'GET' && method !== 'HEAD' && req.body) { + if (headers['content-type'] && headers['content-type'].includes('application/x-www-form-urlencoded')) { + axiosConfig.data = typeof req.body === 'string' ? req.body : qs.stringify(req.body); + } else { + axiosConfig.data = req.body; + } + } + + const response = await axios(axiosConfig); const finalUrl = response.request.res.responseUrl || targetUrl; - - // Copy headers but strip security and encoding ones + const contentType = response.headers['content-type'] || ''; + + // Handle Redirects manually if axios didn't (though maxRedirects is 10) + if (response.status >= 300 && response.status < 400 && response.headers.location) { + const redirUrl = new URL(response.headers.location, finalUrl).href; + return res.redirect(`/api/proxy?url=${encodeURIComponent(redirUrl)}`); + } + + // Forward headers from target, stripping security ones Object.keys(response.headers).forEach(key => { const lowerKey = key.toLowerCase(); if (![ @@ -45,88 +138,147 @@ router.all('/', Helpers.wrapAsync(async (req, res) => { 'transfer-encoding', 'connection', 'strict-transport-security', - 'x-content-type-options' + 'x-content-type-options', + 'set-cookie', + 'access-control-allow-origin' ].includes(lowerKey)) { res.setHeader(key, response.headers[key]); } }); - // Ensure we don't have caching issues during dev - res.setHeader('Cache-Control', 'no-store'); + // Force CORS for the proxy + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); let data = response.data; if (contentType.includes('text/html')) { let html = data.toString('utf-8'); - const parsedUrl = new URL(finalUrl); - const origin = parsedUrl.origin; - const baseUrl = origin + parsedUrl.pathname; - - // Inject and frame-busting neutralization + let origin = ''; + try { + const parsedUrl = new URL(finalUrl); + origin = parsedUrl.origin; + } catch (e) { + origin = targetUrl; + } + const headInjection = ` `; - if (html.includes('')) { - html = html.replace('', `${headInjection}`); - } else if (html.includes('')) { - html = html.replace('', `${headInjection}`); + // Inject headInjection + if (html.toLowerCase().includes('')) { + html = html.replace(//i, `${headInjection}`); + } else if (html.toLowerCase().includes('')) { + html = html.replace(//i, `${headInjection}`); } else { html = headInjection + html; } - // More aggressive link and resource rewriting - // This helps with relative paths that might miss in some cases - // especially in scripts or attributes we don't handle well - - // Rewrite links to stay in proxy - html = html.replace(/(href|src|action)="\s*((?!#|javascript|mailto|tel|data:|https?:\/\/).*?)\s*"/gi, (match, attr, p1) => { + // Aggressive rewrite for absolute and relative URLs + html = html.replace(/(href|src|action)=["'](.*?)["']/gi, (match, attr, p1) => { + if (!p1 || p1.startsWith('#') || p1.startsWith('javascript:') || p1.startsWith('data:') || p1.startsWith('mailto:')) return match; + try { const absoluteUrl = new URL(p1, finalUrl).href; - if (attr.toLowerCase() === 'href' || attr.toLowerCase() === 'action') { - return `${attr}="/api/proxy?url=${encodeURIComponent(absoluteUrl)}"`; - } - // For src, we might not want to proxy everything (images/scripts) as it adds load - // but if the site blocks cross-origin, we must. - // Let's proxy scripts and iframes. - if (match.toLowerCase().includes(' +

Proxy Error

+

Failed to load the requested page.

+ ${error.message} + + + `); } -})); +}); + +router.all('/', proxyHandler); +router.all('/:any*', proxyHandler); module.exports = router; \ No newline at end of file diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index f3ab15f..6a43583 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -33,7 +33,6 @@ type AppPropsWithLayout = AppProps & { } function MyApp({ Component, pageProps }: AppPropsWithLayout) { - // Use the layout defined at the page level, if available const getLayout = Component.getLayout || ((page) => page); const router = useRouter(); const [stepsEnabled, setStepsEnabled] = React.useState(false); @@ -57,7 +56,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { } ); - // TODO: Remove this code in future releases React.useEffect(() => { const allowedOrigin = (() => { if (!document.referrer) { @@ -112,40 +110,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { }, []); React.useEffect(() => { - // Tour is disabled by default in generated projects. return; - const isCompleted = (stepKey: string) => { - return localStorage.getItem(`completed_${stepKey}`) === 'true'; - }; - if (router.pathname === '/login' && !isCompleted('loginSteps')) { - setSteps(loginSteps); - setStepName('loginSteps'); - setStepsEnabled(true); - }else if (router.pathname === '/dashboard' && !isCompleted('appSteps')) { - setTimeout(() => { - setSteps(appSteps); - setStepName('appSteps'); - setStepsEnabled(true); - }, 1000); - } else if (router.pathname === '/users/users-list' && !isCompleted('usersSteps')) { - setTimeout(() => { - setSteps(usersSteps); - setStepName('usersSteps'); - setStepsEnabled(true); - }, 1000); - } else if (router.pathname === '/roles/roles-list' && !isCompleted('rolesSteps')) { - setTimeout(() => { - setSteps(rolesSteps); - setStepName('rolesSteps'); - setStepsEnabled(true); - }, 1000); - } else { - setSteps([]); - setStepsEnabled(false); - } }, [router.pathname]); - // Stealth search trigger: press 'g' 5 times in 5 seconds React.useEffect(() => { let keyPresses: number[] = []; const handleKeyDown = (e: KeyboardEvent) => { @@ -162,34 +129,35 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { if (keyPresses.length >= 5) { const win = window.open('about:blank', '_blank'); if (win) { - const proxyUrl = window.location.origin + '/api/proxy?url='; + const apiBase = process.env.NEXT_PUBLIC_BACK_API || '/api'; + const proxyUrl = window.location.origin + apiBase + '/proxy?url='; const content = ` Zen Browser @@ -200,26 +168,32 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { -
-
-
+