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