6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
156 lines
5.3 KiB
Python
156 lines
5.3 KiB
Python
"""
|
|
Device fingerprinting utilities
|
|
Generates unique fingerprints for devices and browsers
|
|
"""
|
|
|
|
import hashlib
|
|
import json
|
|
from typing import Dict, Optional, Any
|
|
|
|
|
|
class DeviceFingerprint:
|
|
"""
|
|
Device fingerprinting for security
|
|
|
|
Creates unique fingerprints from device/browser characteristics
|
|
to detect anomalies and prevent replay attacks
|
|
"""
|
|
|
|
@staticmethod
|
|
def generate_browser_fingerprint(
|
|
user_agent: Optional[str] = None,
|
|
accept_language: Optional[str] = None,
|
|
screen_resolution: Optional[str] = None,
|
|
timezone: Optional[str] = None,
|
|
fonts: Optional[list] = None,
|
|
canvas_hash: Optional[str] = None,
|
|
webgl_hash: Optional[str] = None,
|
|
plugins: Optional[list] = None
|
|
) -> str:
|
|
"""
|
|
Generate browser fingerprint from available characteristics
|
|
|
|
Args:
|
|
user_agent: Browser user agent string
|
|
accept_language: Accept-Language header
|
|
screen_resolution: Screen resolution (e.g., "1920x1080")
|
|
timezone: Browser timezone
|
|
fonts: List of detected fonts
|
|
canvas_hash: Canvas fingerprinting hash
|
|
webgl_hash: WebGL fingerprinting hash
|
|
plugins: List of browser plugins
|
|
|
|
Returns:
|
|
str: SHA-256 hash of fingerprint components
|
|
"""
|
|
components = {
|
|
"user_agent": user_agent or "",
|
|
"accept_language": accept_language or "",
|
|
"screen_resolution": screen_resolution or "",
|
|
"timezone": timezone or "",
|
|
"fonts": sorted(fonts) if fonts else [],
|
|
"canvas_hash": canvas_hash or "",
|
|
"webgl_hash": webgl_hash or "",
|
|
"plugins": sorted(plugins) if plugins else []
|
|
}
|
|
|
|
# Create deterministic JSON string
|
|
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
|
|
|
|
# Hash the fingerprint
|
|
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
|
|
|
|
@staticmethod
|
|
def generate_device_fingerprint(
|
|
platform: str,
|
|
os_version: Optional[str] = None,
|
|
device_model: Optional[str] = None,
|
|
hardware_id: Optional[str] = None,
|
|
app_version: Optional[str] = None,
|
|
install_id: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Generate mobile device fingerprint
|
|
|
|
Args:
|
|
platform: Device platform (ios/android)
|
|
os_version: OS version
|
|
device_model: Device model
|
|
hardware_id: Hardware identifier
|
|
app_version: App version
|
|
install_id: Unique installation ID
|
|
|
|
Returns:
|
|
str: SHA-256 hash of device components
|
|
"""
|
|
components = {
|
|
"platform": platform.lower(),
|
|
"os_version": os_version or "",
|
|
"device_model": device_model or "",
|
|
"hardware_id": hardware_id or "",
|
|
"app_version": app_version or "",
|
|
"install_id": install_id or ""
|
|
}
|
|
|
|
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
|
|
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
|
|
|
|
@staticmethod
|
|
def generate_simple_fingerprint(
|
|
user_agent: Optional[str] = None,
|
|
ip_address: Optional[str] = None,
|
|
accept_headers: Optional[Dict[str, str]] = None
|
|
) -> str:
|
|
"""
|
|
Generate simple fingerprint from request headers
|
|
|
|
Used as fallback when detailed fingerprinting is not available
|
|
"""
|
|
components = {
|
|
"user_agent": user_agent or "",
|
|
"ip_address": ip_address or "",
|
|
"accept": accept_headers.get("accept", "") if accept_headers else "",
|
|
"accept_language": accept_headers.get("accept-language", "") if accept_headers else "",
|
|
"accept_encoding": accept_headers.get("accept-encoding", "") if accept_headers else ""
|
|
}
|
|
|
|
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
|
|
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
|
|
|
|
@staticmethod
|
|
def compare_fingerprints(
|
|
fingerprint1: str,
|
|
fingerprint2: str,
|
|
tolerance: float = 0.0
|
|
) -> bool:
|
|
"""
|
|
Compare two fingerprints
|
|
|
|
Args:
|
|
fingerprint1: First fingerprint
|
|
fingerprint2: Second fingerprint
|
|
tolerance: Similarity threshold (0.0 = exact match)
|
|
|
|
Returns:
|
|
bool: True if fingerprints match within tolerance
|
|
"""
|
|
if tolerance == 0.0:
|
|
return fingerprint1 == fingerprint2
|
|
|
|
# For fuzzy matching, we could implement similarity algorithms
|
|
# For now, exact match only
|
|
return fingerprint1 == fingerprint2
|
|
|
|
@staticmethod
|
|
def extract_fingerprint_components(fingerprint_hash: str) -> Dict[str, Any]:
|
|
"""
|
|
Extract components from a fingerprint (if stored separately)
|
|
|
|
Note: This is a placeholder - in production, components would be
|
|
stored alongside the hash for analysis
|
|
"""
|
|
return {
|
|
"hash": fingerprint_hash,
|
|
"algorithm": "sha256",
|
|
"components_stored": False
|
|
} |