File size: 3,358 Bytes
72ece3f
 
96b5930
72ece3f
 
 
3fbb925
96b5930
72ece3f
 
 
 
 
 
 
7f99b73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96b5930
72ece3f
 
 
 
7f99b73
 
 
72ece3f
 
 
 
 
96b5930
 
 
 
72ece3f
 
96b5930
 
 
 
72ece3f
 
 
 
96b5930
72ece3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96b5930
72ece3f
 
96b5930
72ece3f
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
 * Cloudflare Worker: Universal Outbound Proxy
 *
 * Deployment:
 * 1. Go to dash.cloudflare.com -> Workers & Pages -> Create Worker.
 * 2. Paste this code and deploy.
 * 3. Use your worker URL (e.g., https://my-proxy.workers.dev) as CLOUDFLARE_PROXY_URL.
 *
 * This worker reads the 'x-target-host' header to determine where to forward the request.
 */

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const targetHost = request.headers.get("x-target-host");
    const proxySecret = (
      env.CLOUDFLARE_PROXY_SECRET ||
      env.PROXY_SHARED_SECRET ||
      ""
    ).trim();

    // Secret check is optional: when unset, requests are allowed without x-proxy-key.
    if (proxySecret) {
      const providedSecret = request.headers.get("x-proxy-key") || "";
      if (providedSecret !== proxySecret) {
        return new Response("Unauthorized", { status: 401 });
      }
    }

    const allowedTargetsRaw = (
      env.ALLOWED_TARGETS ||
      "api.telegram.org,discord.com,discordapp.com,gateway.discord.gg,status.discord.com"
    ).trim();
    const allowProxyAll =
      String(env.ALLOW_PROXY_ALL || "false").toLowerCase() === "true";
    const allowedTargets = allowedTargetsRaw
      .split(",")
      .map((value) => value.trim().toLowerCase())
      .filter(Boolean);

    const isAllowedHost = (hostname) => {
      if (!hostname) return false;
      const normalized = String(hostname).trim().toLowerCase();
      if (!normalized) return false;
      if (allowProxyAll) return true;
      return allowedTargets.some(
        (domain) => normalized === domain || normalized.endsWith(`.${domain}`),
      );
    };

    let targetBase = "";

    if (targetHost) {
      // Use the host provided in the header (preferred)
      if (!isAllowedHost(targetHost)) {
        return new Response("Target host is not allowed.", { status: 403 });
      }
      targetBase = `https://${targetHost}`;
    } else {
      // Fallback: Guess based on path (legacy support)
      if (url.pathname.startsWith("/bot")) {
        targetBase = "https://api.telegram.org";
      } else if (
        url.pathname.startsWith("/api/webhooks") ||
        url.pathname.startsWith("/api/v")
      ) {
        targetBase = "https://discord.com";
      } else {
        return new Response(
          "Invalid request. 'x-target-host' header missing and target not recognized via path.",
          { status: 400 },
        );
      }
    }

    const targetUrl = targetBase + url.pathname + url.search;

    // Copy headers and remove internal/Cloudflare-specific ones
    const headers = new Headers(request.headers);
    headers.delete("cf-connecting-ip");
    headers.delete("cf-ray");
    headers.delete("cf-visitor");
    headers.delete("x-real-ip");
    headers.delete("x-target-host"); // Don't leak this to the target

    const modifiedRequest = new Request(targetUrl, {
      method: request.method,
      headers: headers,
      body: request.body,
      redirect: "follow",
    });

    try {
      const response = await fetch(modifiedRequest);

      // Special handling for Discord/Telegram which might return 403 on some CF IPs
      // If needed, you can add retry logic here.

      return response;
    } catch (e) {
      return new Response(`Proxy Error: ${e.message}`, { status: 502 });
    }
  },
};