Per-org routing via database name with composite user keys#361
Open
Per-org routing via database name with composite user keys#361
Conversation
Each org gets a unique hostname like {orgID}.warehouse.posthog.com.
The control plane extracts the org from TLS SNI, scopes auth to that
org, and routes directly to the org's worker pool. No global username
lookup — connections without valid SNI are rejected in multi-tenant mode.
Config store:
- OrgUser composite PK: (org_id, username) — same username allowed
in different orgs
- ValidateOrgUser(orgID, username, password) replaces ValidateUser
- FindAndValidateUser scans orgs for Flight SQL (no SNI available)
- Idempotent PK migration from single to composite key
Control plane:
- extractOrgFromSNI parses org from TLS ServerName
- handleConnection: SNI org required in multi-tenant mode
- StackForOrg(orgID) replaces StackForUser(username)
- Flight SQL: auth populates userOrg map, CreateSession reads it
Admin API:
- User endpoints org-scoped: /orgs/:id/users/:username
- Store methods take (orgID, username) composite key
Config: --warehouse-domain / DUCKGRES_WAREHOUSE_DOMAIN
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace SNI-based per-org routing with database-based routing. The PostgreSQL database parameter in the connection string identifies the org. User uniqueness is scoped to the database (org). Flow: client connects with dbname=myorg → auth scoped to that org → route to that org's worker pool. Remove: extractOrgFromSNI, --warehouse-domain, DUCKGRES_WAREHOUSE_DOMAIN. No wildcard DNS or TLS certificates needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Orgs are identified by a UUID (from PostHog), but users connect with a human-readable database name (e.g. dbname=acme-analytics). The database name maps to the org ID via a globally unique field on Org. - Org model: add DatabaseName field with unique index - Config store snapshot: DatabaseOrg map (database name → org ID) - ResolveDatabase(database) → org ID lookup - Connection handler: database → ResolveDatabase → org ID → auth → route - Provisioning API: accepts optional database_name, defaults to org ID - Seed SQL: local org gets database_name='duckgres' For now the mapping is 1:1 (one database per org). The schema supports N:1 (multiple databases per org) for future use. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-tenant mode now requires a database name to resolve the org. The test connects with dbname=duckgres which maps to the seeded local org. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GET /api/v1/database-name/check?name=foo returns {"name":"foo","available":true/false}.
Used by the PostHog frontend to validate database name uniqueness
before provisioning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
Orgs are identified by a UUID from PostHog (the org ID). Users connect using a human-readable database name that maps to the org. User uniqueness is scoped per org — the same username can exist in different orgs.
Connection flow:
Model:
Org.DatabaseName— globally unique, human-readable name users connect withOrgUsercomposite PK:(org_id, username)— same username allowed in different orgsSnapshot.DatabaseOrgmap: database name → org ID (1:1 now, N:1 later)Control plane:
ResolveDatabase(database)→ org ID lookup from snapshothandleConnection: database from startup message → resolve → org-scoped auth → route3D000("database does not exist")Provisioning API:
POST /api/v1/orgs/:id/provisionaccepts optionaldatabase_name(defaults to org ID)Admin API:
GET/PUT/DELETE /orgs/:id/users/:usernameConfig store:
ValidateOrgUser(orgID, username, password)— org-scoped authFindAndValidateUser(username, password)— scans all orgs (Flight SQL fallback)Test plan
go build -tags kubernetes .andgo vetpassdbname=mydb user=alice, verify auth scoped to org🤖 Generated with Claude Code