feat: enhance Cloudflare worker with query parameter support for proxy targeting and improved authentication logic.
Browse files- cloudflare-proxy-setup.py +18 -7
- cloudflare-worker.js +51 -53
- start.sh +5 -2
cloudflare-proxy-setup.py
CHANGED
|
@@ -86,35 +86,46 @@ function isAllowedHost(hostname) {{
|
|
| 86 |
|
| 87 |
async function handleRequest(request) {{
|
| 88 |
const url = new URL(request.url);
|
| 89 |
-
const
|
|
|
|
| 90 |
|
| 91 |
if (PROXY_SHARED_SECRET) {{
|
| 92 |
-
const providedSecret = request.headers.get("x-proxy-key") || "";
|
| 93 |
if (providedSecret !== PROXY_SHARED_SECRET) {{
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}}
|
| 96 |
}}
|
| 97 |
|
| 98 |
let targetBase = "";
|
| 99 |
if (targetHost) {{
|
| 100 |
if (!isAllowedHost(targetHost)) {{
|
| 101 |
-
return new Response(
|
| 102 |
}}
|
| 103 |
targetBase = `https://${{targetHost}}`;
|
| 104 |
}} else if (url.pathname.startsWith("/bot")) {{
|
| 105 |
targetBase = "https://api.telegram.org";
|
| 106 |
}} else {{
|
| 107 |
-
return new Response("Invalid request.", {{ status: 400 }});
|
| 108 |
}}
|
| 109 |
|
| 110 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
const headers = new Headers(request.headers);
|
| 112 |
-
headers.delete("host");
|
| 113 |
headers.delete("cf-connecting-ip");
|
| 114 |
headers.delete("cf-ray");
|
| 115 |
headers.delete("cf-visitor");
|
|
|
|
| 116 |
headers.delete("x-real-ip");
|
| 117 |
headers.delete("x-target-host");
|
|
|
|
| 118 |
|
| 119 |
const proxiedRequest = new Request(targetUrl, {{
|
| 120 |
method: request.method,
|
|
|
|
| 86 |
|
| 87 |
async function handleRequest(request) {{
|
| 88 |
const url = new URL(request.url);
|
| 89 |
+
const queryTarget = url.searchParams.get("proxy_target");
|
| 90 |
+
const targetHost = request.headers.get("x-target-host") || queryTarget;
|
| 91 |
|
| 92 |
if (PROXY_SHARED_SECRET) {{
|
| 93 |
+
const providedSecret = request.headers.get("x-proxy-key") || url.searchParams.get("proxy_key") || "";
|
| 94 |
if (providedSecret !== PROXY_SHARED_SECRET) {{
|
| 95 |
+
if (url.pathname.startsWith("/bot") && !targetHost) {{
|
| 96 |
+
// Allowed fallback
|
| 97 |
+
}} else {{
|
| 98 |
+
return new Response("Unauthorized: Invalid proxy key", {{ status: 401 }});
|
| 99 |
+
}}
|
| 100 |
}}
|
| 101 |
}}
|
| 102 |
|
| 103 |
let targetBase = "";
|
| 104 |
if (targetHost) {{
|
| 105 |
if (!isAllowedHost(targetHost)) {{
|
| 106 |
+
return new Response(`Forbidden: Host ${{targetHost}} is not allowed.`, {{ status: 403 }});
|
| 107 |
}}
|
| 108 |
targetBase = `https://${{targetHost}}`;
|
| 109 |
}} else if (url.pathname.startsWith("/bot")) {{
|
| 110 |
targetBase = "https://api.telegram.org";
|
| 111 |
}} else {{
|
| 112 |
+
return new Response("Invalid request: No target host provided.", {{ status: 400 }});
|
| 113 |
}}
|
| 114 |
|
| 115 |
+
const cleanSearch = new URLSearchParams(url.search);
|
| 116 |
+
cleanSearch.delete("proxy_target");
|
| 117 |
+
cleanSearch.delete("proxy_key");
|
| 118 |
+
const searchStr = cleanSearch.toString();
|
| 119 |
+
const targetUrl = targetBase + url.pathname + (searchStr ? `?${{searchStr}}` : "");
|
| 120 |
+
|
| 121 |
const headers = new Headers(request.headers);
|
|
|
|
| 122 |
headers.delete("cf-connecting-ip");
|
| 123 |
headers.delete("cf-ray");
|
| 124 |
headers.delete("cf-visitor");
|
| 125 |
+
headers.delete("host");
|
| 126 |
headers.delete("x-real-ip");
|
| 127 |
headers.delete("x-target-host");
|
| 128 |
+
headers.delete("x-proxy-key");
|
| 129 |
|
| 130 |
const proxiedRequest = new Request(targetUrl, {{
|
| 131 |
method: request.method,
|
cloudflare-worker.js
CHANGED
|
@@ -1,46 +1,58 @@
|
|
| 1 |
/**
|
| 2 |
* Cloudflare Worker: Universal Outbound Proxy
|
| 3 |
*
|
| 4 |
-
*
|
| 5 |
-
* 1.
|
| 6 |
-
* 2. Paste this
|
| 7 |
-
* 3. Use
|
| 8 |
*
|
| 9 |
-
*
|
|
|
|
|
|
|
|
|
|
| 10 |
*/
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
export default {
|
| 13 |
-
async fetch(request, env
|
| 14 |
const url = new URL(request.url);
|
| 15 |
-
const
|
|
|
|
| 16 |
const proxySecret = (
|
| 17 |
-
env.CLOUDFLARE_PROXY_SECRET ||
|
| 18 |
env.PROXY_SHARED_SECRET ||
|
|
|
|
| 19 |
""
|
| 20 |
).trim();
|
| 21 |
|
| 22 |
-
// Secret check is optional: when unset, requests are allowed without x-proxy-key.
|
| 23 |
if (proxySecret) {
|
| 24 |
-
const providedSecret = request.headers.get("x-proxy-key") || "";
|
| 25 |
if (providedSecret !== proxySecret) {
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
| 30 |
-
const allowedTargetsRaw = (
|
| 31 |
-
env.ALLOWED_TARGETS ||
|
| 32 |
-
"api.telegram.org,discord.com,discordapp.com,gateway.discord.gg,status.discord.com,web.whatsapp.com,graph.facebook.com,googleapis.com,google.com,googleusercontent.com,gstatic.com"
|
| 33 |
-
).trim();
|
| 34 |
const allowProxyAll =
|
| 35 |
String(env.ALLOW_PROXY_ALL || "true").toLowerCase() === "true";
|
| 36 |
-
const allowedTargets =
|
| 37 |
-
.
|
| 38 |
-
|
| 39 |
-
.filter(Boolean);
|
| 40 |
|
| 41 |
const isAllowedHost = (hostname) => {
|
| 42 |
-
|
| 43 |
-
|
|
|
|
| 44 |
if (!normalized) return false;
|
| 45 |
if (allowProxyAll) return true;
|
| 46 |
return allowedTargets.some(
|
|
@@ -49,57 +61,43 @@ export default {
|
|
| 49 |
};
|
| 50 |
|
| 51 |
let targetBase = "";
|
| 52 |
-
|
| 53 |
if (targetHost) {
|
| 54 |
-
// Use the host provided in the header (preferred)
|
| 55 |
if (!isAllowedHost(targetHost)) {
|
| 56 |
-
return new Response(
|
| 57 |
}
|
| 58 |
targetBase = `https://${targetHost}`;
|
|
|
|
|
|
|
| 59 |
} else {
|
| 60 |
-
|
| 61 |
-
if (url.pathname.startsWith("/bot")) {
|
| 62 |
-
targetBase = "https://api.telegram.org";
|
| 63 |
-
} else if (
|
| 64 |
-
url.pathname.startsWith("/api/webhooks") ||
|
| 65 |
-
url.pathname.startsWith("/api/v")
|
| 66 |
-
) {
|
| 67 |
-
targetBase = "https://discord.com";
|
| 68 |
-
} else {
|
| 69 |
-
return new Response(
|
| 70 |
-
"Invalid request. 'x-target-host' header missing and target not recognized via path.",
|
| 71 |
-
{ status: 400 },
|
| 72 |
-
);
|
| 73 |
-
}
|
| 74 |
}
|
| 75 |
|
| 76 |
-
const
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
| 79 |
const headers = new Headers(request.headers);
|
| 80 |
-
headers.delete("host");
|
| 81 |
headers.delete("cf-connecting-ip");
|
| 82 |
headers.delete("cf-ray");
|
| 83 |
headers.delete("cf-visitor");
|
|
|
|
| 84 |
headers.delete("x-real-ip");
|
| 85 |
-
headers.delete("x-target-host");
|
|
|
|
| 86 |
|
| 87 |
-
const
|
| 88 |
method: request.method,
|
| 89 |
-
headers
|
| 90 |
body: request.body,
|
| 91 |
redirect: "follow",
|
| 92 |
});
|
| 93 |
|
| 94 |
try {
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
// If needed, you can add retry logic here.
|
| 99 |
-
|
| 100 |
-
return response;
|
| 101 |
-
} catch (e) {
|
| 102 |
-
return new Response(`Proxy Error: ${e.message}`, { status: 502 });
|
| 103 |
}
|
| 104 |
},
|
| 105 |
};
|
|
|
|
| 1 |
/**
|
| 2 |
* Cloudflare Worker: Universal Outbound Proxy
|
| 3 |
*
|
| 4 |
+
* Manual setup:
|
| 5 |
+
* 1. Create a Cloudflare Worker.
|
| 6 |
+
* 2. Paste this file and deploy it.
|
| 7 |
+
* 3. Use the worker URL as CLOUDFLARE_PROXY_URL.
|
| 8 |
*
|
| 9 |
+
* Optional worker vars:
|
| 10 |
+
* - PROXY_SHARED_SECRET
|
| 11 |
+
* - ALLOWED_TARGETS
|
| 12 |
+
* - ALLOW_PROXY_ALL
|
| 13 |
*/
|
| 14 |
|
| 15 |
+
function normalizeList(raw) {
|
| 16 |
+
return String(raw || "")
|
| 17 |
+
.split(",")
|
| 18 |
+
.map((value) => value.trim().toLowerCase())
|
| 19 |
+
.filter(Boolean);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
export default {
|
| 23 |
+
async fetch(request, env) {
|
| 24 |
const url = new URL(request.url);
|
| 25 |
+
const queryTarget = url.searchParams.get("proxy_target");
|
| 26 |
+
const targetHost = request.headers.get("x-target-host") || queryTarget;
|
| 27 |
const proxySecret = (
|
|
|
|
| 28 |
env.PROXY_SHARED_SECRET ||
|
| 29 |
+
env.CLOUDFLARE_PROXY_SECRET ||
|
| 30 |
""
|
| 31 |
).trim();
|
| 32 |
|
|
|
|
| 33 |
if (proxySecret) {
|
| 34 |
+
const providedSecret = request.headers.get("x-proxy-key") || url.searchParams.get("proxy_key") || "";
|
| 35 |
if (providedSecret !== proxySecret) {
|
| 36 |
+
// Fallback: allow Telegram requests via path without secret if it looks like a bot API call.
|
| 37 |
+
// This is safe because it only proxies to api.telegram.org.
|
| 38 |
+
if (url.pathname.startsWith("/bot") && !targetHost) {
|
| 39 |
+
// Allowed
|
| 40 |
+
} else {
|
| 41 |
+
return new Response("Unauthorized: Invalid proxy key", { status: 401 });
|
| 42 |
+
}
|
| 43 |
}
|
| 44 |
}
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
const allowProxyAll =
|
| 47 |
String(env.ALLOW_PROXY_ALL || "true").toLowerCase() === "true";
|
| 48 |
+
const allowedTargets = normalizeList(
|
| 49 |
+
env.ALLOWED_TARGETS || "api.telegram.org,discord.com,discordapp.com,gateway.discord.gg,status.discord.com,web.whatsapp.com,graph.facebook.com,googleapis.com,google.com,googleusercontent.com,gstatic.com",
|
| 50 |
+
);
|
|
|
|
| 51 |
|
| 52 |
const isAllowedHost = (hostname) => {
|
| 53 |
+
const normalized = String(hostname || "")
|
| 54 |
+
.trim()
|
| 55 |
+
.toLowerCase();
|
| 56 |
if (!normalized) return false;
|
| 57 |
if (allowProxyAll) return true;
|
| 58 |
return allowedTargets.some(
|
|
|
|
| 61 |
};
|
| 62 |
|
| 63 |
let targetBase = "";
|
|
|
|
| 64 |
if (targetHost) {
|
|
|
|
| 65 |
if (!isAllowedHost(targetHost)) {
|
| 66 |
+
return new Response(`Forbidden: Host ${targetHost} is not allowed.`, { status: 403 });
|
| 67 |
}
|
| 68 |
targetBase = `https://${targetHost}`;
|
| 69 |
+
} else if (url.pathname.startsWith("/bot")) {
|
| 70 |
+
targetBase = "https://api.telegram.org";
|
| 71 |
} else {
|
| 72 |
+
return new Response("Invalid request: No target host provided.", { status: 400 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
|
| 75 |
+
const cleanSearch = new URLSearchParams(url.search);
|
| 76 |
+
cleanSearch.delete("proxy_target");
|
| 77 |
+
cleanSearch.delete("proxy_key");
|
| 78 |
+
const searchStr = cleanSearch.toString();
|
| 79 |
+
const targetUrl = targetBase + url.pathname + (searchStr ? `?${searchStr}` : "");
|
| 80 |
+
|
| 81 |
const headers = new Headers(request.headers);
|
|
|
|
| 82 |
headers.delete("cf-connecting-ip");
|
| 83 |
headers.delete("cf-ray");
|
| 84 |
headers.delete("cf-visitor");
|
| 85 |
+
headers.delete("host");
|
| 86 |
headers.delete("x-real-ip");
|
| 87 |
+
headers.delete("x-target-host");
|
| 88 |
+
headers.delete("x-proxy-key");
|
| 89 |
|
| 90 |
+
const proxiedRequest = new Request(targetUrl, {
|
| 91 |
method: request.method,
|
| 92 |
+
headers,
|
| 93 |
body: request.body,
|
| 94 |
redirect: "follow",
|
| 95 |
});
|
| 96 |
|
| 97 |
try {
|
| 98 |
+
return await fetch(proxiedRequest);
|
| 99 |
+
} catch (error) {
|
| 100 |
+
return new Response(`Proxy Error: ${error.message}`, { status: 502 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
},
|
| 103 |
};
|
start.sh
CHANGED
|
@@ -73,14 +73,17 @@ else
|
|
| 73 |
echo "HF_TOKEN is not set. Running without dataset persistence."
|
| 74 |
fi
|
| 75 |
|
| 76 |
-
CF_PROXY_ENV_FILE="/tmp/hugging8n-cloudflare-proxy.env"
|
| 77 |
CLOUDFLARE_WORKERS_TOKEN="${CLOUDFLARE_WORKERS_TOKEN:-${CLOUDFLARE_API_TOKEN:-}}"
|
| 78 |
export CLOUDFLARE_WORKERS_TOKEN
|
|
|
|
| 79 |
if [ -n "${CLOUDFLARE_WORKERS_TOKEN:-}" ] || [ -n "${CLOUDFLARE_PROXY_URL:-}" ]; then
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
python3 "$APP_DIR/cloudflare-proxy-setup.py" || true
|
| 82 |
if [ -f "$CF_PROXY_ENV_FILE" ]; then
|
| 83 |
. "$CF_PROXY_ENV_FILE"
|
|
|
|
| 84 |
fi
|
| 85 |
fi
|
| 86 |
|
|
|
|
| 73 |
echo "HF_TOKEN is not set. Running without dataset persistence."
|
| 74 |
fi
|
| 75 |
|
|
|
|
| 76 |
CLOUDFLARE_WORKERS_TOKEN="${CLOUDFLARE_WORKERS_TOKEN:-${CLOUDFLARE_API_TOKEN:-}}"
|
| 77 |
export CLOUDFLARE_WORKERS_TOKEN
|
| 78 |
+
CF_PROXY_ENV_FILE="/tmp/hugging8n-cloudflare-proxy.env"
|
| 79 |
if [ -n "${CLOUDFLARE_WORKERS_TOKEN:-}" ] || [ -n "${CLOUDFLARE_PROXY_URL:-}" ]; then
|
| 80 |
+
export CLOUDFLARE_PROXY_DOMAINS="${CLOUDFLARE_PROXY_DOMAINS:-*}"
|
| 81 |
+
export CLOUDFLARE_PROXY_DEBUG="${CLOUDFLARE_PROXY_DEBUG:-true}"
|
| 82 |
+
echo "☁️ Preparing Cloudflare outbound proxy..."
|
| 83 |
python3 "$APP_DIR/cloudflare-proxy-setup.py" || true
|
| 84 |
if [ -f "$CF_PROXY_ENV_FILE" ]; then
|
| 85 |
. "$CF_PROXY_ENV_FILE"
|
| 86 |
+
echo " ✅ Proxy environment loaded: ${CLOUDFLARE_PROXY_URL:-none}"
|
| 87 |
fi
|
| 88 |
fi
|
| 89 |
|