Add latest changes

Dieser Commit ist enthalten in:
2025-07-03 20:38:33 +00:00
Ursprung 63f3d92724
Commit 6f6cde65db
129 geänderte Dateien mit 3998 neuen und 1199 gelöschten Zeilen

Datei anzeigen

@@ -11,10 +11,25 @@ logger = logging.getLogger(__name__)
async def validate_api_key(request: Request, db: Session = Depends(get_db)):
"""Validate API key from X-API-Key header against system_api_key table"""
# Debug: Log all headers
headers_dict = dict(request.headers)
# logger.warning(f"DEBUG: All headers: {headers_dict}")
# logger.warning(f"DEBUG: Header keys: {list(headers_dict.keys())}")
# Try different variations
api_key = request.headers.get("X-API-Key")
if not api_key:
api_key = request.headers.get("x-api-key")
if not api_key:
# Try case-insensitive search
for key, value in headers_dict.items():
if key.lower() == "x-api-key":
api_key = value
logger.warning(f"DEBUG: Found API key under header: {key}")
break
if not api_key:
logger.warning("API request without API key")
logger.warning(f"API request without API key. Headers: {list(request.headers.keys())}")
raise HTTPException(
status_code=401,
detail="API key required",
@@ -22,13 +37,43 @@ async def validate_api_key(request: Request, db: Session = Depends(get_db)):
)
# Query the system API key
cursor = db.execute(
text("SELECT api_key FROM system_api_key WHERE id = 1")
)
result = cursor.fetchone()
try:
cursor = db.execute(
text("SELECT api_key FROM system_api_key WHERE id = 1")
)
result = cursor.fetchone()
# logger.warning(f"DEBUG: DB query result: {result}")
except Exception as e:
logger.error(f"Database query error: {str(e)}")
raise HTTPException(
status_code=500,
detail="Database error"
)
if not result or result[0] != api_key:
logger.warning(f"Invalid API key attempt: {api_key[:8]}...")
if not result:
logger.warning(f"No API key found in database")
raise HTTPException(
status_code=401,
detail="Invalid API key"
)
logger.warning(f"DEBUG: Found API key in request: {api_key}")
logger.warning(f"DEBUG: API key from DB: {result[0]}")
logger.warning(f"DEBUG: API key match: {result[0] == api_key}")
logger.warning(f"DEBUG: Types - DB: {type(result[0])}, Request: {type(api_key)}")
if result[0] != api_key:
logger.warning(f"API key mismatch!")
logger.warning(f"Expected (DB): '{result[0]}'")
logger.warning(f"Got (Request): '{api_key}'")
logger.warning(f"API key lengths - DB: {len(result[0])}, Request: {len(api_key)}")
# Character by character comparison
for i, (c1, c2) in enumerate(zip(result[0], api_key)):
if c1 != c2:
logger.warning(f"First difference at position {i}: DB='{c1}' (ord={ord(c1)}), Request='{c2}' (ord={ord(c2)})")
break
raise HTTPException(
status_code=401,
detail="Invalid API key"

Datei anzeigen

@@ -10,7 +10,7 @@ class Settings(BaseSettings):
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
DATABASE_URL: str = "postgresql://license_user:license_password@db:5432/license_db"
DATABASE_URL: str = "postgresql://adminuser:supergeheimespasswort@postgres:5432/meinedatenbank"
ALLOWED_ORIGINS: List[str] = [

Datei anzeigen

@@ -0,0 +1,116 @@
"""
Background scheduler for License Server
Handles periodic tasks like session cleanup
"""
import logging
from datetime import datetime, timedelta
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.db.database import SessionLocal
logger = logging.getLogger(__name__)
scheduler = BackgroundScheduler()
def cleanup_expired_sessions():
"""Clean up sessions that haven't sent heartbeat within timeout period"""
db: Session = SessionLocal()
try:
# Get session timeout from config (default 60 seconds)
result = db.execute(text("""
SELECT session_timeout
FROM client_configs
WHERE client_name = 'Account Forger'
""")).first()
timeout_seconds = result[0] if result else 60
# Find expired sessions
expired_sessions = db.execute(text(f"""
SELECT id, license_id, hardware_fingerprint, session_token
FROM license_sessions
WHERE ended_at IS NULL
AND last_heartbeat < NOW() - INTERVAL '{timeout_seconds} seconds'
""")).fetchall()
if expired_sessions:
logger.info(f"Found {len(expired_sessions)} expired sessions to clean up")
# Mark sessions as ended
for session in expired_sessions:
db.execute(text("""
UPDATE license_sessions
SET ended_at = NOW(), end_reason = 'timeout'
WHERE id = :session_id
"""), {"session_id": session[0]})
logger.info(f"Ended session {session[0]} for license {session[1]} due to timeout")
db.commit()
logger.info(f"Successfully cleaned up {len(expired_sessions)} expired sessions")
except Exception as e:
logger.error(f"Error cleaning up sessions: {str(e)}")
db.rollback()
finally:
db.close()
def cleanup_old_sessions():
"""Remove old ended sessions from database (older than 30 days)"""
db: Session = SessionLocal()
try:
result = db.execute(text("""
DELETE FROM license_sessions
WHERE ended_at IS NOT NULL
AND ended_at < NOW() - INTERVAL '30 days'
"""))
if result.rowcount > 0:
db.commit()
logger.info(f"Cleaned up {result.rowcount} old sessions")
except Exception as e:
logger.error(f"Error cleaning up old sessions: {str(e)}")
db.rollback()
finally:
db.close()
def init_scheduler():
"""Initialize and start the background scheduler"""
# Add job to cleanup expired sessions every 30 seconds
scheduler.add_job(
func=cleanup_expired_sessions,
trigger=IntervalTrigger(seconds=30),
id='cleanup_expired_sessions',
name='Cleanup expired sessions',
replace_existing=True
)
# Add job to cleanup old sessions daily at 3 AM
scheduler.add_job(
func=cleanup_old_sessions,
trigger='cron',
hour=3,
minute=0,
id='cleanup_old_sessions',
name='Cleanup old sessions',
replace_existing=True
)
scheduler.start()
logger.info("Background scheduler started")
logger.info("- Session cleanup runs every 30 seconds")
logger.info("- Old session cleanup runs daily at 3:00 AM")
def shutdown_scheduler():
"""Shutdown the scheduler gracefully"""
if scheduler.running:
scheduler.shutdown()
logger.info("Background scheduler stopped")