- 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>
69 lines
2.5 KiB
Nginx Configuration File
69 lines
2.5 KiB
Nginx Configuration File
# Rate limiting zones — included inside http{} block
|
|
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
|
|
limit_req_zone $binary_remote_addr zone=tv_limit:10m rate=10r/m;
|
|
|
|
server {
|
|
listen 80;
|
|
server_tokens off;
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
# Security headers
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; font-src 'self' data:;" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
# Gzip compression
|
|
gzip on;
|
|
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml;
|
|
gzip_min_length 1024;
|
|
|
|
# Rate-limited auth endpoints (checked before the generic /api/ block)
|
|
location ~ ^/api/(auth/(login|register)|admin/login)$ {
|
|
limit_req zone=auth_limit burst=3 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://backend:8000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
# Rate-limited TV dashboard endpoint (public, token-based)
|
|
location ~ ^/api/dashboard/ {
|
|
limit_req zone=tv_limit burst=5 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://backend:8000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
# API proxy → FastAPI backend
|
|
location /api/ {
|
|
proxy_pass http://backend:8000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
# WebSocket proxy → FastAPI backend
|
|
location /ws/ {
|
|
limit_req zone=tv_limit burst=5 nodelay;
|
|
limit_req_status 429;
|
|
proxy_pass http://backend:8000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_read_timeout 3600s;
|
|
}
|
|
|
|
# Vue Router — all other paths serve index.html (SPA fallback)
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
}
|