Enforce tenant isolation on verified audit firm domains

This commit is contained in:
A R R R Associates
2026-07-10 15:04:54 +05:30
parent 18cfb0b8a7
commit 2e87013563
3 changed files with 127 additions and 15 deletions
+62
View File
@@ -33,6 +33,54 @@ SAFE_POST_LOGIN_REDIRECTS = {
"/employee/dashboard",
}
# Verified active audit-firm domains are tenant authentication boundaries.
# Marketplace and consultant domain behaviour remains unchanged.
TENANT_BOUND_DOMAIN_TYPES = {"audit_firm_domain", "audit_firm_subdomain"}
def _bound_domain_tenant_id(request: Request) -> int | None:
"""Return the trusted tenant id bound to the current audit-firm domain.
DomainResolverMiddleware only marks exact, active and verified mappings as
resolved. The additional checks here make the authentication boundary
explicit and safe if the middleware evolves later.
"""
if not bool(getattr(request.state, "domain_resolved", False)):
return None
if not bool(getattr(request.state, "domain_is_verified", False)):
return None
if (getattr(request.state, "domain_status", None) or "").strip().lower() != "active":
return None
if (getattr(request.state, "domain_type", None) or "").strip() not in TENANT_BOUND_DOMAIN_TYPES:
return None
tenant_id = getattr(request.state, "domain_tenant_id", None)
try:
return int(tenant_id) if tenant_id not in (None, "", 0, "0") else None
except (TypeError, ValueError):
return None
def _clear_login_session(request: Request) -> None:
"""Remove authentication/context state without disturbing CSRF/session middleware."""
for key in (
SESSION_USER_ID_KEY,
SESSION_LOGIN_AT_KEY,
"user_email",
"tenant_id",
"branch_id",
"tenant_code",
"branch_code",
"active_tenant_id",
"active_branch_id",
"active_tenant_code",
"active_branch_code",
"active_financial_year",
"must_change_password",
"post_login_redirect",
"otp_verified",
):
request.session.pop(key, None)
def _consume_safe_post_login_redirect(request: Request) -> str | None:
value = request.session.pop(PENDING_POST_LOGIN_REDIRECT_KEY, None)
@@ -447,6 +495,20 @@ def login_submit(
permissions = _user_permissions(db, user_id)
bs = _get_branch_security_policy(db, user)
# A verified active audit-firm domain is a hard tenant boundary.
# System Admin retains the existing platform-support capability, but all
# tenant users must belong to the tenant mapped to this hostname.
bound_tenant_id = _bound_domain_tenant_id(request)
is_system_admin = "System Admin" in set(roles or [])
user_tenant_id = int(tenant_id) if tenant_id not in (None, "", 0, "0") else None
if bound_tenant_id is not None and not is_system_admin and user_tenant_id != bound_tenant_id:
_clear_login_session(request)
return _render_login(
request,
flash="This account does not belong to the firm associated with this domain. Please use your firm's login URL.",
status_code=403,
)
request.session[SESSION_USER_ID_KEY] = user_id
request.session[SESSION_LOGIN_AT_KEY] = now.isoformat()
request.session["user_email"] = user_email