From 2be9cbf2dfe640441abcd8a41b9f49fa578e8679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Mili=C4=87evi=C4=87?= Date: Fri, 19 Jun 2026 13:58:42 -0500 Subject: [PATCH] fix(connect-gpt): gateway open by default, enforce auth only when GATEWAY_API_KEY set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway is a demo/template showing hotels how to connect their own GPT, so it should work out-of-the-box with no config. Invert to: OPEN when no key is set; when GATEWAY_API_KEY is configured it is REQUIRED (timing-safe). Drops the GATEWAY_ALLOW_PUBLIC flag — setting a key is the single lockdown switch. Co-Authored-By: Claude Opus 4.8 --- tools/haip-connect-gpt/.env.example | 15 +++++++-------- tools/haip-connect-gpt/api/index.ts | 1 - tools/haip-connect-gpt/src/app.auth.test.ts | 9 +-------- tools/haip-connect-gpt/src/app.ts | 19 ++++++++----------- tools/haip-connect-gpt/src/server.ts | 1 - 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/tools/haip-connect-gpt/.env.example b/tools/haip-connect-gpt/.env.example index 121bc70..70f3fe0 100644 --- a/tools/haip-connect-gpt/.env.example +++ b/tools/haip-connect-gpt/.env.example @@ -13,15 +13,14 @@ PORT=8080 # deployment domain if left unset; set it to your production domain for a stable spec. PUBLIC_BASE_URL=http://localhost:8080 -# --- Caller authentication (REQUIRED unless GATEWAY_ALLOW_PUBLIC=true) --- -# Credential the ChatGPT Action must present (Authorization: Bearer , or -# x-api-key). The gateway holds HAIP's privileged Connect key, so leaving the -# gateway open lets anyone who finds the URL drive the Connect API. Set this here -# AND in the ChatGPT Action's Authentication (API Key / Bearer) config. +# --- Caller authentication (optional; OPEN demo when unset) --- +# This is a demo/template gateway: with no key set it is OPEN (the public demo so +# hotels can see how to connect their own GPT). To LOCK IT DOWN for a real +# deployment, set GATEWAY_API_KEY to a strong secret — then every action request +# must present it (Authorization: Bearer , or x-api-key), and you put the +# same value in the ChatGPT Action's Authentication (API Key / Bearer) config. +# The gateway holds HAIP's privileged Connect key, so set this in production. GATEWAY_API_KEY= -# Explicit opt-out to keep the action routes public (the unauthenticated demo). -# Leave unset/false in any real deployment. -GATEWAY_ALLOW_PUBLIC=false # --- Tool-call logging (Supabase project: haip-demo, direct Postgres) --- # Connection string for the haip_tool_calls table. Leave blank to disable diff --git a/tools/haip-connect-gpt/api/index.ts b/tools/haip-connect-gpt/api/index.ts index db7d0f0..f5ac209 100644 --- a/tools/haip-connect-gpt/api/index.ts +++ b/tools/haip-connect-gpt/api/index.ts @@ -34,7 +34,6 @@ const app = buildApp({ adapter: new HaipConnectAdapter({ baseUrl, apiKey }), publicBaseUrl, gatewayApiKey: process.env['GATEWAY_API_KEY'], - allowPublic: process.env['GATEWAY_ALLOW_PUBLIC'] === 'true', }); const ready = app.ready(); diff --git a/tools/haip-connect-gpt/src/app.auth.test.ts b/tools/haip-connect-gpt/src/app.auth.test.ts index f3bb491..4cfeea3 100644 --- a/tools/haip-connect-gpt/src/app.auth.test.ts +++ b/tools/haip-connect-gpt/src/app.auth.test.ts @@ -61,16 +61,9 @@ describe('gateway authentication', () => { await app.close(); }); - it('fails closed: no key configured and not explicitly public → 401', async () => { + it('is open when no key is configured (demo posture)', async () => { const app = buildApp({ adapter: stubAdapter(), publicBaseUrl: 'http://x' }); const res = await app.inject({ method: 'POST', url: '/hotels/search', payload: {} }); - expect(res.statusCode).toBe(401); - await app.close(); - }); - - it('explicit opt-out (allowPublic) keeps the demo open', async () => { - const app = buildApp({ adapter: stubAdapter(), publicBaseUrl: 'http://x', allowPublic: true }); - const res = await app.inject({ method: 'POST', url: '/hotels/search', payload: {} }); expect(res.statusCode).toBe(200); await app.close(); }); diff --git a/tools/haip-connect-gpt/src/app.ts b/tools/haip-connect-gpt/src/app.ts index 4771895..f41445e 100644 --- a/tools/haip-connect-gpt/src/app.ts +++ b/tools/haip-connect-gpt/src/app.ts @@ -31,11 +31,6 @@ export interface AppOptions { * internet who finds the URL can drive the Connect API. Configure via GATEWAY_API_KEY. */ gatewayApiKey?: string; - /** - * Explicit opt-out that leaves the action routes public (the unauthenticated - * demo). Mirrors HAIP_ALLOW_INSECURE — secure-by-default otherwise. - */ - allowPublic?: boolean; } /** Public routes that never require a caller credential. */ @@ -64,16 +59,18 @@ export function buildApp(opts: AppOptions): FastifyInstance { app.register(cors, { origin: true }); - // Require a caller credential on every non-public route. The gateway proxies - // requests with HAIP's privileged upstream `x-api-key`, so an unauthenticated - // gateway is an open door to the Connect API. Fail-closed: if no key is - // configured and `allowPublic` was not explicitly set, refuse action routes. + // Caller auth on the action routes. This is a demo/template gateway, so it is + // OPEN by default — the public demo shows hotels how to connect their own GPT + // with zero setup. A real deployment locks it down simply by setting + // GATEWAY_API_KEY: when a key is configured it is REQUIRED (Authorization: + // Bearer or x-api-key), validated timing-safe. The gateway holds HAIP's + // upstream Connect key, so anyone enabling this in production should set a key. app.addHook('onRequest', async (req, reply) => { + if (!opts.gatewayApiKey) return; // no key configured → open (demo posture) const path = (req.url.split('?')[0] ?? req.url).replace(/\/+$/, '') || '/'; if (PUBLIC_PATHS.has(path)) return; - if (opts.allowPublic) return; const provided = extractCredential(req); - if (!opts.gatewayApiKey || !provided || !timingSafeEqualStr(provided, opts.gatewayApiKey)) { + if (!provided || !timingSafeEqualStr(provided, opts.gatewayApiKey)) { reply.code(401).send({ error: 'unauthorized', message: 'A valid gateway credential is required.' }); } }); diff --git a/tools/haip-connect-gpt/src/server.ts b/tools/haip-connect-gpt/src/server.ts index bdd1925..a06dbaf 100644 --- a/tools/haip-connect-gpt/src/server.ts +++ b/tools/haip-connect-gpt/src/server.ts @@ -27,7 +27,6 @@ const app = buildApp({ adapter, publicBaseUrl, gatewayApiKey: process.env['GATEWAY_API_KEY'], - allowPublic: process.env['GATEWAY_ALLOW_PUBLIC'] === 'true', }); app