feat: route Microsoft API calls through Cloudflare Worker to avoid IP blocks

- Add cloudflare-worker/worker.js: edge proxy forwarding requests to allowed
  Microsoft hosts from Cloudflare distributed IPs. Set CF_WORKER_URL env var
  to activate; omit to keep direct-to-Microsoft behaviour (self-hosters unaffected).
- Wrap all outbound Microsoft URLs in proxyURL() in main.go so the Worker
  sits transparently in front of every session and download-link request.
- Fix Back button navigating to external referrer when user lands directly
  on a product URL (#5) — use window.history.state?.idx to detect whether
  there is prior in-app history before calling navigate(-1).

Closes #5, Closes #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shekhar Vaidya 2026-05-18 11:39:47 +05:30
parent a8f9ba1da1
commit 8b036e94ba
4 changed files with 97 additions and 7 deletions

View file

@ -8,6 +8,7 @@ import (
"log"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"sync"
@ -34,8 +35,37 @@ var (
sessionCache = make(map[string]SessionEntry)
cacheMutex sync.RWMutex
SESSION_TTL = 15 * time.Minute
workerSecret = os.Getenv("CF_WORKER_SECRET")
)
func setWorkerSecret(req *http.Request) {
if workerSecret != "" {
req.Header.Set("X-Worker-Secret", workerSecret)
}
}
// proxyURL rewrites a Microsoft URL to go through the CF Worker when CF_WORKER_URL is set.
// The Worker receives the original host via ?host= and forwards the request from Cloudflare's edge.
func proxyURL(msURL string) string {
workerBase := os.Getenv("CF_WORKER_URL")
if workerBase == "" {
return msURL
}
parsed, err := url.Parse(msURL)
if err != nil {
return msURL
}
workerParsed, err := url.Parse(workerBase)
if err != nil {
return msURL
}
q := parsed.Query()
q.Set("host", parsed.Hostname())
workerParsed.Path = parsed.Path
workerParsed.RawQuery = q.Encode()
return workerParsed.String()
}
// Map product ID to referer URL
func getReferer(productId string) string {
id, err := strconv.Atoi(productId)
@ -60,8 +90,9 @@ func setupSession() (string, error) {
q1 := url.Values{}
q1.Set("org_id", ORG_ID)
q1.Set("session_id", sessionID)
req1, _ := http.NewRequest("GET", "https://vlscppe.microsoft.com/tags?"+q1.Encode(), nil)
req1, _ := http.NewRequest("GET", proxyURL("https://vlscppe.microsoft.com/tags?"+q1.Encode()), nil)
req1.Header.Set("User-Agent", UA)
setWorkerSecret(req1)
client.Do(req1) // Ignore errors intentionally
// Step 2: Fetch tracking JS
@ -69,8 +100,9 @@ func setupSession() (string, error) {
q2.Set("instanceId", CUSTOMER_ID)
q2.Set("PageId", "si")
q2.Set("session_id", sessionID)
req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil)
req2, _ := http.NewRequest("GET", proxyURL("https://ov-df.microsoft.com/mdt.js?"+q2.Encode()), nil)
req2.Header.Set("User-Agent", UA)
setWorkerSecret(req2)
resp2, err := client.Do(req2)
if err != nil {
return sessionID, nil // Proceed anyway
@ -100,10 +132,11 @@ func setupSession() (string, error) {
q3.Set("w", wVal)
q3.Set("mdt", fmt.Sprintf("%d", mdt))
q3.Set("rticks", rtVal)
fpURL := "https://ov-df.microsoft.com/?" + q3.Encode()
fpURL := proxyURL("https://ov-df.microsoft.com/?" + q3.Encode())
req3, _ := http.NewRequest("GET", fpURL, nil)
req3.Header.Set("User-Agent", UA)
setWorkerSecret(req3)
client.Do(req3)
}
@ -190,7 +223,7 @@ func handleSkuInfo(w http.ResponseWriter, r *http.Request) {
skuQ.Set("friendlyFileName", "undefined")
skuQ.Set("Locale", LOCALE)
skuQ.Set("sessionID", sessionID)
reqURL := "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + skuQ.Encode()
reqURL := proxyURL("https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + skuQ.Encode())
client := &http.Client{Timeout: 15 * time.Second}
var finalData map[string]interface{}
@ -204,6 +237,7 @@ func handleSkuInfo(w http.ResponseWriter, r *http.Request) {
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
resp, err := client.Do(req)
if err != nil {
@ -303,13 +337,14 @@ func handleProxy(w http.ResponseWriter, r *http.Request) {
warmupQ.Set("friendlyFileName", "undefined")
warmupQ.Set("Locale", LOCALE)
warmupQ.Set("sessionID", sessionID)
warmupURL := "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + warmupQ.Encode()
warmupURL := proxyURL("https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + warmupQ.Encode())
client := &http.Client{Timeout: 10 * time.Second}
req, _ := http.NewRequest("GET", warmupURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
client.Do(req)
cacheMutex.Lock()
@ -324,13 +359,14 @@ func handleProxy(w http.ResponseWriter, r *http.Request) {
proxyQ.Set("friendlyFileName", "undefined")
proxyQ.Set("Locale", LOCALE)
proxyQ.Set("sessionID", sessionID)
reqURL := "https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?" + proxyQ.Encode()
reqURL := proxyURL("https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?" + proxyQ.Encode())
client := &http.Client{Timeout: 15 * time.Second}
req, _ := http.NewRequest("GET", reqURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
resp, err := client.Do(req)
if err != nil {

View file

@ -0,0 +1,54 @@
const ALLOWED_HOSTS = [
"www.microsoft.com",
"vlscppe.microsoft.com",
"ov-df.microsoft.com",
];
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (env.CF_WORKER_SECRET) {
const secret = request.headers.get("X-Worker-Secret");
if (secret !== env.CF_WORKER_SECRET) {
return new Response(JSON.stringify({ error: "Forbidden" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
}
// Target host comes from the ?host= param; path+search are forwarded as-is
const targetHost = url.searchParams.get("host");
if (!targetHost || !ALLOWED_HOSTS.includes(targetHost)) {
return new Response(JSON.stringify({ error: "Invalid or missing host parameter" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
url.searchParams.delete("host");
url.hostname = targetHost;
url.protocol = "https:";
url.port = "";
const upstreamRequest = new Request(url.toString(), {
method: request.method,
headers: {
"User-Agent": request.headers.get("User-Agent") || "Mozilla/5.0",
"Referer": request.headers.get("Referer") || "",
"Accept": request.headers.get("Accept") || "application/json",
},
});
const upstream = await fetch(upstreamRequest);
return new Response(upstream.body, {
status: upstream.status,
headers: {
"Content-Type": upstream.headers.get("Content-Type") || "application/json",
"Access-Control-Allow-Origin": "*",
},
});
},
};

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -213,7 +213,7 @@ export default function ProductDetailPage() {
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
{/* Back */}
<button
onClick={() => navigate(-1)}
onClick={() => navigate(window.history.state?.idx ? -1 : '/')}
className="flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 mb-8 transition-colors"
>
<ArrowLeft size={15} />