Files
boc/iom/quixzoom-auth-service/utils/device_fingerprint.py
T
Bernt 6989a98d75 feat: Passwordless cross-device authentication
- 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
2026-07-07 07:11:50 +00:00

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
}