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

@@ -1,11 +1,12 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import text
from datetime import datetime, timedelta
from typing import Dict, Any
import uuid
from app.db.database import get_db
from app.models.models import License, Activation, Version
from app.models.models import License, DeviceRegistration, LicenseSession, Version
from app.schemas.license import (
LicenseActivationRequest,
LicenseActivationResponse,
@@ -47,58 +48,72 @@ async def activate_license(
message="License has expired"
)
existing_activations = db.query(Activation).filter(
Activation.license_id == license.id,
Activation.is_active == True
existing_devices = db.query(DeviceRegistration).filter(
DeviceRegistration.license_id == license.id,
DeviceRegistration.is_active == True
).all()
existing_machine = next(
(a for a in existing_activations if a.machine_id == request.machine_id),
existing_device = next(
(d for d in existing_devices if d.hardware_fingerprint == request.hardware_fingerprint),
None
)
if existing_machine:
if existing_machine.hardware_hash != request.hardware_hash:
return LicenseActivationResponse(
success=False,
message="Hardware mismatch for this machine"
)
existing_machine.last_heartbeat = datetime.utcnow()
existing_machine.app_version = request.app_version
existing_machine.os_info = request.os_info
if existing_device:
# Update existing device info
existing_device.last_seen_at = datetime.utcnow()
existing_device.app_version = request.app_version
existing_device.device_metadata = request.os_info
existing_device.device_name = request.machine_name
db.commit()
return LicenseActivationResponse(
success=True,
message="License reactivated successfully",
activation_id=existing_machine.id,
activation_id=existing_device.id,
expires_at=license.expires_at,
features={"all_features": True}
)
if len(existing_activations) >= license.max_activations:
if len(existing_devices) >= license.device_limit:
return LicenseActivationResponse(
success=False,
message=f"Maximum activations ({license.max_activations}) reached"
message=f"Device limit ({license.device_limit}) reached"
)
new_activation = Activation(
# Determine device type from OS info
device_type = 'unknown'
if request.os_info:
os_string = str(request.os_info).lower()
if 'windows' in os_string:
device_type = 'desktop'
elif 'mac' in os_string or 'darwin' in os_string:
device_type = 'desktop'
elif 'linux' in os_string:
device_type = 'desktop'
elif 'android' in os_string:
device_type = 'mobile'
elif 'ios' in os_string:
device_type = 'mobile'
new_device = DeviceRegistration(
license_id=license.id,
machine_id=request.machine_id,
hardware_hash=request.hardware_hash,
os_info=request.os_info,
app_version=request.app_version
device_name=request.machine_name,
hardware_fingerprint=request.hardware_fingerprint,
device_type=device_type,
operating_system=request.os_info.get('os', 'unknown') if isinstance(request.os_info, dict) else str(request.os_info),
metadata=request.os_info,
app_version=request.app_version,
ip_address=request.ip_address if hasattr(request, 'ip_address') else None
)
db.add(new_activation)
db.add(new_device)
db.commit()
db.refresh(new_activation)
db.refresh(new_device)
return LicenseActivationResponse(
success=True,
message="License activated successfully",
activation_id=new_activation.id,
activation_id=new_device.id,
expires_at=license.expires_at,
features={"all_features": True}
)
@@ -109,19 +124,20 @@ async def verify_license(
db: Session = Depends(get_db),
api_key: str = Depends(validate_api_key)
):
activation = db.query(Activation).filter(
Activation.id == request.activation_id,
Activation.machine_id == request.machine_id,
Activation.is_active == True
device = db.query(DeviceRegistration).filter(
DeviceRegistration.id == request.activation_id,
DeviceRegistration.device_name == request.machine_name,
DeviceRegistration.hardware_fingerprint == request.hardware_fingerprint,
DeviceRegistration.is_active == True
).first()
if not activation:
if not device:
return LicenseVerificationResponse(
valid=False,
message="Invalid activation"
)
license = activation.license
license = device.license
if not license.is_active:
return LicenseVerificationResponse(
@@ -135,36 +151,22 @@ async def verify_license(
message="License key mismatch"
)
if activation.hardware_hash != request.hardware_hash:
grace_period = datetime.utcnow() - timedelta(days=settings.OFFLINE_GRACE_PERIOD_DAYS)
if activation.last_heartbeat < grace_period:
return LicenseVerificationResponse(
valid=False,
message="Hardware mismatch and grace period expired"
)
else:
return LicenseVerificationResponse(
valid=True,
message="Hardware mismatch but within grace period",
expires_at=license.expires_at,
features={"all_features": True}
)
if license.expires_at and license.expires_at < datetime.utcnow():
return LicenseVerificationResponse(
valid=False,
message="License has expired"
)
activation.last_heartbeat = datetime.utcnow()
# Update last seen timestamp
device.last_seen_at = datetime.utcnow()
db.commit()
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
requires_update = False
update_url = None
if latest_version and activation.app_version:
if latest_version.version_number > activation.app_version:
if latest_version and device.app_version:
if latest_version.version_number > device.app_version:
requires_update = True
update_url = latest_version.download_url
@@ -190,9 +192,9 @@ async def get_license_info(
if not license:
raise HTTPException(status_code=404, detail="License not found")
activations = db.query(Activation).filter(
Activation.license_id == license.id,
Activation.is_active == True
devices = db.query(DeviceRegistration).filter(
DeviceRegistration.license_id == license.id,
DeviceRegistration.is_active == True
).all()
return {
@@ -202,17 +204,19 @@ async def get_license_info(
"customer_name": license.customer_name,
"is_active": license.is_active,
"expires_at": license.expires_at,
"max_activations": license.max_activations,
"current_activations": len(activations),
"activations": [
"device_limit": license.device_limit,
"current_devices": len(devices),
"devices": [
{
"id": a.id,
"machine_id": a.machine_id,
"activation_date": a.activation_date,
"last_heartbeat": a.last_heartbeat,
"app_version": a.app_version
"id": d.id,
"hardware_fingerprint": d.hardware_fingerprint,
"device_name": d.device_name,
"device_type": d.device_type,
"first_activated_at": d.first_activated_at,
"last_seen_at": d.last_seen_at,
"app_version": d.app_version
}
for a in activations
for d in devices
]
}
@@ -222,12 +226,12 @@ async def start_session(
db: Session = Depends(get_db),
api_key: str = Depends(validate_api_key)
):
# Verify API key matches client config
# Get client config (API key already validated by dependency)
from sqlalchemy import text
result = db.execute(text("SELECT api_key, current_version, minimum_version FROM client_configs WHERE client_name = 'Account Forger'")).first()
result = db.execute(text("SELECT current_version, minimum_version FROM client_configs WHERE client_name = 'Account Forger'")).first()
if not result or result.api_key != api_key:
raise HTTPException(status_code=401, detail="Invalid API key")
if not result:
raise HTTPException(status_code=404, detail="Client configuration not found")
# Check if version is supported
if request.version < result.minimum_version:
@@ -260,47 +264,96 @@ async def start_session(
session_token=None
)
# Check for existing active session
existing_session_result = db.execute(
text("SELECT session_token, hardware_id FROM license_sessions WHERE license_id = :license_id"),
# Get license details with concurrent_sessions_limit
license_details = db.execute(
text("SELECT concurrent_sessions_limit FROM licenses WHERE id = :license_id"),
{"license_id": license.id}
).first()
if existing_session_result:
if existing_session_result.hardware_id == request.hardware_id:
# Same device, update heartbeat
db.execute(
text("UPDATE license_sessions SET last_heartbeat = CURRENT_TIMESTAMP WHERE session_token = :token"),
{"token": existing_session_result.session_token}
)
db.commit()
return SessionStartResponse(
success=True,
message="Existing session resumed",
session_token=existing_session_result.session_token,
requires_update=request.version < result.current_version,
update_url=None,
whats_new=None
)
else:
max_concurrent_sessions = license_details.concurrent_sessions_limit if license_details else 1
# Check if device is registered
device = db.query(DeviceRegistration).filter(
DeviceRegistration.license_id == license.id,
DeviceRegistration.hardware_fingerprint == request.hardware_fingerprint,
DeviceRegistration.is_active == True
).first()
if not device:
# Register new device if under limit
device_count = db.query(DeviceRegistration).filter(
DeviceRegistration.license_id == license.id,
DeviceRegistration.is_active == True
).count()
if device_count >= license.device_limit:
return SessionStartResponse(
success=False,
message="Es ist nur eine Sitzung erlaubt, stelle sicher, dass nirgendwo sonst das Programm läuft",
message=f"Device limit ({license.device_limit}) reached",
session_token=None
)
# Register device
device = DeviceRegistration(
license_id=license.id,
hardware_fingerprint=request.hardware_fingerprint,
device_name=request.machine_name,
device_type='desktop',
app_version=request.version,
ip_address=request.ip_address
)
db.add(device)
db.commit()
db.refresh(device)
# Check if this device already has an active session
existing_session = db.execute(
text("SELECT session_token FROM license_sessions WHERE license_id = :license_id AND device_registration_id = :device_id AND ended_at IS NULL"),
{"license_id": license.id, "device_id": device.id}
).first()
if existing_session:
# Same device, update heartbeat
db.execute(
text("UPDATE license_sessions SET last_heartbeat = CURRENT_TIMESTAMP WHERE session_token = :token"),
{"token": existing_session.session_token}
)
db.commit()
return SessionStartResponse(
success=True,
message="Existing session resumed",
session_token=existing_session.session_token,
requires_update=request.version < result.current_version,
update_url=None,
whats_new=None
)
# Count active sessions for this license
active_sessions_count = db.execute(
text("SELECT COUNT(*) FROM license_sessions WHERE license_id = :license_id AND ended_at IS NULL"),
{"license_id": license.id}
).scalar()
if active_sessions_count >= max_concurrent_sessions:
return SessionStartResponse(
success=False,
message=f"Maximum concurrent sessions ({max_concurrent_sessions}) reached. Please close another session first.",
session_token=None
)
# Create new session
session_token = str(uuid.uuid4())
db.execute(
text("""
INSERT INTO license_sessions (license_id, hardware_id, ip_address, client_version, session_token)
VALUES (:license_id, :hardware_id, :ip_address, :version, :token)
INSERT INTO license_sessions (license_id, device_registration_id, hardware_fingerprint, ip_address, client_version, session_token)
VALUES (:license_id, :device_id, :hardware_fingerprint, :ip_address, :version, :token)
"""),
{
"license_id": license.id,
"hardware_id": request.hardware_id,
"device_id": device.id,
"hardware_fingerprint": request.hardware_fingerprint,
"ip_address": request.ip_address,
"version": request.version,
"token": session_token
@@ -356,7 +409,7 @@ async def end_session(
# Get session info before deleting
session_info = db.execute(
text("""
SELECT license_id, hardware_id, ip_address, client_version, started_at
SELECT license_id, hardware_fingerprint, machine_name, ip_address, client_version, started_at
FROM license_sessions
WHERE session_token = :token
"""),
@@ -372,21 +425,22 @@ async def end_session(
# Log to session history
db.execute(
text("""
INSERT INTO session_history (license_id, hardware_id, ip_address, client_version, started_at, ended_at, end_reason)
VALUES (:license_id, :hardware_id, :ip_address, :version, :started, CURRENT_TIMESTAMP, 'normal')
INSERT INTO session_history (license_id, hardware_fingerprint, machine_name, ip_address, client_version, started_at, ended_at, end_reason)
VALUES (:license_id, :hardware_fingerprint, :machine_name, :ip_address, :version, :started, CURRENT_TIMESTAMP, 'normal')
"""),
{
"license_id": session_info.license_id,
"hardware_id": session_info.hardware_id,
"hardware_fingerprint": session_info.hardware_fingerprint,
"machine_name": session_info.machine_name if session_info.machine_name else None,
"ip_address": session_info.ip_address,
"version": session_info.client_version,
"started": session_info.started_at
}
)
# Delete the session
# Mark session as ended instead of deleting it
db.execute(
text("DELETE FROM license_sessions WHERE session_token = :token"),
text("UPDATE license_sessions SET ended_at = CURRENT_TIMESTAMP, end_reason = 'normal' WHERE session_token = :token"),
{"token": request.session_token}
)