feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
- 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:
162
docs/development/diagnostics/domain-health.md
Normal file
162
docs/development/diagnostics/domain-health.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Domain Health Diagnostic Tool
|
||||
|
||||
The Domain Health tool simulates the middleware resolution pipeline for every configured store access method, verifying that each domain, subdomain, and path-based route resolves to the expected store.
|
||||
|
||||
## Overview
|
||||
|
||||
| Aspect | Detail |
|
||||
|--------|--------|
|
||||
| Location | Admin > Diagnostics > Resolution > Domain Health |
|
||||
| URL | `/admin/platform-debug` (select "Domain Health" in sidebar) |
|
||||
| API Endpoint | `GET /api/v1/admin/debug/domain-health` |
|
||||
| Access | Super admin only |
|
||||
|
||||
## What It Checks
|
||||
|
||||
The tool runs four categories of checks against the **live database**:
|
||||
|
||||
### 1. Custom Subdomains (StorePlatform)
|
||||
|
||||
Checks every active `StorePlatform` entry that has a `custom_subdomain` set.
|
||||
|
||||
| Example | What It Tests |
|
||||
|---------|--------------|
|
||||
| `acme-rewards.rewardflow.lu` | Does `detection_method=subdomain` with `custom_subdomain` + platform context resolve to the expected store? |
|
||||
|
||||
This is the most critical check because the fail-closed routing policy means a custom subdomain that doesn't resolve will return a 404 rather than falling back to a global lookup.
|
||||
|
||||
### 2. Default Subdomains per Platform
|
||||
|
||||
Checks every active store's default `subdomain` on each platform it belongs to.
|
||||
|
||||
| Example | What It Tests |
|
||||
|---------|--------------|
|
||||
| `acme.omsflow.lu` | Does `detection_method=subdomain` with `Store.subdomain` + platform context resolve to the expected store? |
|
||||
|
||||
The middleware first tries `StorePlatform.custom_subdomain`, then falls back to `Store.subdomain` — but **only** if the store has an active `StorePlatform` membership on the detected platform. A store with no membership on that platform will be blocked (fail-closed, prevents cross-tenant leaks).
|
||||
|
||||
Entries where `custom_subdomain` already equals the store's `subdomain` are de-duplicated (covered by check #1).
|
||||
|
||||
### 3. Custom Domains (StoreDomain)
|
||||
|
||||
Checks every active, verified `StoreDomain` entry.
|
||||
|
||||
| Example | What It Tests |
|
||||
|---------|--------------|
|
||||
| `wizatech.shop` | Does `detection_method=custom_domain` resolve to the store that owns the domain? |
|
||||
|
||||
The Platform column shows the platform associated with the `StoreDomain.platform_id` relationship.
|
||||
|
||||
### 4. Path-Based Routes
|
||||
|
||||
Checks every active store's `subdomain` field via path-based resolution.
|
||||
|
||||
| Example | What It Tests |
|
||||
|---------|--------------|
|
||||
| `/storefront/luxweb/` | Does `detection_method=path` with `subdomain=luxweb` resolve to the correct store? |
|
||||
| `/store/luxweb/` | Same check for the store dashboard path |
|
||||
|
||||
The Platform column shows all platforms the store is a member of (via `StorePlatform`).
|
||||
|
||||
!!! note "Path segments use Store.subdomain"
|
||||
Path-based URLs use the store's `subdomain` field, **not** `store_code`. For example, a store with `store_code=LUXWEBSITES` and `subdomain=luxweb` is accessed at `/storefront/luxweb/`, not `/storefront/LUXWEBSITES/`.
|
||||
|
||||
## How It Works
|
||||
|
||||
The tool calls the **same resolution functions** that the live middleware uses:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Health Check] -->|builds context dict| B[StoreContextManager.get_store_from_context]
|
||||
B -->|queries DB| C[Store resolved?]
|
||||
C -->|id matches expected| D[PASS]
|
||||
C -->|mismatch or None| E[FAIL]
|
||||
```
|
||||
|
||||
It does **not** make actual HTTP requests. This means it validates database configuration and resolution logic, but cannot detect:
|
||||
|
||||
- DNS misconfigurations
|
||||
- Reverse proxy / Cloudflare routing issues
|
||||
- SSL certificate problems
|
||||
- Network-level failures
|
||||
|
||||
## Reading the Results
|
||||
|
||||
### Summary Bar
|
||||
|
||||
| Counter | Meaning |
|
||||
|---------|---------|
|
||||
| Total Checked | Number of access methods tested |
|
||||
| Passed | Resolved to the expected store |
|
||||
| Failed | Resolution mismatch or store not found |
|
||||
|
||||
### Results Table
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Status | `pass` or `fail` badge |
|
||||
| Domain | The domain, subdomain, or path being tested |
|
||||
| Type | `custom subdomain`, `default subdomain`, `custom domain`, `path (storefront)`, or `path (store)` |
|
||||
| Platform | Platform code(s) the entry belongs to |
|
||||
| Expected Store | The store code we expect to resolve |
|
||||
| Resolved Store | The store code that actually resolved (or `--` if none) |
|
||||
| Note | Error description for failed entries |
|
||||
|
||||
### Common Failure Scenarios
|
||||
|
||||
| Symptom | Likely Cause |
|
||||
|---------|-------------|
|
||||
| Custom subdomain fails | `StorePlatform.custom_subdomain` doesn't match or `StorePlatform.is_active=false` |
|
||||
| Default subdomain fails | Store has no active `StorePlatform` membership on the detected platform (cross-tenant blocked) |
|
||||
| Custom domain fails | `StoreDomain` not verified, inactive, or store is inactive |
|
||||
| Path-based fails | Store's `subdomain` field is empty or store is inactive |
|
||||
| Resolved store differs from expected | Two stores have conflicting subdomain/domain entries |
|
||||
|
||||
## API Response
|
||||
|
||||
```
|
||||
GET /api/v1/admin/debug/domain-health
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 36,
|
||||
"passed": 30,
|
||||
"failed": 6,
|
||||
"details": [
|
||||
{
|
||||
"domain": "acme-rewards.rewardflow.lu",
|
||||
"type": "custom subdomain",
|
||||
"platform_code": "loyalty",
|
||||
"expected_store": "ACME",
|
||||
"resolved_store": "ACME",
|
||||
"status": "pass",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"domain": "acme.omsflow.lu",
|
||||
"type": "default subdomain",
|
||||
"platform_code": "oms",
|
||||
"expected_store": "ACME",
|
||||
"resolved_store": "ACME",
|
||||
"status": "pass",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"domain": "/storefront/acme/",
|
||||
"type": "path (storefront)",
|
||||
"platform_code": "oms, loyalty",
|
||||
"expected_store": "ACME",
|
||||
"resolved_store": "ACME",
|
||||
"status": "pass",
|
||||
"note": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Middleware Stack](../../architecture/middleware.md) - Full middleware pipeline reference
|
||||
- [Dev Tools Module](../../modules/dev_tools/index.md) - Other diagnostic tools
|
||||
- [Multi-Tenant System](../../architecture/multi-tenant.md) - Tenant detection architecture
|
||||
Reference in New Issue
Block a user