diff --git a/iom/quixzoom-auth-service/docker-compose.yml b/iom/quixzoom-auth-service/docker-compose.yml index 3bd25494e..3d37bcc0e 100644 --- a/iom/quixzoom-auth-service/docker-compose.yml +++ b/iom/quixzoom-auth-service/docker-compose.yml @@ -5,7 +5,7 @@ services: build: . container_name: quixzoom-passwordless ports: - - "8080:8080" + - "8082:8080" environment: - PORT=8080 - HOST=0.0.0.0 @@ -28,8 +28,7 @@ services: redis: image: redis:7-alpine container_name: quixzoom-auth-redis - ports: - - "6379:6379" + # No exposed port - internal only volumes: - redis-data:/data networks: diff --git a/iom/quixzoom-auth-service/nginx-passwordless.conf b/iom/quixzoom-auth-service/nginx-passwordless.conf new file mode 100644 index 000000000..80c2a6105 --- /dev/null +++ b/iom/quixzoom-auth-service/nginx-passwordless.conf @@ -0,0 +1,40 @@ +# quiXzoom Passwordless Auth — Nginx konfiguration +# Lägg till i /etc/nginx/conf.d/ eller inkludera från huvudkonfig + +upstream quixzoom_passwordless { + server 127.0.0.1:8082; + keepalive 16; +} + +# Passwordless auth endpoints +location /v1/auth/passwordless { + proxy_pass http://quixzoom_passwordless; + proxy_http_version 1.1; + proxy_set_header Connection ""; + 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; + + # CORS + add_header Access-Control-Allow-Origin "https://quixzoom.se" always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; + add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; + + # Handle preflight + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin "https://quixzoom.se"; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; + add_header Access-Control-Allow-Headers "Content-Type, Authorization"; + add_header Access-Control-Max-Age 86400; + return 204; + } + + # Rate limiting + limit_req zone=general burst=20 nodelay; + + # Timeouts + proxy_connect_timeout 5s; + proxy_send_timeout 10s; + proxy_read_timeout 10s; +} diff --git a/iom/quixzoom-auth-service/routes/passwordless.py b/iom/quixzoom-auth-service/routes/passwordless.py index ccbbf0de6..09755ec94 100644 --- a/iom/quixzoom-auth-service/routes/passwordless.py +++ b/iom/quixzoom-auth-service/routes/passwordless.py @@ -7,7 +7,7 @@ import os import time import hashlib import secrets -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional from fastapi import APIRouter, HTTPException, Header, Request, Depends from fastapi.responses import JSONResponse @@ -229,11 +229,21 @@ async def approve_passwordless( # Verify timestamp is recent (within 5 minutes) try: - timestamp = datetime.fromisoformat(body.timestamp.replace('Z', '+00:00')) - if datetime.utcnow() - timestamp > timedelta(minutes=5): + # Parse timestamp and make it offset-aware + timestamp_str = body.timestamp.replace('Z', '+00:00') + timestamp = datetime.fromisoformat(timestamp_str) + + # Ensure timestamp is offset-aware + if timestamp.tzinfo is None: + timestamp = timestamp.replace(tzinfo=timezone.utc) + + # Get current UTC time as offset-aware + now = datetime.now(timezone.utc) + + if now - timestamp > timedelta(minutes=5): raise HTTPException(status_code=403, detail="Timestamp too old") - except ValueError: - raise HTTPException(status_code=400, detail="Invalid timestamp format") + except ValueError as e: + raise HTTPException(status_code=400, detail=f"Invalid timestamp format: {str(e)}") # TODO: Verify signature # expected_signature = hmac_sha256(session_id + request_token + timestamp, device_secret) diff --git a/quixzoom-market-pages/se/login/index.html b/quixzoom-market-pages/se/login/index.html index a88a82786..1a98cb94f 100644 --- a/quixzoom-market-pages/se/login/index.html +++ b/quixzoom-market-pages/se/login/index.html @@ -455,7 +455,7 @@ input::placeholder{color:rgba(255,255,255,.3)}