diff --git a/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts b/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts index db354ab7720..37b680c2f22 100644 --- a/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts +++ b/packages/app/src/cli/utilities/app/http-reverse-proxy.test.ts @@ -55,6 +55,34 @@ describe.sequential.each(each)('http-reverse-proxy for %s', (protocol) => { }) }) + test('responds to CORS preflight OPTIONS with default headers', {retry: 2}, async ({ports, servers}) => { + const response = await fetch(`${protocol}://localhost:${ports.proxyPort}/path1/test`, { + method: 'OPTIONS', + headers: { + Origin: 'https://extensions.shopifycdn.com', + 'Access-Control-Request-Method': 'GET', + 'Access-Control-Request-Headers': 'Authorization', + }, + agent, + }) + expect(response.status).toBe(204) + expect(response.headers.get('access-control-allow-origin')).toBe('https://extensions.shopifycdn.com') + expect(response.headers.get('access-control-allow-methods')).toBe('GET') + expect(response.headers.get('access-control-allow-headers')).toBe('Authorization') + expect(response.headers.get('access-control-max-age')).toBe('86400') + }) + + test('responds to CORS preflight OPTIONS with defaults when no request headers', {retry: 2}, async ({ports, servers}) => { + const response = await fetch(`${protocol}://localhost:${ports.proxyPort}/path1/test`, { + method: 'OPTIONS', + agent, + }) + expect(response.status).toBe(204) + expect(response.headers.get('access-control-allow-origin')).toBe('*') + expect(response.headers.get('access-control-allow-methods')).toBe('GET, POST, PUT, DELETE, PATCH, OPTIONS') + expect(response.headers.get('access-control-allow-headers')).toBe('Content-Type, Authorization') + }) + test('closes the server when aborted', {retry: 2}, async ({ports, servers}) => { servers.abortController.abort() // Try the assertion immediately, and if it fails, wait and retry diff --git a/packages/app/src/cli/utilities/app/http-reverse-proxy.ts b/packages/app/src/cli/utilities/app/http-reverse-proxy.ts index 9304bab8e84..86c1e4397ec 100644 --- a/packages/app/src/cli/utilities/app/http-reverse-proxy.ts +++ b/packages/app/src/cli/utilities/app/http-reverse-proxy.ts @@ -70,6 +70,18 @@ function getProxyServerRequestListener( return function (req, res) { const target = match(rules, req) if (target) { + // Handle CORS preflight requests directly + // The proxy does not forward OPTIONS reliably, so we respond here + // using the headers requested by the client. + if (req.method === 'OPTIONS') { + res.writeHead(204, { + 'Access-Control-Allow-Origin': req.headers['origin'] ?? '*', + 'Access-Control-Allow-Methods': req.headers['access-control-request-method'] ?? 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Headers': req.headers['access-control-request-headers'] ?? 'Content-Type, Authorization', + 'Access-Control-Max-Age': '86400', + }) + return res.end() + } return proxy.web(req, res, {target}, (err) => { useConcurrentOutputContext({outputPrefix: 'proxy', stripAnsi: false}, () => { const lastError = isAggregateError(err) ? err.errors[err.errors.length - 1] : undefined diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56e2b589cde..663d840f340 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5917,12 +5917,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}