Skip to main content

D16 — Email-first tenant discovery (central) (RESOLVED)

Architecture decision record, relocated verbatim from the retired single-file docs/tenancy/ decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.

Residual security risk (tracked): this endpoint is unauthenticated and returns tenant uuid + name for a probed email, so it is an email-enumeration / tenant-identity disclosure vector — accepted as the cost of email-first discovery, mitigated by Turnstile + throttle:10,1. Options to harden are tracked in docs/tracking/security-hardening.md.

The client SPA does not know its tenant up front. Following the major-SaaS pattern, login is email-first: the SPA submits the email to a CENTRAL endpoint that resolves which tenant(s) the client user belongs to, then sends the chosen tenant as X-Tenant for login.

  • POST /api/tenant-discovery (routes/api.php, TenantDiscoveryController) runs in the CENTRAL / BYPASSRLS context (wrapped in tenancy()->central()), so it can search client_users across all tenants — an RLS-scoped query could not. Returns { "tenants": [{ "id", "name" }] } (a list, since an email may exist in several tenants — the SPA shows a picker; one match → auto-proceed).
  • Central API routing is now enabled (bootstrap/app.phpapi: routes/api.php, prefix api). Central cross-tenant endpoints live in routes/api.php; tenant-scoped endpoints stay in routes/tenant.php (identified + RLS).
  • SECURITY: the endpoint inherently discloses email→tenant membership (true of any email-first flow). It is strictly rate-limited (throttle:10,1). See follow-up in §3.

← Decision log index