feat: per-user API keys with authenticated provenance#24
Conversation
Replaces the single shared API key with a keystore: 'api_keys' block in
the credentials YAML mapping sha256-hex(token) -> {id: <contributor>}.
The legacy single 'api_key' still works (folds to id "owner") for
back-compat. Auth disabled only when no keys at all are configured
(dev mode preserved). Malformed keystore fails closed (server refuses
to start).
The auth middleware resolves the bearer token to a contributor id
(sha256 + constant lookup; never returns "unknown") and injects it into
the ASGI scope.
POST /events stamps the authenticated id into the event as 'created_by',
OVERWRITING any client-supplied value (anti-spoofing), persisted into
the durable queue line.
The graph write stamps 'created_by' WRITE-ONCE via ON CREATE SET on both
node-write paths (Session inline MERGE and the universal _NODE_MERGE_CYPHER),
never as part of the MERGE key — node identity/uniqueness constraints
unchanged. Adds a non-unique idx_session_created_by index.
Authenticate-and-stamp only (no roles in v1); workspace remains a client
label. Zero client change. Historical nodes keep null created_by.
Verified end-to-end against an isolated instance: a peer's lying
'created_by' is overwritten by the server (anti-spoof), created_by is
immutable across a second writer (write-once), and a bogus token gets 401
with nothing written. 1353 unit tests pass; ruff/pyright clean.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Review — per-user API keysSolid core. The security-critical pieces check out: the anti-spoof overwrite on
Blocking
Small / cheap
|
|
Heads-up: the completion work for this feature is now in #25, opened against |
|
Superseded by #25, now merged to main. #25 carries commit 7854776 (this work) plus the completion fixes from review — fail-closed empty keystore, structural write-once |
|
All changes from this PR are now in |
Per-User API Keys with Authenticated Provenance
What: Replaces the single shared API key model with a keystore-based system that assigns authenticated, spoof-proof contributor attribution to all graph writes.
Why: The previous single-key model left no way to distinguish which peer contributed which data — a data-integrity gap the council flagged (workspace is client-supplied, unchecked). This feature solves it by:
Design: Council-reviewed spec (docs/designs/per-user-api-keys.md, v2) with all must-fixes resolved: D1–D10 decisions locked, identity persisted through the async queue, fail-closed on auth, write-once graph semantics, and zero client change.
Proof: Verified end-to-end on an isolated test stack (never touching the live deployment or Marc's data):
created_by, server overwrites it ✓created_bystays unchanged ✓All 1353 unit tests pass; ruff/pyright clean.
Impact: Server-only change; zero client change. Historical nodes retain null
created_by. Single legacyapi_keystill works (maps to id 'owner') for back-compat. Auth disabled only when no keys configured (dev mode preserved).Next: A CLA bot comment is expected and fine. Please review and let me know if anything needs adjustment before merge.
🤖 Generated with Amplifier