feat(auth): labelled API keys for per-key usage attribution
API-key requests were unattributable. The middleware stored
request.state.api_key but the request logger only read
request.state.identity (the session user), so every API-key call
logged as anonymous. The key value is never logged (correct), but
nothing derived from it was either — two keys were indistinguishable
in every log line, so "how much is this key being used" had no answer.
API_KEYS now accepts an optional label per entry:
API_KEYS="zach:<key>,daisy:<key>" # labelled
API_KEYS="<key>,<key>" # bare — still works
Each entry is either a bare key or a 'label:key' pair, split on the
first colon — the same encoding AUTH_BASIC_USERS uses for
'username:hash'. A bare key gets a derived 'key-<fingerprint>' label
(first 8 hex of sha256) so even unlabelled keys stay distinct in logs
without exposing the key. A key that itself contains a colon must be
given an explicit label.
Changes:
- api_key_auth: _load_api_keys returns a {key: label} dict instead of
a set; _is_valid_key becomes _match_key returning the matched label
(or None). On a valid request, request.state.api_key_label is set.
- logging_middleware: _identity_fields now also surfaces
api_key_label, and accumulates fields from both auth paths instead
of returning early — a request can carry a session identity, an API
key label, or neither.
- config: API_KEYS field description documents the label syntax.
Backward compatible: an existing flat comma-separated API_KEYS with no
colons keeps working unchanged; each key just gets a fingerprint label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>