Skip to content

feat(api-key): client-facing API key management and per-key usage - #56

Open
yinxulai wants to merge 1 commit into
mainfrom
feat/client-api-keys
Open

yinxulai wants to merge 1 commit into
mainfrom
feat/client-api-keys

Conversation

@yinxulai

@yinxulai yinxulai commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Closes #27.

What

The proxy can now issue client-facing API keys downward and validate them at the /v1/* entry, plus report usage grouped by key.

Today the proxy validates no caller identity. Anyone who can reach /v1/* on an exposed listen host can consume the user's upstream quota — the only real boundary is where the proxy listens. This adds a gate that is independent of the listen address.

How

Backend (packages/core)

  • New config-DB table api_keys (metadata only) + api-key-store with CRUD, application-layer name uniqueness, and soft delete (rows retained so request_logs.apiKeyId history still resolves).
  • Plaintext lives only in the host secret store; the DB keeps a keyReference. Plaintext is returned once on create/rotate and never via list/get/update.
  • api-key-auth resolves identity by reverse lookup — constant-time compare (timingSafeEqual) of the presented secret against the active set — cached by the config-read generation, so any key mutation auto-invalidates it. Disabled / expired / deleted keys are rejected.
  • New /api/api-key/{list,get,create,update,delete,rotate} management routes. Create cleans up the secret on failure so no orphan plaintext is left.
  • New request_logs.apiKeyId column + idx_request_logs_api_key_time, threaded through request context, logging types, collector, and session.
  • analytics-store.getApiKeyStats groups usage by key and always keeps the anonymous (null) group so the unauthenticated share stays visible.

Contracts (packages/contracts)

  • ApiKey / CreatedApiKey / ApiKeyStat schemas; apiKeyId on the request log schemas; apiKeyStats on the analytics summary; apiKeyAuthEnabled setting.

Proxy

  • Auth gate runs before protocol endpoint matching, controlled by the runtime apiKeyAuthEnabled setting — default off to preserve zero-config onboarding. Rejected requests return 401.

Console (packages/console)

  • API keys page (list / create / edit / enable / disable / rotate / delete), a one-time secret dialog, a client API-key auth card in runtime settings, and a per-key usage table. Both i18n catalogs updated.

Docs (apps/docs/specs)

  • security-privacy.md gains a "客户端 API Key" section; roadmap.md §7 item marked done; data-model.md documents the new table, column, index, and the cross-DB (id-only) reference.

Tests

  • api-key-store.test.ts (CRUD, uniqueness, soft delete, enable/expiry)
  • api-key-auth.test.ts (extraction, bind/reject, disabled/expired/deleted, cache invalidation)
  • api-keys.test.ts management routes (plaintext-once, rotate invalidates old, soft delete + secret removal, no orphan on rejected create)
  • analytics-store.test.ts getApiKeyStats block (per-key split, anonymous always kept, ordering)

Notes

  • No cross-DB FK: api_keys lives in the config DB, request_logs.apiKeyId in the data DB; the link is an id-only logical reference (dangling ids allowed, name resolved live).
  • The baseline migrations are regenerated in place (single-baseline policy); DATABASE_SCHEMA_VERSIONS is unchanged.

Adds client API keys that the proxy can issue downward and validate at the
/v1/* entry, plus per-key usage statistics. Motivated by issue #27: previously
the proxy validated no caller identity, so anyone reaching /v1/* on an exposed
listen host could consume the user's upstream quota.

Backend (packages/core):
- New config-DB table `api_keys` (metadata only) and companion `api-key-store`
  with CRUD, name uniqueness at the application layer, and soft delete.
- Plaintext lives only in the host secret store; the DB keeps a `keyReference`.
  Plaintext is returned once on create/rotate and never via list/get/update.
- `api-key-auth` resolves caller identity by reverse lookup (constant-time
  compare against active secrets), cached by the config-read generation so any
  key mutation invalidates it. Disabled/expired/deleted keys are rejected.
- New `/api/api-key/{list,get,create,update,delete,rotate}` management routes;
  create cleans up the secret on failure so no orphan plaintext remains.
- New `request_logs.apiKeyId` column + `idx_request_logs_api_key_time`;
  threaded through request context, logging types, collector, and session.
- `analytics-store.getApiKeyStats` groups usage by key, always keeping the
  anonymous (null) group; exposed via the analytics summary.

Contracts:
- ApiKey / CreatedApiKey / ApiKeyStat schemas; `apiKeyId` on request log
  schemas; `apiKeyStats` on the analytics summary; `apiKeyAuthEnabled` setting.

Console:
- API keys page (list/create/edit/enable/disable/rotate/delete), a one-time
  secret dialog, a client API-key auth card in runtime settings, and a
  per-key usage table. Both i18n catalogs updated.

Proxy:
- Auth gate runs before protocol endpoint matching and is controlled by the
  runtime `apiKeyAuthEnabled` setting (default off to preserve zero-config
  onboarding). Rejected requests return 401.

Docs (apps/docs/specs): security-privacy.md gains a "客户端 API Key" section,
roadmap §7 item marked done, and data-model.md documents the new table, column,
index, and cross-DB (id-only) reference.

Tests: api-key-store, api-key-auth, api-keys routes, and getApiKeyStats.

Refs #27
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
osw-apis 2a847b4 Oct 04 2026, 01:40 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
2a847b4 2026-10-04T01:41:36.899Z View logs ↗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

本地 API Key(向下签发给调用方的凭证)要不要做

1 participant