Created
November 4, 2025 13:02
-
-
Save dexit/eb76d31d1e07d7468d21f6faaec30db4 to your computer and use it in GitHub Desktop.
CF Worker Cors Proxy Preflight HEaders Allowed whitelist domains with wildcard support and endpoint url control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // --- CONFIGURATION VIA ENVIRONMENT VARIABLES --- | |
| // In your Worker's Settings -> Variables, set the following: | |
| // | |
| // 1. ALLOWED_ORIGINS (Whitelist for target URLs) | |
| // Value: *.example.com, api.another.com, my-domain.net | |
| // | |
| // 2. PROXY_ENDPOINT (The path the proxy will run on) | |
| // Value: /corsproxy/ | |
| // A list of headers to remove from the origin's response. | |
| const HEADERS_TO_STRIP = [ | |
| 'content-security-policy', | |
| 'x-frame-options', | |
| 'x-content-type-options', | |
| ]; | |
| /** | |
| * Checks if a given URL's hostname is allowed based on a whitelist. | |
| * @param {string} targetUrl The URL whose hostname needs to be checked. | |
| * @param {string} allowedOriginsCommaSeparated A comma-separated string of allowed domains. | |
| * @returns {boolean} True if the URL is allowed, false otherwise. | |
| */ | |
| function isUrlAllowed(targetUrl, allowedOriginsCommaSeparated) { | |
| if (!allowedOriginsCommaSeparated) return false; // Deny if not configured | |
| const whitelist = allowedOriginsCommaSeparated.split(',').map(domain => domain.trim()); | |
| const hostname = new URL(targetUrl).hostname; | |
| for (const pattern of whitelist) { | |
| if (pattern === '*') return true; | |
| if (pattern.startsWith('*.')) { | |
| const domain = pattern.substring(2); | |
| if (hostname.endsWith('.' + domain) || hostname === domain) return true; | |
| } else { | |
| if (hostname === pattern) return true; | |
| } | |
| } | |
| return false; | |
| } | |
| async function handleOptions(request) { | |
| const corsHeaders = { | |
| "Access-Control-Allow-Origin": "*", // More permissive for preflight | |
| "Access-Control-Allow-Methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS", | |
| "Access-Control-Max-Age": "86400", | |
| }; | |
| if ( | |
| request.headers.get("Origin") !== null && | |
| request.headers.get("Access-Control-Request-Method") !== null && | |
| request.headers.get("Access-Control-Request-Headers") !== null | |
| ) { | |
| // Handle CORS preflight requests. | |
| return new Response(null, { | |
| headers: { | |
| ...corsHeaders, | |
| "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers"), | |
| }, | |
| }); | |
| } else { | |
| // Handle standard OPTIONS request. | |
| return new Response(null, { | |
| headers: { Allow: "GET, HEAD, POST, PUT, DELETE, OPTIONS" }, | |
| }); | |
| } | |
| } | |
| async function handleRequest(request, allowedOrigins) { | |
| const url = new URL(request.url); | |
| const targetUrl = url.searchParams.get("apiurl"); | |
| if (!targetUrl) { | |
| return new Response('Error: "apiurl" query parameter is missing.', { status: 400 }); | |
| } | |
| // --- Whitelist Check --- | |
| if (!isUrlAllowed(targetUrl, allowedOrigins)) { | |
| return new Response(`Error: The requested URL is not in the whitelist.`, { status: 403 }); | |
| } | |
| // Rewrite request to point to the target URL. | |
| const forwardedRequest = new Request(targetUrl, request); | |
| forwardedRequest.headers.set("Origin", new URL(targetUrl).origin); | |
| let response = await fetch(forwardedRequest); | |
| response = new Response(response.body, response); | |
| // Set secure CORS headers on the final response. | |
| response.headers.set("Access-Control-Allow-Origin", url.origin); | |
| response.headers.append("Vary", "Origin"); | |
| HEADERS_TO_STRIP.forEach(header => { | |
| response.headers.delete(header); | |
| }); | |
| return response; | |
| } | |
| export default { | |
| async fetch(request, env, ctx) { | |
| const url = new URL(request.url); | |
| const proxyEndpoint = env.PROXY_ENDPOINT || "/corsproxy/"; // Default to /corsproxy/ | |
| if (url.pathname.startsWith(proxyEndpoint)) { | |
| if (request.method === "OPTIONS") { | |
| return handleOptions(request); | |
| } else if (["GET", "HEAD", "POST", "PUT", "DELETE"].includes(request.method)) { | |
| return handleRequest(request, env.ALLOWED_ORIGINS || "pathwaygroup.co.uk,pathwayskillzone.ac.uk"); | |
| } else { | |
| return new Response(null, { status: 405, statusText: "Method Not Allowed" }); | |
| } | |
| } else { | |
| // Serve a demo page for any other path | |
| return new Response("This worker is running. Use the configured proxy endpoint to make requests.", { | |
| headers: { "Content-Type": "text/plain" }, | |
| }); | |
| } | |
| }, | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment