Skip to content

Commit e99ee0f

Browse files
jerryhenleyclaude
andauthored
Add Supabase tenant extension as built-in (#267)
Move the Supabase tenant extension into the hindsight-api package so users can enable it with just an environment variable — no file copying or Docker image modifications needed. Key improvements over the original submission: - JWKS-based local JWT verification (no network call per request) with automatic fallback to /auth/v1/user for legacy HS256 projects - Service key is now optional (only needed for HS256 or health checks) - UUID validation on user IDs before schema name construction - Schema prefix validation against Postgres identifier rules - Key rotation handling with automatic JWKS cache refresh - Proper logging via Python logging module - Tenant extension lifecycle hooks (on_startup/on_shutdown) wired into the server lifespan - Public tenant_extension property on MemoryEngine - 54 unit tests covering both verification modes, cache behavior, error paths, and the extension loader - README updated to reflect JWKS-first architecture Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c568094 commit e99ee0f

File tree

9 files changed

+4349
-3250
lines changed

9 files changed

+4349
-3250
lines changed

hindsight-api/hindsight_api/api/http.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,12 @@ async def lifespan(app: FastAPI):
14321432
poller_task = asyncio.create_task(poller.run())
14331433
logging.info(f"Worker poller started (worker_id={worker_id})")
14341434

1435+
# Call tenant extension startup hook (e.g. JWKS fetch for Supabase)
1436+
tenant_extension = memory.tenant_extension
1437+
if tenant_extension:
1438+
await tenant_extension.on_startup()
1439+
logging.info("Tenant extension started")
1440+
14351441
# Call HTTP extension startup hook
14361442
if http_extension:
14371443
await http_extension.on_startup()
@@ -1450,6 +1456,11 @@ async def lifespan(app: FastAPI):
14501456
pass
14511457
logging.info("Worker poller stopped")
14521458

1459+
# Call tenant extension shutdown hook
1460+
if tenant_extension:
1461+
await tenant_extension.on_shutdown()
1462+
logging.info("Tenant extension stopped")
1463+
14531464
# Call HTTP extension shutdown hook
14541465
if http_extension:
14551466
await http_extension.on_shutdown()

hindsight-api/hindsight_api/engine/memory_engine.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,11 @@ def __init__(
467467
tenant_extension = DefaultTenantExtension(config={})
468468
self._tenant_extension = tenant_extension
469469

470+
@property
471+
def tenant_extension(self) -> "TenantExtension | None":
472+
"""The configured tenant extension, if any."""
473+
return self._tenant_extension
474+
470475
async def _validate_operation(self, validation_coro) -> None:
471476
"""
472477
Run validation if an operation validator is configured.

hindsight-api/hindsight_api/extensions/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"""
1717

1818
from hindsight_api.extensions.base import Extension
19-
from hindsight_api.extensions.builtin import ApiKeyTenantExtension
19+
from hindsight_api.extensions.builtin import ApiKeyTenantExtension, SupabaseTenantExtension
2020
from hindsight_api.extensions.context import DefaultExtensionContext, ExtensionContext
2121
from hindsight_api.extensions.http import HttpExtension
2222
from hindsight_api.extensions.loader import load_extension
@@ -80,6 +80,7 @@
8080
"MentalModelRefreshResult",
8181
# Tenant/Auth
8282
"ApiKeyTenantExtension",
83+
"SupabaseTenantExtension",
8384
"AuthenticationError",
8485
"RequestContext",
8586
"Tenant",

hindsight-api/hindsight_api/extensions/builtin/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
77
Available built-in extensions:
88
- ApiKeyTenantExtension: Simple API key validation with public schema
9+
- SupabaseTenantExtension: Supabase JWT validation with per-user schema isolation
910
1011
Example usage:
1112
HINDSIGHT_API_TENANT_EXTENSION=hindsight_api.extensions.builtin.tenant:ApiKeyTenantExtension
13+
HINDSIGHT_API_TENANT_EXTENSION=hindsight_api.extensions.builtin.supabase_tenant:SupabaseTenantExtension
1214
"""
1315

16+
from hindsight_api.extensions.builtin.supabase_tenant import SupabaseTenantExtension
1417
from hindsight_api.extensions.builtin.tenant import ApiKeyTenantExtension
1518

1619
__all__ = [
1720
"ApiKeyTenantExtension",
21+
"SupabaseTenantExtension",
1822
]

0 commit comments

Comments
 (0)