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:
@@ -5,7 +5,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
container_name: quixzoom-passwordless
|
container_name: quixzoom-passwordless
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8082:8080"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
@@ -28,8 +28,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: quixzoom-auth-redis
|
container_name: quixzoom-auth-redis
|
||||||
ports:
|
# No exposed port - internal only
|
||||||
- "6379:6379"
|
|
||||||
volumes:
|
volumes:
|
||||||
- redis-data:/data
|
- redis-data:/data
|
||||||
networks:
|
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 time
|
||||||
import hashlib
|
import hashlib
|
||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import APIRouter, HTTPException, Header, Request, Depends
|
from fastapi import APIRouter, HTTPException, Header, Request, Depends
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
@@ -229,11 +229,21 @@ async def approve_passwordless(
|
|||||||
|
|
||||||
# Verify timestamp is recent (within 5 minutes)
|
# Verify timestamp is recent (within 5 minutes)
|
||||||
try:
|
try:
|
||||||
timestamp = datetime.fromisoformat(body.timestamp.replace('Z', '+00:00'))
|
# Parse timestamp and make it offset-aware
|
||||||
if datetime.utcnow() - timestamp > timedelta(minutes=5):
|
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")
|
raise HTTPException(status_code=403, detail="Timestamp too old")
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
raise HTTPException(status_code=400, detail="Invalid timestamp format")
|
raise HTTPException(status_code=400, detail=f"Invalid timestamp format: {str(e)}")
|
||||||
|
|
||||||
# TODO: Verify signature
|
# TODO: Verify signature
|
||||||
# expected_signature = hmac_sha256(session_id + request_token + timestamp, device_secret)
|
# expected_signature = hmac_sha256(session_id + request_token + timestamp, device_secret)
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ input::placeholder{color:rgba(255,255,255,.3)}
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API_BASE = 'https://api.quixzoom.com/v1';
|
const API_BASE = 'https://api.quixzoom.com';
|
||||||
const TOKEN_KEY = '***';
|
const TOKEN_KEY = '***';
|
||||||
const REFRESH_KEY = '***';
|
const REFRESH_KEY = '***';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user