feat(extensions): add StaticKeysTenantExtension — env-configured per-user API keys with per-schema isolation - #3675
Open
rafaelkallis wants to merge 6 commits into
Conversation
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.
Summary
Adds a built-in
TenantExtensionmapping static API keys (from env) to per-user PostgreSQL schemas. Fills the gap betweenApiKeyTenantExtension(single shared key) andSupabaseTenantExtension(external IdP): fully self-hosted multi-user memory isolation with no users table and no new services.Closes #3674
Configuration
Behavior
Authorization: Bearer <key>→ authenticated as the mapped user{prefix}_{user_id}provisioned lazily on first access and cached; provisioning failure raisesAuthenticationError(not cached)AuthenticationError)hmac.compare_digest)authenticate()setscontext.tenant_id/context.api_key_idfor usage meteringlist_tenants()returns all configured users (withtenant_id) so the worker and maintenance sweep per-user schemasauthenticate_mcpdelegates toauthenticate(or skips whenmcp_auth_disabled— parity withApiKeyTenantExtension)/healthstays public (unauthenticated)Changes
hindsight-api-slim/hindsight_api/extensions/builtin/multi_key_tenant.py(new)hindsight-api-slim/hindsight_api/extensions/builtin/__init__.py(export)hindsight-api-slim/hindsight_api/extensions/__init__.py(export)hindsight-api-slim/tests/test_multi_key_tenant.py(new, 30 tests)hindsight-docs/docs/developer/extensions.md(docs)Testing
list_tenants, loader integration, and engine-level enforcement (retain/recall auth)pytest tests/test_multi_key_tenant.py→ 30 passed; ruff + ty cleanuser_rafael,user_test→ created on first access)Compatibility
Fully opt-in. Existing modes (default no-auth,
ApiKeyTenantExtension,SupabaseTenantExtension) are unchanged. No migration, no schema changes to existing deployments. Only activates whenHINDSIGHT_API_TENANT_EXTENSIONpoints at it.Related
Open questions / notes for reviewers
mcp_auth_disabled=truefalls back to the base schema (get_config().database_schema), matchingApiKeyTenantExtensionparity — happy to change to refuse-to-start for multi-user if preferredself._key_to_user/self._usersuse plain dicts — this mirrors the existing builtin style (supabase_tenant.py); can switch to dataclasses if preferred