feat: Passwordless auth — backend deployad, frontend uppdaterad

- Backend: Docker-container på port 8082
- Redis: Intern container (ingen exposed port)
- Nginx-config: nginx-passwordless.conf (väntar på deploy)
- Webb: API_BASE uppdaterad till api.quixzoom.com
- Fix: datetime timezone-aware i approve-endpoint
- Test: End-to-end flöde verifierat
This commit is contained in:
Bernt
2026-07-07 09:05:21 +00:00
parent 6989a98d75
commit 623561d30a
4 changed files with 58 additions and 9 deletions
+2 -3
View File
@@ -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:
@@ -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;
}
@@ -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)
+1 -1
View File
@@ -455,7 +455,7 @@ input::placeholder{color:rgba(255,255,255,.3)}
</div>
<script>
const API_BASE = 'https://api.quixzoom.com/v1';
const API_BASE = 'https://api.quixzoom.com';
const TOKEN_KEY = '***';
const REFRESH_KEY = '***';