Skip to content

feat(extensions): add StaticKeysTenantExtension — env-configured per-user API keys with per-schema isolation - #3675

Open
rafaelkallis wants to merge 6 commits into
vectorize-io:mainfrom
rafaelkallis:feature/multi-key-tenant-extension
Open

feat(extensions): add StaticKeysTenantExtension — env-configured per-user API keys with per-schema isolation#3675
rafaelkallis wants to merge 6 commits into
vectorize-io:mainfrom
rafaelkallis:feature/multi-key-tenant-extension

Conversation

@rafaelkallis

Copy link
Copy Markdown

Summary

Adds a built-in TenantExtension mapping static API keys (from env) to per-user PostgreSQL schemas. Fills the gap between ApiKeyTenantExtension (single shared key) and SupabaseTenantExtension (external IdP): fully self-hosted multi-user memory isolation with no users table and no new services.

Closes #3674

Configuration

HINDSIGHT_API_TENANT_EXTENSION=hindsight_api.extensions.builtin.multi_key_tenant:StaticKeysTenantExtension
HINDSIGHT_API_TENANT_USERS=user1:key1,user1:key2,user2:key3   # required; multiple keys may map to one user
HINDSIGHT_API_TENANT_SCHEMA_PREFIX=user                       # optional, default "user" → user_<user_id> schemas
HINDSIGHT_API_TENANT_MCP_AUTH_DISABLED=true                   # optional

Behavior

  • Authorization: Bearer <key> → authenticated as the mapped user
  • Per-user schema {prefix}_{user_id} provisioned lazily on first access and cached; provisioning failure raises AuthenticationError (not cached)
  • Unknown/missing key → 401 (AuthenticationError)
  • Constant-time key comparison (hmac.compare_digest)
  • authenticate() sets context.tenant_id / context.api_key_id for usage metering
  • list_tenants() returns all configured users (with tenant_id) so the worker and maintenance sweep per-user schemas
  • authenticate_mcp delegates to authenticate (or skips when mcp_auth_disabled — parity with ApiKeyTenantExtension)
  • Multiple keys may map to the same user (same schema)
  • /health stays 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

  • 30 new tests: init validation (missing/invalid config, SQL-injection user ids, multi-key-per-user), auth (valid/invalid/missing, metering fields, schema provision-once + failure-not-cached), MCP (delegate/disabled), list_tenants, loader integration, and engine-level enforcement (retain/recall auth)
  • pytest tests/test_multi_key_tenant.py → 30 passed; ruff + ty clean
  • Dogfooded in a live self-hosted deployment (Docker Compose, external Postgres):
    • 401/200 auth matrix verified (no key / wrong key → 401; per-user keys → 200)
    • per-user schemas provisioned lazily (user_rafael, user_test → created on first access)
    • memory isolation verified: retain as user A, recall as user B → empty; recall as user A → hit
    • worker polls both tenant schemas and runs consolidation per user schema

Compatibility

Fully opt-in. Existing modes (default no-auth, ApiKeyTenantExtension, SupabaseTenantExtension) are unchanged. No migration, no schema changes to existing deployments. Only activates when HINDSIGHT_API_TENANT_EXTENSION points at it.

Related

Open questions / notes for reviewers

  • mcp_auth_disabled=true falls back to the base schema (get_config().database_schema), matching ApiKeyTenantExtension parity — happy to change to refuse-to-start for multi-user if preferred
  • Keys are plaintext in env (operator-managed; rotation = edit + restart). A hashed variant could be a follow-up
  • self._key_to_user / self._users use plain dicts — this mirrors the existing builtin style (supabase_tenant.py); can switch to dataclasses if preferred

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: StaticKeysTenantExtension — env-configured per-user API keys with per-schema isolation

1 participant