Skip to content

[Enhancement] Add service API-key auth and rate limiting to protect the Gemini quota #9

Description

@zeemscript

What's Happening

Every endpoint in main.py is completely open. The service is deployed publicly at https://dnb-ai.onrender.com (referenced in the CORS list, line 28, and the keep-awake workflow), and anyone who finds the URL can:

  • Burn the project's Gemini quota and budget with unlimited POST /chat calls — each one costs real money and there is no throttle of any kind
  • Read other users' conversations: chat_id is client-supplied (line 114) and the response returns the full history (lines 148-168), so anyone who knows or guesses a session ID gets the entire conversation
  • Delete anyone's session via DELETE /chat/{chat_id}

CORS (lines 21-33) only restrains browsers — curl and scripts ignore it entirely. This service is meant to be called by the DeenBridge backend/frontend, not the open internet, but nothing enforces that.

Where to Find This

  • main.py — CORS middleware (lines 21-33), /chat (lines 108-174), /chat/{chat_id} delete (lines 176-187), /ping (lines 102-105)
  • render.yamlenvVars block where the new secret must be declared (sync: false, like GEMINI_API_KEY)
  • .github/workflows/keep-render-awake.yml — the pinger must keep working (leave its target unauthenticated or pass the header)

What We Want

  1. Shared-secret authentication: require an X-API-Key header on /chat and /chat/{chat_id}, validated against a SERVICE_API_KEY environment variable, returning 401 when missing/wrong. Implement with FastAPI's APIKeyHeader security scheme and a dependency, so the requirement shows up in /docs
  2. Leave the health/ping route unauthenticated so Render health checks and the keep-awake workflow keep working
  3. Per-client rate limiting on /chat (e.g. slowapi): a sensible default like 20 requests/minute per API key (falling back to client IP), returning 429 with a Retry-After header
  4. If SERVICE_API_KEY is unset, fail loudly at startup in production but allow an explicit opt-out for local development (e.g. AUTH_DISABLED=true), so contributors without secrets can still run the service
  5. Document the new header and env vars in the README, and add SERVICE_API_KEY to render.yaml

Technical Context

  • slowapi is the standard FastAPI wrapper around limits; its default in-memory storage is fine for the current single-instance deployment — note in code comments that a shared Redis backend is needed if the session-persistence issue lands multi-instance support
  • Render sits behind a proxy: use X-Forwarded-For (Render sets it) rather than the socket peer address for IP-based limiting
  • Use secrets.compare_digest for the key comparison
  • Comparing keys per-request is cheap; do not add token/JWT infrastructure — a single shared secret between the DeenBridge backend and this service is the right size for this architecture

Acceptance Criteria

  • POST /chat without X-API-Key (or with a wrong key) returns 401; with the correct key it works exactly as before
  • The 21st request within a minute from the same key returns 429 with Retry-After
  • Health/ping remains reachable without a key; the keep-awake workflow still succeeds
  • Local development without any secret is possible via the documented opt-out
  • README and render.yaml document SERVICE_API_KEY

Metadata

Metadata

Labels

GrantFox OSSPart of the GrantFox OSS programMaybe RewardedPotential reward for completionOfficial Campaign | FWC26GrantFox official campaign FWC26complexity:highMaps to Drips Wave High tier (200 pts)enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions