Security hardening: go-live review fixes

- TV tokens upgraded from 4 to 6 digits; Regen Token button in Admin
- Nginx rate limiting on TV dashboard and WebSocket endpoints
- Login lockout after 5 failed attempts (15 min); clears on admin password reset
- HSTS header added; CSP unsafe-inline removed from script-src; CORS restricted to explicit methods/headers
- Dependency CVE fixes: PyJWT 2.12.0, aiomysql 0.3.0, cryptography 46.0.5, python-multipart 0.0.22
- datetime.utcnow() replaced with datetime.now(timezone.utc) throughout
- SQL identifier whitelist for startup migration queries
- README updated: security notes section, lockout docs, token regen, NPM proxy guidance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 00:00:14 -07:00
parent be86cae7fa
commit 3022bc328b
11 changed files with 228 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
"""Shared timer-elapsed computation used by sessions and dashboard routers."""
from datetime import datetime
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
@@ -39,7 +39,7 @@ async def compute_block_elapsed(
last_start = None
running = last_start is not None
if running:
elapsed += (datetime.utcnow() - last_start).total_seconds()
elapsed += (datetime.now(timezone.utc) - last_start).total_seconds()
# is_paused is True whenever the timer is not actively running —
# covers: explicitly paused, never started, or only selected.
@@ -75,7 +75,7 @@ async def compute_break_elapsed(
last_start = None
running = last_start is not None
if running:
elapsed += (datetime.utcnow() - last_start).total_seconds()
elapsed += (datetime.now(timezone.utc) - last_start).total_seconds()
is_paused = not running
return int(elapsed), is_paused