Skip to content

Per-org routing via database name with composite user keys#361

Open
EDsCODE wants to merge 12 commits intomainfrom
eric/sni-per-org-routing
Open

Per-org routing via database name with composite user keys#361
EDsCODE wants to merge 12 commits intomainfrom
eric/sni-per-org-routing

Conversation

@EDsCODE
Copy link
Contributor

@EDsCODE EDsCODE commented Mar 25, 2026

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:

psql "host=duckgres.example.com dbname=acme-analytics user=alice"
  → ResolveDatabase("acme-analytics") → org UUID
  → ValidateOrgUser(orgUUID, "alice", password)
  → StackForOrg(orgUUID) → worker pool

Model:

  • Org.DatabaseName — globally unique, human-readable name users connect with
  • OrgUser composite PK: (org_id, username) — same username allowed in different orgs
  • Snapshot.DatabaseOrg map: database name → org ID (1:1 now, N:1 later)

Control plane:

  • ResolveDatabase(database) → org ID lookup from snapshot
  • handleConnection: database from startup message → resolve → org-scoped auth → route
  • Unknown database returns SQLSTATE 3D000 ("database does not exist")

Provisioning API:

  • POST /api/v1/orgs/:id/provision accepts optional database_name (defaults to org ID)

Admin API:

  • User endpoints org-scoped: GET/PUT/DELETE /orgs/:id/users/:username

Config store:

  • ValidateOrgUser(orgID, username, password) — org-scoped auth
  • FindAndValidateUser(username, password) — scans all orgs (Flight SQL fallback)
  • Idempotent PK migration from single-column to composite key

Test plan

  • go build -tags kubernetes . and go vet pass
  • Config store + provisioning tests pass
  • E2E: connect with dbname=mydb user=alice, verify auth scoped to org
  • E2E: same username in two orgs with different databases

🤖 Generated with Claude Code

EDsCODE and others added 3 commits March 25, 2026 15:23
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>
@EDsCODE EDsCODE marked this pull request as ready for review March 25, 2026 23:11
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>
@EDsCODE EDsCODE changed the title Add SNI-based per-org routing with composite user keys Scope users per-org via database name with composite user keys Mar 26, 2026
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>
@EDsCODE EDsCODE changed the title Scope users per-org via database name with composite user keys Per-org routing via database name with composite user keys Mar 26, 2026
EDsCODE and others added 7 commits March 26, 2026 11:38
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>
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.

1 participant