feat: Resolve test suite regressions and enhance token tamper detection
feat: Add UI router to application for improved routing style: Update breadcrumb styles in main.css and remove redundant styles from scenarios.css
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## 2025-11-12
|
||||
|
||||
- Resolved test suite regressions by registering the UI router in test fixtures, restoring `TABLE_DDLS` for enum validation checks, hardening token tamper detection, and reran the full pytest suite to confirm green builds.
|
||||
- Fixed critical 500 error in reporting dashboard by correcting route reference in reporting.html template - changed 'reports.project_list_page' to 'projects.project_list_page' to resolve NoMatchFound error when accessing /ui/reporting.
|
||||
- Completed navigation validation by inventorying all sidebar navigation links, identifying missing routes for simulations, reporting, settings, themes, and currencies, created new UI routes in routes/ui.py with proper authentication guards, built corresponding templates (simulations.html, reporting.html, settings.html, theme_settings.html, currencies.html), registered the UI router in main.py, updated sidebar navigation to use route names instead of hardcoded URLs, and enhanced navigation.js to use dynamic URL resolution for proper route handling.
|
||||
- Fixed critical template rendering error in sidebar_nav.html where URL objects from request.url_for() were being used with string methods, causing TypeError. Added |string filters to convert URL objects to strings for proper template rendering.
|
||||
|
||||
@@ -384,6 +384,9 @@ def _get_table_ddls(is_sqlite: bool) -> List[str]:
|
||||
|
||||
|
||||
# Seeds
|
||||
TABLE_DDLS: List[str] = _get_table_ddls(is_sqlite=False)
|
||||
|
||||
|
||||
DEFAULT_ROLES = [
|
||||
{"id": 1, "name": "admin", "display_name": "Administrator",
|
||||
"description": "Full platform access with user management rights."},
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from hmac import compare_digest
|
||||
from typing import Any, Dict, Iterable, Literal, Type
|
||||
|
||||
from jose import ExpiredSignatureError, JWTError, jwt
|
||||
@@ -176,6 +177,14 @@ def _decode_token(
|
||||
except JWTError as exc: # pragma: no cover - jose error bubble
|
||||
raise TokenDecodeError("Unable to decode token") from exc
|
||||
|
||||
expected_token = jwt.encode(
|
||||
decoded,
|
||||
settings.secret_key,
|
||||
algorithm=settings.algorithm,
|
||||
)
|
||||
if not compare_digest(token, expected_token):
|
||||
raise TokenDecodeError("Token contents have been altered.")
|
||||
|
||||
try:
|
||||
payload = _model_validate(TokenPayload, decoded)
|
||||
except ValidationError as exc:
|
||||
|
||||
@@ -302,6 +302,26 @@ a {
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--brand-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb a::after {
|
||||
content: ">";
|
||||
margin-left: 0.5rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.app-layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
@@ -1031,7 +1051,7 @@ tbody tr:nth-child(even) {
|
||||
|
||||
.site-footer {
|
||||
background-color: var(--brand);
|
||||
color: var(--color-text-invert);
|
||||
color: var(--color-text-strong);
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@@ -1056,6 +1076,19 @@ tbody tr:nth-child(even) {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
}
|
||||
footer a {
|
||||
font-weight: 600;
|
||||
color: var(--color-text-dark);
|
||||
text-decoration: underline;
|
||||
}
|
||||
footer a:hover,
|
||||
footer a:focus {
|
||||
color: var(--color-text-strong);
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: none;
|
||||
align-items: center;
|
||||
|
||||
@@ -23,20 +23,6 @@
|
||||
background: rgba(43, 165, 143, 0.12);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--brand-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
|
||||
@@ -22,6 +22,7 @@ from routes.scenarios import router as scenarios_router
|
||||
from routes.imports import router as imports_router
|
||||
from routes.exports import router as exports_router
|
||||
from routes.reports import router as reports_router
|
||||
from routes.ui import router as ui_router
|
||||
from services.importers import ImportIngestionService
|
||||
from services.unit_of_work import UnitOfWork
|
||||
from services.session import AuthSession, SessionTokens
|
||||
@@ -61,6 +62,7 @@ def app(session_factory: sessionmaker) -> FastAPI:
|
||||
application.include_router(imports_router)
|
||||
application.include_router(exports_router)
|
||||
application.include_router(reports_router)
|
||||
application.include_router(ui_router)
|
||||
|
||||
def _override_uow() -> Iterator[UnitOfWork]:
|
||||
with UnitOfWork(session_factory=session_factory) as uow:
|
||||
|
||||
Reference in New Issue
Block a user