Multi-Tenancy¶
Overview¶
SaaS-Courier implements Row Level Security (RLS) for tenant isolation in PostgreSQL.
Tenant Resolution¶
Every authenticated request resolves:
- user_id - from JWT sub claim
- tenant_id - from JWT tenant_id claim
- role - from JWT or database
Important: tenant_id always comes from the JWT, never from request parameters.
Middleware extracts tenant from token:
async def extract_tenant(request: Request) -> UUID:
token = extract_token_from_header(request)
payload = decode_jwt(token)
return UUID(payload["tenant_id"])
Row Level Security (RLS)¶
All tenant-scoped tables enforce RLS:
ENABLE ROW LEVEL SECURITY;
FORCE ROW LEVEL SECURITY;
CREATE POLICY mandatory_tenant_policy ON shipments
USING (tenant_id = current_setting('app.current_tenant')::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Context Manager¶
Use the TenantContext to prevent session leakage:
async def get_shipments(conn, tenant_id: UUID):
async with TenantContext(tenant_id):
result = await conn.execute(
"SELECT * FROM shipments WHERE tenant_id = current_setting('app.current_tenant')::uuid"
)
return result
Cross-Tenant Prevention¶
- Queries always filter by tenant_id
- Ownership verification before any operation
- RLS blocks any cross-tenant access at database level
async def get_shipment(shipment_id: UUID, tenant_id: UUID):
shipment = await repo.get_by_id(shipment_id)
if shipment.tenant_id != tenant_id:
raise PermissionError("Access denied")
return shipment
Multi-Domain Configuration¶
Per-tenant custom domains (loaded from DB):
ROUNDAWAY_EXPRESS_DOMAIN=roundaway-express.com
ROUNDAWAY_EXPRESS_TRACKING_URL=https://track.roundaway-express.com
Rate Limits by Tier¶
| Tier | Rate Limit | Features |
|---|---|---|
| STARTER | 100/min | Core |
| GROWTH | 500/min | + Analytics |
| SCALE | 1000/min | + API Access |
| ENTERPRISE | Custom | + White-label |
API Keys¶
For SCALE and ENTERPRISE tiers: - Generated per tenant - Stored as hash - Enable ERP/e-commerce integrations