From cdb43c165346a66c4ea3b59a89b36083418483fb Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Fri, 18 Sep 2026 19:25:00 +0300 Subject: [PATCH] Preserve JSON content negotiation through both proxy routes --- .github/workflows/qa.yml | 1 + ops/timeweb/api-proxy.php | 2 +- public/api/index.php | 2 +- tests/proxy-http.php | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/proxy-http.php diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index cb93866..bd915bf 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -17,5 +17,6 @@ jobs: - run: npm audit --audit-level=high - run: npm run check:deploy - run: php -l public/api/index.php && php -l ops/timeweb/api-proxy.php + - run: php tests/proxy-http.php app && php tests/proxy-http.php api - run: npx playwright install --with-deps chromium webkit - run: npm run test:e2e diff --git a/ops/timeweb/api-proxy.php b/ops/timeweb/api-proxy.php index 11fb0e1..fef0715 100644 --- a/ops/timeweb/api-proxy.php +++ b/ops/timeweb/api-proxy.php @@ -32,7 +32,7 @@ const ALLOWED_ORIGINS = [ ]; const FORWARD_REQUEST_HEADERS = [ - 'authorization', 'apikey', 'content-type', 'prefer', 'range', + 'authorization', 'apikey', 'content-type', 'accept', 'prefer', 'range', 'x-client-info', 'x-supabase-api-version', 'accept-profile', 'content-profile', 'x-upsert', 'cache-control', ]; diff --git a/public/api/index.php b/public/api/index.php index 11fb0e1..fef0715 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -32,7 +32,7 @@ const ALLOWED_ORIGINS = [ ]; const FORWARD_REQUEST_HEADERS = [ - 'authorization', 'apikey', 'content-type', 'prefer', 'range', + 'authorization', 'apikey', 'content-type', 'accept', 'prefer', 'range', 'x-client-info', 'x-supabase-api-version', 'accept-profile', 'content-profile', 'x-upsert', 'cache-control', ]; diff --git a/tests/proxy-http.php b/tests/proxy-http.php new file mode 100644 index 0000000..5d1df2e --- /dev/null +++ b/tests/proxy-http.php @@ -0,0 +1,35 @@ + $url]; } +function curl_setopt_array($ch, $options) { $ch->options = $options; return true; } +function curl_exec($ch) { + if ($ch->url !== 'https://usfjwhztqoopzzfmfbis.supabase.co/rest/v1/rpc/sun_fetch_app_state?select=revision') { + throw new \RuntimeException('Proxy did not preserve the RPC projection query'); + } + $headers = $ch->options[\CURLOPT_HTTPHEADER]; + foreach (['accept: application/vnd.pgrst.object+json', 'authorization: Bearer test-token', 'apikey: test-key', 'content-type: application/json'] as $header) { + if (!in_array($header, $headers, true)) throw new \RuntimeException('Required header lost: ' . explode(':', $header)[0]); + } + if ($ch->options[\CURLOPT_CUSTOMREQUEST] !== 'POST' || !$ch->options[\CURLOPT_SSL_VERIFYPEER]) throw new \RuntimeException('Method/TLS changed'); + $ch->responseHeaders = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"; + return $ch->responseHeaders . '{"revision":7}'; +} +function curl_getinfo($ch, $option) { return $option === \CURLINFO_HEADER_SIZE ? strlen($ch->responseHeaders) : 200; } +function curl_close($ch) {} + +$app = ($argv[1] ?? 'app') === 'app'; +$_SERVER['HTTP_HOST'] = $app ? 'app.caterium.ru' : 'api.caterium.ru'; +$_SERVER['REQUEST_URI'] = $app ? '/api/index.php?__caterium_path=%2Frest%2Fv1%2Frpc%2Fsun_fetch_app_state&select=revision' : '/rest/v1/rpc/sun_fetch_app_state?select=revision'; +$_SERVER['REQUEST_METHOD'] = 'POST'; +$_SERVER['HTTP_ACCEPT'] = 'application/vnd.pgrst.object+json'; +$_SERVER['HTTP_AUTHORIZATION'] = 'Bearer test-token'; +$_SERVER['HTTP_APIKEY'] = 'test-key'; +$_SERVER['CONTENT_TYPE'] = 'application/json'; +$_GET['__caterium_path'] = '/rest/v1/rpc/sun_fetch_app_state'; +ob_start(); +eval('namespace CateriumProxyTest;' . substr(file_get_contents(__DIR__ . '/../public/api/index.php'), 5)); +$body = ob_get_clean(); +if (json_decode($body, true) !== ['revision' => 7]) throw new \RuntimeException('RPC response changed'); +echo 'PASS proxy HTTP contract: ' . ($app ? 'same-origin' : 'dedicated API') . "\n";