diff --git a/backend/main.py b/backend/main.py index 252291c..cb8b7e0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -112,11 +112,13 @@ async def lifespan(app: FastAPI): app = FastAPI(title="Yolkbook API", lifespan=lifespan) +_cors_origins = [o.strip() for o in os.environ.get("ALLOWED_ORIGINS", "").split(",") if o.strip()] app.add_middleware( CORSMiddleware, - allow_origins=["*"], - allow_methods=["*"], - allow_headers=["*"], + allow_origins=_cors_origins, + allow_credentials=True, + allow_methods=["GET", "POST", "PUT", "DELETE"], + allow_headers=["Authorization", "Content-Type"], ) app.include_router(auth_router.router) diff --git a/nginx/html/index.html b/nginx/html/index.html index ef720d2..66005dd 100644 --- a/nginx/html/index.html +++ b/nginx/html/index.html @@ -70,7 +70,7 @@ - + diff --git a/nginx/html/js/admin.js b/nginx/html/js/admin.js index 6475f9f..8e2bbb5 100644 --- a/nginx/html/js/admin.js +++ b/nginx/html/js/admin.js @@ -47,20 +47,20 @@ function renderUsers(users) { : ``; const impersonateBtn = !isSelf - ? `` + ? `` : ''; const deleteBtn = !isSelf - ? `` + ? `` : ''; return ` - ${u.username} + ${escHtml(u.username)} ${roleLabel} ${statusLabel} ${created} - + ${toggleBtn} ${impersonateBtn} ${deleteBtn} @@ -115,7 +115,7 @@ async function toggleUser(id, disable) { } } -async function impersonateUser(id, username) { +async function impersonateUser(id) { try { const data = await API.post(`/api/admin/users/${id}/impersonate`, {}); // Save admin token so user can return diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 4351a1a..4fcb467 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -13,8 +13,11 @@ http { gzip_types text/plain text/css application/javascript application/json; gzip_min_length 1000; - # ── Rate limiting — login endpoint ──────────────────────────────────────── - limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; + # ── Rate limiting ───────────────────────────────────────────────────────── + limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; + limit_req_zone $binary_remote_addr zone=register:10m rate=3r/m; + limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/m; + limit_req_status 429; server { listen 80; @@ -24,10 +27,11 @@ http { index index.html; # ── Security headers ────────────────────────────────────────────────── - add_header X-Content-Type-Options "nosniff" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" 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' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; frame-ancestors 'none'" always; # ── Static files ────────────────────────────────────────────────────── location / { @@ -45,7 +49,7 @@ http { access_log off; } - # ── Login rate limiting ─────────────────────────────────────────────── + # ── Auth / admin rate limiting ──────────────────────────────────────── location = /api/auth/login { limit_req zone=login burst=3 nodelay; @@ -64,6 +68,42 @@ http { add_header Referrer-Policy "strict-origin-when-cross-origin" always; } + location = /api/auth/register { + limit_req zone=register burst=2 nodelay; + + proxy_pass http://api:8000; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + add_header Cache-Control "no-store" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + } + + location /api/admin/ { + limit_req zone=admin burst=5 nodelay; + + proxy_pass http://api:8000; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + add_header Cache-Control "no-store" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + } + # ── API reverse proxy ───────────────────────────────────────────────── # All /api/* requests are forwarded to the FastAPI container. # The container is reachable by its service name on the Docker network.