somratpro commited on
Commit
bff801f
·
1 Parent(s): 8315203

feat: enhance Cloudflare worker with query parameter support for proxy targeting and improved authentication logic.

Browse files
Files changed (3) hide show
  1. cloudflare-proxy-setup.py +18 -7
  2. cloudflare-worker.js +51 -53
  3. 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 targetHost = request.headers.get("x-target-host");
 
90
 
91
  if (PROXY_SHARED_SECRET) {{
92
- const providedSecret = request.headers.get("x-proxy-key") || "";
93
  if (providedSecret !== PROXY_SHARED_SECRET) {{
94
- return new Response("Unauthorized", {{ status: 401 }});
 
 
 
 
95
  }}
96
  }}
97
 
98
  let targetBase = "";
99
  if (targetHost) {{
100
  if (!isAllowedHost(targetHost)) {{
101
- return new Response("Target host is not allowed.", {{ status: 403 }});
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 targetUrl = targetBase + url.pathname + url.search;
 
 
 
 
 
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
- * Deployment:
5
- * 1. Go to dash.cloudflare.com -> Workers & Pages -> Create Worker.
6
- * 2. Paste this code and deploy.
7
- * 3. Use your worker URL (e.g., https://my-proxy.workers.dev) as CLOUDFLARE_PROXY_URL.
8
  *
9
- * This worker reads the 'x-target-host' header to determine where to forward the request.
 
 
 
10
  */
11
 
 
 
 
 
 
 
 
12
  export default {
13
- async fetch(request, env, ctx) {
14
  const url = new URL(request.url);
15
- const targetHost = request.headers.get("x-target-host");
 
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
- return new Response("Unauthorized", { status: 401 });
 
 
 
 
 
 
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 = allowedTargetsRaw
37
- .split(",")
38
- .map((value) => value.trim().toLowerCase())
39
- .filter(Boolean);
40
 
41
  const isAllowedHost = (hostname) => {
42
- if (!hostname) return false;
43
- const normalized = String(hostname).trim().toLowerCase();
 
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("Target host is not allowed.", { status: 403 });
57
  }
58
  targetBase = `https://${targetHost}`;
 
 
59
  } else {
60
- // Fallback: Guess based on path (legacy support)
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 targetUrl = targetBase + url.pathname + url.search;
77
-
78
- // Copy headers and remove internal/Cloudflare-specific ones
 
 
 
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"); // Don't leak this to the target
 
86
 
87
- const modifiedRequest = new Request(targetUrl, {
88
  method: request.method,
89
- headers: headers,
90
  body: request.body,
91
  redirect: "follow",
92
  });
93
 
94
  try {
95
- const response = await fetch(modifiedRequest);
96
-
97
- // Special handling for some providers which might return 403 on some CF IPs.
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
- echo "Preparing Cloudflare outbound proxy..."
 
 
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