-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Overview
The two foundational code changes that everything else depends on:
- Make Supabase auth cookies work across all
*.taskflow.comsubdomains - Teach the middleware to identify the current tenant from the
Hostheader instead of the URL path
Context
Currently all routing is path-based (/portal/[tenantSlug]). After this issue, the tenant is always known from the subdomain, and the middleware injects it as a header for downstream use. This is the critical enabler for all other SaaS issues.
Changes
lib/supabase/server.ts
Set cookie domain on all get/set/remove operations in createServerClient:
const cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN; // ".taskflow.com" in prod, undefined in dev
// In set/remove cookie handlers:
cookieStore.set({ name, value, ...options, ...(cookieDomain ? { domain: cookieDomain } : {}) });lib/supabase/middleware.ts
Same cookie domain injection in the updateSession() cookie handlers.
lib/supabase/client.ts
Browser client handles cookies automatically via Supabase SSR — no change needed. The server-side cookie with .taskflow.com domain is readable by all subdomains.
proxy.ts
Add subdomain extraction and header injection:
// New utility function:
function getTenantSlugFromHost(host: string): string | null {
const baseDomain = process.env.NEXT_PUBLIC_BASE_DOMAIN ?? "localhost";
if (!host.includes(baseDomain)) return null; // localhost or unknown host
const subdomain = host.split(".")[0];
if (subdomain === "www" || subdomain === baseDomain) return null;
return subdomain; // e.g. "acme" from "acme.taskflow.com"
}- In the proxy handler:
const tenantSlug = getTenantSlugFromHost(request.headers.get("host") ?? ""); - Inject into every outgoing response/request:
requestHeaders.set("x-tenant-slug", tenantSlug ?? ""); - Replace all
pathname.split("/")[2]slug extraction withtenantSlugfrom above - Admin route guard: user's
app_metadata.tenant_slugmust matchtenantSlugfrom subdomain - Portal route guard: same check — user's
tenant_slugmust match subdomain slug - Remove
ALLOW_REGISTRATIONguard (registration will be open after Phase 6c: Portal auth — Google OAuth #4)
New env vars (add to .env.example)
NEXT_PUBLIC_BASE_DOMAIN=taskflow.com # base domain (no protocol)
NEXT_PUBLIC_COOKIE_DOMAIN=.taskflow.com # leading dot for wildcard
Leave both unset for local dev (cookies scope to localhost, no subdomain needed).
Key Files
lib/supabase/server.tslib/supabase/middleware.tsproxy.ts.env.example
Testing
- In dev: subdomain routing won't apply (localhost has no subdomains). Test via
lvh.metrick or add127.0.0.1 acme.lvh.meto/etc/hostsand setNEXT_PUBLIC_BASE_DOMAIN=lvh.me. - Unit test:
getTenantSlugFromHost()with various inputs (subdomain, base domain, localhost, www) - Verify session cookie is readable on a different subdomain (integration test)
Depends On
Issue #87 (infra) — specifically the NEXT_PUBLIC_COOKIE_DOMAIN env var must be set in Vercel.