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 });
}
},
};
|