feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
Some checks failed
CI / pytest (push) Waiting to run
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

- Fix IPv6 host parsing with _strip_port() utility
- Remove dangerous StorePlatform→Store.subdomain silent fallback
- Close storefront gate bypass when frontend_type is None
- Add custom subdomain management UI and API for stores
- Add domain health diagnostic tool
- Convert db.add() in loops to db.add_all() (24 PERF-006 fixes)
- Add tests for all new functionality (18 subdomain service tests)
- Add .github templates for validator compliance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 18:13:01 +01:00
parent 07fab01f6a
commit 540205402f
38 changed files with 1827 additions and 134 deletions

View File

@@ -0,0 +1,232 @@
# app/modules/dev_tools/services/domain_health_service.py
"""
Domain Health Check Service
Simulates the middleware resolution pipeline for every active access method
(custom subdomain, default subdomain, custom domain, path-based) to verify
they resolve to the expected store.
"""
import logging
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
def run_domain_health_check(db: Session) -> dict:
"""
Check all active store access methods by simulating middleware resolution:
1. StorePlatform custom subdomains (acme-rewards.rewardflow.lu)
2. Default subdomains per platform (acme.omsflow.lu)
3. StoreDomain custom domains (wizatech.shop)
4. Path-based store routes (/storefront/{subdomain}/, /store/{subdomain}/)
Returns:
dict with keys: total, passed, failed, details (list of entry dicts)
"""
from app.modules.tenancy.models import Platform, Store, StoreDomain
from app.modules.tenancy.models.store_platform import StorePlatform
from middleware.store_context import StoreContextManager
details: list[dict] = []
# Pre-fetch all active stores, platforms, and memberships
active_stores = (
db.query(Store).filter(Store.is_active.is_(True)).all()
)
store_by_id: dict[int, Store] = {s.id: s for s in active_stores}
all_platforms = (
db.query(Platform).filter(Platform.is_active.is_(True)).all()
)
platform_by_id: dict[int, Platform] = {p.id: p for p in all_platforms}
all_store_platforms = (
db.query(StorePlatform)
.filter(StorePlatform.is_active.is_(True))
.all()
)
# Build store_id → list of (StorePlatform, Platform) for reuse
store_memberships: dict[int, list[tuple]] = {}
for sp in all_store_platforms:
platform = platform_by_id.get(sp.platform_id)
if platform:
store_memberships.setdefault(sp.store_id, []).append((sp, platform))
# ── 1. Custom subdomains (StorePlatform.custom_subdomain) ──
# e.g. acme-rewards.rewardflow.lu
for sp in all_store_platforms:
if not sp.custom_subdomain:
continue
expected_store = store_by_id.get(sp.store_id)
platform = platform_by_id.get(sp.platform_id)
if not expected_store or not platform:
continue
context = {
"detection_method": "subdomain",
"subdomain": sp.custom_subdomain,
"_platform": platform,
}
resolved = StoreContextManager.get_store_from_context(db, context)
passed = resolved is not None and resolved.id == expected_store.id
details.append(_entry(
domain=f"{sp.custom_subdomain}.{platform.domain or platform.code}",
entry_type="custom subdomain",
platform_code=platform.code,
expected_store=expected_store,
resolved_store=resolved,
passed=passed,
note="" if passed else "custom_subdomain lookup failed",
))
# ── 2. Default subdomains per platform ──
# e.g. acme.omsflow.lu — uses Store.subdomain on the platform domain.
# With fail-closed routing, this only works if StorePlatform.custom_subdomain
# matches Store.subdomain, so this check surfaces missing entries.
for store in active_stores:
if not store.subdomain:
continue
memberships = store_memberships.get(store.id, [])
for sp, platform in memberships:
if not platform.domain:
continue
# If custom_subdomain is already set to this value, it's covered
# in check #1 above — but we still check default subdomain access
# to confirm it resolves (it exercises the same code path).
# Skip if custom_subdomain == subdomain to avoid duplicate entries.
if sp.custom_subdomain and sp.custom_subdomain.lower() == store.subdomain.lower():
continue
context = {
"detection_method": "subdomain",
"subdomain": store.subdomain,
"_platform": platform,
}
resolved = StoreContextManager.get_store_from_context(db, context)
passed = resolved is not None and resolved.id == store.id
note = ""
if not passed:
note = (
f"Store '{store.subdomain}' not found or has no active "
f"membership on platform {platform.code}"
)
details.append(_entry(
domain=f"{store.subdomain}.{platform.domain}",
entry_type="default subdomain",
platform_code=platform.code,
expected_store=store,
resolved_store=resolved,
passed=passed,
note=note,
))
# ── 3. Custom domains (StoreDomain) ──
# e.g. wizatech.shop
store_domains = (
db.query(StoreDomain)
.filter(
StoreDomain.is_active.is_(True),
StoreDomain.is_verified.is_(True),
)
.all()
)
for sd in store_domains:
expected_store = sd.store
if not expected_store:
continue
platform = sd.platform
platform_code = platform.code if platform else None
context = {
"detection_method": "custom_domain",
"domain": sd.domain,
}
resolved = StoreContextManager.get_store_from_context(db, context)
passed = resolved is not None and resolved.id == expected_store.id
details.append(_entry(
domain=sd.domain,
entry_type="custom domain",
platform_code=platform_code,
expected_store=expected_store,
resolved_store=resolved,
passed=passed,
note="" if passed else "custom domain resolution failed",
))
# ── 4. Path-based routes ──
# e.g. /storefront/luxweb/, /store/luxweb/
# Path-based URLs use Store.subdomain as the path segment.
for store in active_stores:
if not store.subdomain:
continue
memberships = store_memberships.get(store.id, [])
platform_codes = sorted(p.code for _, p in memberships) if memberships else []
platform_label = ", ".join(platform_codes) if platform_codes else None
for prefix, label in [
("/storefront/", "storefront"),
("/store/", "store"),
]:
context = {
"detection_method": "path",
"subdomain": store.subdomain,
"path_prefix": f"{prefix}{store.subdomain}",
"full_prefix": prefix,
}
resolved = StoreContextManager.get_store_from_context(db, context)
passed = resolved is not None and resolved.id == store.id
details.append(_entry(
domain=f"{prefix}{store.subdomain}/",
entry_type=f"path ({label})",
platform_code=platform_label,
expected_store=store,
resolved_store=resolved,
passed=passed,
note="" if passed else f"path-based {label} resolution failed",
))
total = len(details)
passed_count = sum(1 for d in details if d["status"] == "pass")
return {
"total": total,
"passed": passed_count,
"failed": total - passed_count,
"details": details,
}
def _entry(
domain: str,
entry_type: str,
platform_code: str | None,
expected_store,
resolved_store,
passed: bool,
note: str,
) -> dict:
"""Build a single health-check result entry."""
return {
"domain": domain,
"type": entry_type,
"platform_code": platform_code,
"expected_store": (
getattr(expected_store, "store_code", expected_store.subdomain)
if expected_store else None
),
"resolved_store": (
getattr(resolved_store, "store_code", resolved_store.subdomain)
if resolved_store else None
),
"status": "pass" if passed else "fail",
"note": note,
}