fix: BUG-7, BUG-10, BUG-11, BUG-12 — remaining bug fixes
- BUG-12: ENCRYPTION_KEY validation now exits with sys.exit(1) on invalid key - BUG-11: WP Advanced health check adds authenticated fallback with auth_valid - BUG-7: Catch-all GET route serves styled 404 HTML instead of plain text - BUG-10: Recent Activity fetches extra entries before filtering, fixes None project Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -395,8 +395,8 @@ async def get_recent_activity(limit: int = 10) -> list:
|
|||||||
|
|
||||||
audit_logger = get_audit_logger()
|
audit_logger = get_audit_logger()
|
||||||
|
|
||||||
# Read recent entries from audit log
|
# Fetch extra entries because we filter some out
|
||||||
entries = audit_logger.get_recent_entries(limit=limit)
|
entries = audit_logger.get_recent_entries(limit=limit * 3)
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
# Skip internal/noise events
|
# Skip internal/noise events
|
||||||
@@ -404,16 +404,22 @@ async def get_recent_activity(limit: int = 10) -> list:
|
|||||||
if evt in ("health_metric_recorded", "health_check"):
|
if evt in ("health_metric_recorded", "health_check"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
project = (entry.get("metadata") or {}).get("project_id") or "-"
|
||||||
|
|
||||||
activity.append(
|
activity.append(
|
||||||
{
|
{
|
||||||
"timestamp": entry.get("timestamp") or "",
|
"timestamp": entry.get("timestamp") or "",
|
||||||
"type": evt or "unknown",
|
"type": evt or "unknown",
|
||||||
"message": entry.get("message") or "",
|
"message": entry.get("message") or "",
|
||||||
"project": (entry.get("metadata") or {}).get("project_id", "-"),
|
"project": project if project != "None" else "-",
|
||||||
"level": entry.get("level") or "INFO",
|
"level": entry.get("level") or "INFO",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Stop once we have enough
|
||||||
|
if len(activity) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error getting recent activity: {e}")
|
logger.warning(f"Error getting recent activity: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -553,12 +553,36 @@ class HealthMonitor:
|
|||||||
plugin_instance = plugin_registry.create_instance(plugin_type, site_id, config_dict)
|
plugin_instance = plugin_registry.create_instance(plugin_type, site_id, config_dict)
|
||||||
return await plugin_instance.health_check()
|
return await plugin_instance.health_check()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(
|
logger.warning(
|
||||||
f"Could not create plugin instance for {project_id}, "
|
f"Could not create plugin instance for {project_id}, "
|
||||||
f"falling back to basic HTTP check: {e}"
|
f"falling back to authenticated HTTP check: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fallback: basic HTTP check if plugin instantiation fails
|
# Fallback: authenticated health check for WordPress-based plugins
|
||||||
|
if plugin_type in ("wordpress", "wordpress_advanced", "woocommerce"):
|
||||||
|
try:
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
auth = aiohttp.BasicAuth(
|
||||||
|
config.username or "",
|
||||||
|
config.app_password or "",
|
||||||
|
)
|
||||||
|
async with aiohttp.ClientSession(auth=auth) as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{config.url}/wp-json/wp/v2/users/me",
|
||||||
|
timeout=aiohttp.ClientTimeout(total=10),
|
||||||
|
ssl=False,
|
||||||
|
) as resp:
|
||||||
|
auth_valid = resp.status == 200
|
||||||
|
basic_result = await self._basic_http_health_check(config.url, project_id)
|
||||||
|
basic_result["auth_valid"] = auth_valid
|
||||||
|
if not auth_valid:
|
||||||
|
basic_result["auth_warning"] = "Site accessible but credentials may be invalid"
|
||||||
|
return basic_result
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Last resort: basic HTTP check
|
||||||
return await self._basic_http_health_check(config.url, project_id)
|
return await self._basic_http_health_check(config.url, project_id)
|
||||||
|
|
||||||
async def _basic_http_health_check(self, url: str | None, project_id: str) -> dict[str, Any]:
|
async def _basic_http_health_check(self, url: str | None, project_id: str) -> dict[str, Any]:
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ activity.message }}</p>
|
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ activity.message }}</p>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ activity.project }}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400">{{ activity.project if activity.project and activity.project != 'None' else '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">{{ activity.timestamp[:19] if activity.timestamp else '-' }}</span>
|
<span class="text-xs text-gray-400 dark:text-gray-500">{{ activity.timestamp[:19] if activity.timestamp else '-' }}</span>
|
||||||
|
|||||||
41
server.py
41
server.py
@@ -1234,18 +1234,26 @@ if _encryption_key_raw:
|
|||||||
|
|
||||||
_key_bytes = _b64.b64decode(_encryption_key_raw)
|
_key_bytes = _b64.b64decode(_encryption_key_raw)
|
||||||
if len(_key_bytes) != 32:
|
if len(_key_bytes) != 32:
|
||||||
logger.error(
|
logger.critical(
|
||||||
"Invalid ENCRYPTION_KEY: must decode to 32 bytes, got %d. "
|
"FATAL: ENCRYPTION_KEY must decode to exactly 32 bytes, got %d. "
|
||||||
"User site management will not work.",
|
'Generate a valid key with: python -c "import os,base64; '
|
||||||
|
'print(base64.b64encode(os.urandom(32)).decode())"',
|
||||||
len(_key_bytes),
|
len(_key_bytes),
|
||||||
)
|
)
|
||||||
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
from core.encryption import initialize_credential_encryption
|
from core.encryption import initialize_credential_encryption
|
||||||
|
|
||||||
initialize_credential_encryption(_encryption_key_raw)
|
initialize_credential_encryption(_encryption_key_raw)
|
||||||
logger.info("Credential encryption initialized")
|
logger.info("Credential encryption initialized")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Invalid ENCRYPTION_KEY: %s. User site management will not work.", e)
|
logger.critical(
|
||||||
|
"FATAL: Invalid ENCRYPTION_KEY: %s. "
|
||||||
|
'Generate a valid key with: python -c "import os,base64; '
|
||||||
|
'print(base64.b64encode(os.urandom(32)).decode())"',
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logger.info("ENCRYPTION_KEY not set — user site management disabled")
|
logger.info("ENCRYPTION_KEY not set — user site management disabled")
|
||||||
|
|
||||||
@@ -4697,17 +4705,8 @@ def create_multi_endpoint_app(transport: str = "streamable-http"):
|
|||||||
|
|
||||||
routes.append(Mount("/static", app=StaticFiles(directory=_static_dir), name="static"))
|
routes.append(Mount("/static", app=StaticFiles(directory=_static_dir), name="static"))
|
||||||
|
|
||||||
# Main admin endpoint (must be last - catches all remaining routes)
|
# Catch-all for unmatched GET requests — serves styled 404 page
|
||||||
routes.append(Mount("/", app=main_app, name="mcp_admin"))
|
async def catch_all_404(request):
|
||||||
|
|
||||||
# Add middlewares
|
|
||||||
middleware = [
|
|
||||||
StarletteMiddleware(OAuthRequiredMiddleware),
|
|
||||||
StarletteMiddleware(DashboardCSRFMiddleware),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Custom 404 handler — styled HTML instead of plain text
|
|
||||||
async def not_found_handler(request: Request, exc):
|
|
||||||
_404_path = os.path.join(
|
_404_path = os.path.join(
|
||||||
os.path.dirname(__file__), "core", "templates", "dashboard", "404.html"
|
os.path.dirname(__file__), "core", "templates", "dashboard", "404.html"
|
||||||
)
|
)
|
||||||
@@ -4722,11 +4721,21 @@ def create_multi_endpoint_app(transport: str = "streamable-http"):
|
|||||||
|
|
||||||
return PlainTextResponse("404 Not Found", status_code=404)
|
return PlainTextResponse("404 Not Found", status_code=404)
|
||||||
|
|
||||||
|
routes.append(Route("/{path:path}", catch_all_404, methods=["GET"]))
|
||||||
|
|
||||||
|
# Main admin endpoint (must be last - catches all remaining non-GET routes)
|
||||||
|
routes.append(Mount("/", app=main_app, name="mcp_admin"))
|
||||||
|
|
||||||
|
# Add middlewares
|
||||||
|
middleware = [
|
||||||
|
StarletteMiddleware(OAuthRequiredMiddleware),
|
||||||
|
StarletteMiddleware(DashboardCSRFMiddleware),
|
||||||
|
]
|
||||||
|
|
||||||
app = Starlette(
|
app = Starlette(
|
||||||
routes=routes,
|
routes=routes,
|
||||||
lifespan=combined_lifespan,
|
lifespan=combined_lifespan,
|
||||||
middleware=middleware,
|
middleware=middleware,
|
||||||
exception_handlers={404: not_found_handler},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|||||||
Reference in New Issue
Block a user