Repository navigation
Conversation
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
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
osw-apis | 2a847b4 | Oct 04 2026, 01:40 AM |
🚀 Deploying Preview to Cloudflare 🚀Preview Deployments by commit
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)api_keys(metadata only) +api-key-storewith CRUD, application-layer name uniqueness, and soft delete (rows retained sorequest_logs.apiKeyIdhistory still resolves).keyReference. Plaintext is returned once on create/rotate and never vialist/get/update.api-key-authresolves 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./api/api-key/{list,get,create,update,delete,rotate}management routes. Create cleans up the secret on failure so no orphan plaintext is left.request_logs.apiKeyIdcolumn +idx_request_logs_api_key_time, threaded through request context, logging types, collector, and session.analytics-store.getApiKeyStatsgroups usage by key and always keeps the anonymous (null) group so the unauthenticated share stays visible.Contracts (
packages/contracts)ApiKey/CreatedApiKey/ApiKeyStatschemas;apiKeyIdon the request log schemas;apiKeyStatson the analytics summary;apiKeyAuthEnabledsetting.Proxy
apiKeyAuthEnabledsetting — default off to preserve zero-config onboarding. Rejected requests return401.Console (
packages/console)Docs (
apps/docs/specs)security-privacy.mdgains a "客户端 API Key" section;roadmap.md§7 item marked done;data-model.mddocuments 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.tsmanagement routes (plaintext-once, rotate invalidates old, soft delete + secret removal, no orphan on rejected create)analytics-store.test.tsgetApiKeyStatsblock (per-key split, anonymous always kept, ordering)Notes
api_keyslives in the config DB,request_logs.apiKeyIdin the data DB; the link is an id-only logical reference (dangling ids allowed, name resolved live).DATABASE_SCHEMA_VERSIONSis unchanged.