Skip to content

Commit 0473e8a

Browse files
sng-asyncfuncclaude
andcommitted
feat(deploy): Cloudflare Worker proxies /api/codex to a clean-IP box backend
OpenAI's Cloudflare challenges datacenter IPs (Workers get 403 on chatgpt.com/backend-api; residential/box IPs get 200 — verified). So the live demo now runs the directRunner backend on an ascii.dev 'box' VM (clean IP, always-on via ttlSeconds:null) exposed at a public HTTPS URL, and the Cloudflare Worker reverse-proxies /api/codex/* to it. The browser only ever talks to codexauth.sharenow.today, so the HttpOnly session cookie round-trips same-origin. Verified live end-to-end: SPA serves, /session + /login/start proxy to the box and return a real device code; the box IP reaches chatgpt.com (200) so output actually streams. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 884901e commit 0473e8a

2 files changed

Lines changed: 34 additions & 33 deletions

File tree

deploy/cloudflare/worker.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/**
2-
* Cloudflare Worker that hosts the whole CodexAuth demo:
3-
* - /api/codex/* → the contract handler + directRunner (pure fetch, no CLI),
4-
* with session/token state persisted to KV
5-
* - everything else → the React SPA from the ASSETS binding
2+
* Cloudflare Worker for the CodexAuth demo.
63
*
7-
* This is the serverless deploy: no Node binary, no persistent process. Built on
8-
* the same codex-auth/backend the Express/Next adapters use.
4+
* - /api/codex/* → reverse-proxied to the backend running on a "box" VM
5+
* (a clean residential-ish IP). The directRunner there can
6+
* reach chatgpt.com/backend-api, which a Worker's datacenter
7+
* IP cannot (OpenAI's Cloudflare challenges it). The browser
8+
* only ever talks to codexauth.sharenow.today — same-origin,
9+
* so the HttpOnly session cookie round-trips normally.
10+
* - everything else → the React SPA from the ASSETS binding.
11+
*
12+
* CODEX_BACKEND_ORIGIN (var) is the box's public HTTPS origin.
913
*/
10-
import { handleCodexRequestKV } from "../../src/backend/cloudflare/kvSessionStore.js";
11-
import { directRunner } from "../../src/backend/direct/index.js";
12-
import type { KVNamespace, Fetcher } from "@cloudflare/workers-types";
14+
import type { Fetcher } from "@cloudflare/workers-types";
1315

1416
interface Env {
1517
ASSETS: Fetcher;
16-
CODEX_SESSIONS: KVNamespace;
17-
COOKIE_SECRET: string;
18-
CODEX_MODEL?: string;
18+
CODEX_BACKEND_ORIGIN: string;
1919
}
2020

2121
const BASE_PATH = "/api/codex";
@@ -25,22 +25,29 @@ export default {
2525
const url = new URL(request.url);
2626

2727
if (url.pathname.startsWith(BASE_PATH)) {
28-
if (!env.COOKIE_SECRET || env.COOKIE_SECRET.length < 16) {
29-
return new Response("COOKIE_SECRET is not configured", { status: 500 });
30-
}
31-
return handleCodexRequestKV(request, {
32-
kv: env.CODEX_SESSIONS,
33-
cookieSecret: env.COOKIE_SECRET,
34-
basePath: BASE_PATH,
35-
// Force gpt-5.5: it's the model the ChatGPT-account responses backend
36-
// accepts. Account-aware /models discovery is often Cloudflare-blocked
37-
// from a Worker's datacenter IP, and the generic fallbacks (gpt-5-codex,
38-
// gpt-5) are rejected — verified live.
39-
runner: directRunner({ models: [env.CODEX_MODEL ?? "gpt-5.5"] }),
28+
const backend = env.CODEX_BACKEND_ORIGIN?.replace(/\/+$/, "");
29+
if (!backend) return new Response("CODEX_BACKEND_ORIGIN not set", { status: 500 });
30+
31+
const target = new URL(backend);
32+
target.pathname = url.pathname;
33+
target.search = url.search;
34+
35+
// Forward method, headers (incl. cookie), and body. Preserve streaming both
36+
// ways (NDJSON for /run/stream).
37+
const proxied = new Request(target.toString(), {
38+
method: request.method,
39+
headers: request.headers,
40+
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
41+
redirect: "manual",
42+
});
43+
const resp = await fetch(proxied);
44+
return new Response(resp.body, {
45+
status: resp.status,
46+
statusText: resp.statusText,
47+
headers: resp.headers,
4048
});
4149
}
4250

43-
// Everything else is the static SPA.
4451
return env.ASSETS.fetch(request);
4552
},
4653
};

deploy/cloudflare/wrangler.jsonc

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,21 @@
33
"name": "codexauth",
44
"main": "worker.ts",
55
"compatibility_date": "2025-06-01",
6-
"compatibility_flags": ["nodejs_compat"],
76
"workers_dev": false,
87
"assets": {
98
"directory": "./public",
109
"binding": "ASSETS",
1110
"not_found_handling": "single-page-application",
1211
"run_worker_first": ["/api/*"]
1312
},
14-
"kv_namespaces": [
15-
{
16-
"binding": "CODEX_SESSIONS",
17-
"id": "18d05b5f9a9e470eb14e69370f15a9ff"
18-
}
19-
],
2013
"routes": [
2114
{
2215
"pattern": "codexauth.sharenow.today",
2316
"custom_domain": true
2417
}
2518
],
2619
"vars": {
27-
"CODEX_MODEL": "gpt-5.5"
20+
// The box VM's public HTTPS origin (clean IP that can reach chatgpt.com).
21+
"CODEX_BACKEND_ORIGIN": "https://surah-waxer-ploidy-3000.on.ascii.dev"
2822
}
2923
}

0 commit comments

Comments
 (0)