Skip to content

Instantly share code, notes, and snippets.

@dexit
Created November 6, 2025 10:37
Show Gist options
  • Select an option

  • Save dexit/da0a02be25f2ae05deec1e6e52277766 to your computer and use it in GitHub Desktop.

Select an option

Save dexit/da0a02be25f2ae05deec1e6e52277766 to your computer and use it in GitHub Desktop.
cf-cors-proxy-requests-postcodes.js
// --- CONFIGURATION VIA ENVIRONMENT VARIABLES ---
// In your Worker's Settings -> Variables, set the following:
//
// 1. ALLOWED_ORIGINS (Whitelist for target URLs for the proxy)
// Value: *.example.com, api.another.com, my-domain.net
//
// 2. PROXY_ENDPOINT (The path the proxy will run on)
// Value: /corsproxy/
//
// 3. SEARCH_PARAM (The query parameter for the proxy, e.g., 'url' or 'apiurl')
// Value: apiurl
const HEADERS_TO_STRIP = [
'content-security-policy',
'x-frame-options',
'x-content-type-options',
];
// --- PROXY HELPER FUNCTIONS (Unchanged) ---
function isUrlAllowed(targetUrl, allowedOriginsCommaSeparated) {
if (!allowedOriginsCommaSeparated) return false;
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 handleProxyOptions(request) {
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"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) {
return new Response(null, {
headers: { ...corsHeaders, "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers") },
});
} else {
return new Response(null, { headers: { Allow: "GET, HEAD, POST, PUT, DELETE, OPTIONS" } });
}
}
async function handleProxyRequest(request, env) {
const url = new URL(request.url);
const definedSearchParam = env.SEARCH_PARAM || 'apiurl';
const allowedOrigins = env.ALLOWED_ORIGINS || 'pathwaygroup.co.uk,pathwayskillszone.ac.uk,cloudflare.com,workers.dev';
const targetUrl = url.searchParams.get(definedSearchParam);
if (!targetUrl) {
return new Response(`Error: "${definedSearchParam}" query parameter is missing.`, { status: 400 });
}
if (!isUrlAllowed(targetUrl, allowedOrigins)) {
return new Response(`Error: The requested URL is not in the whitelist.`, { status: 403 });
}
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);
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;
}
// --- REVISED: POSTCODE LOOKUP WITH FALLBACK LOGIC ---
// /corsproxy/?apiurl=https://pathwaygroup.co.uk/dev/hubhook/hspics/src/postcodes/v2/api/asf?postcode=CV213FE
//
/**
* Handles requests to the /postcodes/ endpoint with sequential fallback logic.
* @param {Request} request The incoming request object.
*/
async function handlePostcodeRequest(request) {
const { pathname } = new URL(request.url);
const postcode = decodeURIComponent(pathname.split('/')[2] || "").trim();
const corsJsonResponse = (data, status) => {
return new Response(JSON.stringify(data), {
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 's-maxage=86400' // Cache successful responses for 1 day
},
});
};
if (!postcode) {
return corsJsonResponse({ error: "Postcode missing. Use the format /postcodes/YourPostcode" }, 400);
}
const encodedPostcode = encodeURIComponent(postcode);
const fetchHeaders = { 'User-Agent': 'Cloudflare-Worker-Postcode-Aggregator/1.0' };
// --- Step 1: Try active postcodes from postcodes.io ---
const activeResponse = await fetch(`https://api.postcodes.io/postcodes/${encodedPostcode}`, { headers: fetchHeaders });
if (activeResponse.ok) {
const data = await activeResponse.json();
return corsJsonResponse({ postcode, source: 'postcodes.io (active)', data: data.result }, 200);
}
// --- Step 2: If not found (404), try terminated postcodes ---
if (activeResponse.status === 404) {
const terminatedResponse = await fetch(`https://api.postcodes.io/terminated_postcodes/${encodedPostcode}`, { headers: fetchHeaders });
if (terminatedResponse.ok) {
const data = await terminatedResponse.json();
return corsJsonResponse({ postcode, source: 'postcodes.io (terminated)', data: data.result }, 200);
}
}
// --- Step 3: If both postcodes.io lookups fail, fall back to OpenStreetMap ---
const osmResponse = await fetch(`https://nominatim.openstreetmap.org/search?q=${encodedPostcode}&format=jsonv2&limit=5`, { headers: fetchHeaders });
if (osmResponse.ok) {
const data = await osmResponse.json();
if (data && data.length > 0) {
return corsJsonResponse({ postcode, source: 'openstreetmap', data: data }, 200);
}
}
// --- Final Step: If all sources fail, return a 404 ---
return corsJsonResponse({ error: "Postcode not found in any available source." }, 404);
}
// --- MAIN ROUTER (Unchanged) ---
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const proxyEndpoint = env.PROXY_ENDPOINT || "/corsproxy/";
// Route 1: CORS Proxy
if (url.pathname.startsWith(proxyEndpoint)) {
if (request.method === "OPTIONS") {
return handleProxyOptions(request);
} else if (["GET", "HEAD", "POST", "PUT", "DELETE"].includes(request.method)) {
return handleProxyRequest(request, env);
} else {
return new Response(null, { status: 405, statusText: "Method Not Allowed" });
}
}
// Route 2: Postcode Lookup
else if (url.pathname.startsWith("/postcodes/")) {
if (request.method === "GET" || request.method === "OPTIONS") {
return handlePostcodeRequest(request);
} else {
return new Response(null, { status: 405, statusText: "Method Not Allowed" });
}
}
// Default Route
else {
return new Response("Worker is running. Use the /corsproxy/ or /postcodes/ endpoints.", {
headers: { "Content-Type": "text/plain" },
});
}
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment