feat(proxy): per-client keys with per-client usage accounting - #186
Open
Frozen666 wants to merge 1 commit into
Open
feat(proxy): per-client keys with per-client usage accounting#186Frozen666 wants to merge 1 commit into
Frozen666 wants to merge 1 commit into
Conversation
One shared proxy.apiKey means every consumer of a team proxy looks the same:
the per-account usage says WHAT was spent, never by WHOM. proxy.clientKeys
([{name, key}]) gives each consumer their own key; both auth gates (HTTP
x-api-key and CONNECT Proxy-Authorization) resolve which entry matched, and
the tokens each response reports are booked against that name — alongside the
existing per-account accounting, fed by the same response parsing.
Per-client counters show up under `clients` in /teamclaude/status and
`teamclaude status`, persist in the state file next to quota, and prefix
--activity-log lines with [name]. Key edits apply live via /teamclaude/reload.
The shared apiKey and the loopback exemption stay unattributed; a loopback
caller that DOES present a valid client key is attributed like any other.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7DVJAvCRWgtw1TvNgsq4b
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.
Motivation
With a single shared
proxy.apiKey, every consumer of the proxy looks the same: the per-account usage answers what was spent, but not by whom. When several people (or tools) share one proxy, there is no way to see which key holder consumed which share of the pooled quota.What this adds
proxy.clientKeys— optional per-client keys alongside the sharedproxy.apiKey:x-api-keygate and the CONNECTProxy-Authorizationgate (Bearer, or Basic with the key in either slot, so--proxy http://<key>@host:portworks per client).message_start/message_deltaand non-streamingusage) are booked against the matching entry'sname— same parsing, second dimension, alongside the untouched per-account accounting.clientsin/teamclaude/statusand as a Clients section inteamclaude status;--activity-loglines get a[name]prefix.POST /teamclaude/reload— both gates readconfig.proxyper request, matching howeventLogging/blockedModelsare read live.Design notes
apiKeyand the loopback exemption stay unattributed (client: null) rather than inventing an identity. A loopback caller that does present a valid client key is attributed like any other. Deployments wanting complete stats give every consumer an entry.forcedClient, for the same reason the pin does — no socket tagging. The map stays bounded by accounts × client keys, both operator-controlled.resolveConnectPinnow recognizes any configured key in the Basic username as auth-not-pin (previously only the shared key); it accepts both the legacy bare-string argument and the proxy config object, so existing callers are unaffected.connectAuthorizedis kept as a boolean back-compat wrapper.Tests
test/client-usage.test.js— 9 tests: tracker aggregation + additive restore with malformed entries;resolveClientAuthidentity mapping (incl. clientKeys-only configs and a clientKeys entry duplicating the shared key);resolveConnectAuthfor both Basic slots + Bearer + loopback semantics;resolveConnectPinback-compat; and end-to-end proxying against a mock upstream asserting tokens land on the right client for both SSE and JSON bodies while per-account totals are unchanged. Full suite passes, lint clean.🤖 Generated with Claude Code