Jun 26 MT-1 phase
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
app/tenancy/middleware.py
|
||||
-------------------------
|
||||
Wires tenant resolution into the Flask request lifecycle.
|
||||
|
||||
`init_tenancy(app)` registers a single app-level before_request handler that:
|
||||
* always clears g.tenant / g.tenant_engine (so downstream code can rely on them)
|
||||
* does NOTHING further when MULTI_TENANT_ENABLED is False → today's behaviour
|
||||
* bypasses static + configured exempt paths (health checks)
|
||||
* otherwise resolves the Host header to a tenant and selects its engine
|
||||
* returns a 404 page for an unknown / unverified / suspended host
|
||||
|
||||
Flask-SQLAlchemy already removes the scoped session on app-context teardown,
|
||||
so each request rebinds via RoutingSession.get_bind against the fresh
|
||||
g.tenant_engine — no teardown handler is needed here.
|
||||
"""
|
||||
|
||||
from flask import g, request, current_app, Response
|
||||
|
||||
from app.tenancy.resolver import resolve_tenant
|
||||
from app.tenancy.engine_cache import get_tenant_engine
|
||||
|
||||
_UNKNOWN_TENANT_PAGE = (
|
||||
"<!doctype html><html lang='en'><head><meta charset='utf-8'>"
|
||||
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
|
||||
"<title>Workspace not found</title>"
|
||||
"<style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;"
|
||||
"background:#f6f7f9;color:#1f2937;display:flex;min-height:100vh;margin:0;"
|
||||
"align-items:center;justify-content:center;text-align:center}"
|
||||
".card{background:#fff;padding:2.5rem 3rem;border-radius:12px;"
|
||||
"box-shadow:0 1px 4px rgba(0,0,0,.08);max-width:30rem}"
|
||||
"h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:.25rem 0;color:#6b7280}"
|
||||
"</style></head><body><div class='card'>"
|
||||
"<h1>Workspace not found</h1>"
|
||||
"<p>This address isn’t linked to an active JQC workspace.</p>"
|
||||
"<p>Check the URL, or contact your administrator.</p>"
|
||||
"</div></body></html>"
|
||||
)
|
||||
|
||||
|
||||
def _is_exempt(path):
|
||||
if path.startswith('/static/'):
|
||||
return True
|
||||
for prefix in current_app.config.get('MULTI_TENANT_EXEMPT_PATHS', []):
|
||||
if prefix and path.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def init_tenancy(app):
|
||||
@app.before_request
|
||||
def _resolve_tenant():
|
||||
# Default state — referenced safely by downstream code regardless of mode.
|
||||
g.tenant = None
|
||||
g.tenant_engine = None
|
||||
|
||||
if not current_app.config.get('MULTI_TENANT_ENABLED', False):
|
||||
return # inert: default database serves everything (single-tenant)
|
||||
|
||||
if _is_exempt(request.path):
|
||||
return
|
||||
|
||||
host = (request.host or '').split(':')[0].strip().lower()
|
||||
tenant = resolve_tenant(host)
|
||||
if tenant is None:
|
||||
return Response(_UNKNOWN_TENANT_PAGE, status=404, mimetype='text/html')
|
||||
|
||||
g.tenant = tenant
|
||||
g.tenant_engine = get_tenant_engine(tenant)
|
||||
Reference in New Issue
Block a user