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
|
## 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.
|
- 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.
|
- 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.
|
- 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
|
# Seeds
|
||||||
|
TABLE_DDLS: List[str] = _get_table_ddls(is_sqlite=False)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ROLES = [
|
DEFAULT_ROLES = [
|
||||||
{"id": 1, "name": "admin", "display_name": "Administrator",
|
{"id": 1, "name": "admin", "display_name": "Administrator",
|
||||||
"description": "Full platform access with user management rights."},
|
"description": "Full platform access with user management rights."},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from hmac import compare_digest
|
||||||
from typing import Any, Dict, Iterable, Literal, Type
|
from typing import Any, Dict, Iterable, Literal, Type
|
||||||
|
|
||||||
from jose import ExpiredSignatureError, JWTError, jwt
|
from jose import ExpiredSignatureError, JWTError, jwt
|
||||||
@@ -176,6 +177,14 @@ def _decode_token(
|
|||||||
except JWTError as exc: # pragma: no cover - jose error bubble
|
except JWTError as exc: # pragma: no cover - jose error bubble
|
||||||
raise TokenDecodeError("Unable to decode token") from exc
|
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:
|
try:
|
||||||
payload = _model_validate(TokenPayload, decoded)
|
payload = _model_validate(TokenPayload, decoded)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
|
|||||||
@@ -302,6 +302,26 @@ a {
|
|||||||
border-color: var(--brand);
|
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 {
|
.app-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -1031,7 +1051,7 @@ tbody tr:nth-child(even) {
|
|||||||
|
|
||||||
.site-footer {
|
.site-footer {
|
||||||
background-color: var(--brand);
|
background-color: var(--brand);
|
||||||
color: var(--color-text-invert);
|
color: var(--color-text-strong);
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,6 +1076,19 @@ tbody tr:nth-child(even) {
|
|||||||
object-fit: cover;
|
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 {
|
.sidebar-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -23,20 +23,6 @@
|
|||||||
background: rgba(43, 165, 143, 0.12);
|
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 {
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
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.imports import router as imports_router
|
||||||
from routes.exports import router as exports_router
|
from routes.exports import router as exports_router
|
||||||
from routes.reports import router as reports_router
|
from routes.reports import router as reports_router
|
||||||
|
from routes.ui import router as ui_router
|
||||||
from services.importers import ImportIngestionService
|
from services.importers import ImportIngestionService
|
||||||
from services.unit_of_work import UnitOfWork
|
from services.unit_of_work import UnitOfWork
|
||||||
from services.session import AuthSession, SessionTokens
|
from services.session import AuthSession, SessionTokens
|
||||||
@@ -61,6 +62,7 @@ def app(session_factory: sessionmaker) -> FastAPI:
|
|||||||
application.include_router(imports_router)
|
application.include_router(imports_router)
|
||||||
application.include_router(exports_router)
|
application.include_router(exports_router)
|
||||||
application.include_router(reports_router)
|
application.include_router(reports_router)
|
||||||
|
application.include_router(ui_router)
|
||||||
|
|
||||||
def _override_uow() -> Iterator[UnitOfWork]:
|
def _override_uow() -> Iterator[UnitOfWork]:
|
||||||
with UnitOfWork(session_factory=session_factory) as uow:
|
with UnitOfWork(session_factory=session_factory) as uow:
|
||||||
|
|||||||
Reference in New Issue
Block a user