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
253 lines
7.1 KiB
Python
253 lines
7.1 KiB
Python
"""
|
|
Device Service - Manages device registration and operations
|
|
"""
|
|
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
|
|
from models.device import (
|
|
Device, DeviceCreate, DeviceUpdate, DeviceResponse,
|
|
DeviceStatus, DevicePlatform
|
|
)
|
|
from utils.security import security_manager
|
|
from utils.device_fingerprint import DeviceFingerprint
|
|
|
|
|
|
class DeviceService:
|
|
"""
|
|
Service for managing registered devices
|
|
|
|
Handles:
|
|
- Device registration
|
|
- Device lookup
|
|
- Device revocation
|
|
- Device fingerprint validation
|
|
"""
|
|
|
|
def __init__(self, db_pool=None):
|
|
self.db = db_pool
|
|
|
|
async def register_device(self, device_data: DeviceCreate) -> Device:
|
|
"""
|
|
Register a new device
|
|
|
|
Args:
|
|
device_data: Device registration data
|
|
|
|
Returns:
|
|
Device: Created device
|
|
"""
|
|
# Hash the device fingerprint for storage
|
|
fingerprint_hash = security_manager.hash_fingerprint(device_data.device_fingerprint)
|
|
|
|
device = Device(
|
|
user_id=device_data.user_id,
|
|
device_name=device_data.device_name,
|
|
platform=device_data.platform,
|
|
public_key=device_data.public_key,
|
|
device_fingerprint=fingerprint_hash,
|
|
push_token=device_data.push_token,
|
|
status=DeviceStatus.ACTIVE,
|
|
created_at=datetime.utcnow(),
|
|
updated_at=datetime.utcnow()
|
|
)
|
|
|
|
# Store in database
|
|
if self.db:
|
|
await self._save_device_to_db(device)
|
|
|
|
return device
|
|
|
|
async def get_device(self, device_id: str) -> Optional[Device]:
|
|
"""Get device by ID"""
|
|
if not self.db:
|
|
return None
|
|
|
|
row = await self.db.fetchrow(
|
|
"""
|
|
SELECT * FROM devices WHERE id = $1
|
|
""",
|
|
device_id
|
|
)
|
|
|
|
if row:
|
|
return Device(**dict(row))
|
|
return None
|
|
|
|
async def get_device_by_fingerprint(
|
|
self,
|
|
user_id: str,
|
|
fingerprint: str
|
|
) -> Optional[Device]:
|
|
"""Get device by user ID and fingerprint"""
|
|
if not self.db:
|
|
return None
|
|
|
|
fingerprint_hash = security_manager.hash_fingerprint(fingerprint)
|
|
|
|
row = await self.db.fetchrow(
|
|
"""
|
|
SELECT * FROM devices
|
|
WHERE user_id = $1 AND device_fingerprint = $2 AND status = 'active'
|
|
""",
|
|
user_id,
|
|
fingerprint_hash
|
|
)
|
|
|
|
if row:
|
|
return Device(**dict(row))
|
|
return None
|
|
|
|
async def get_user_devices(self, user_id: str) -> List[DeviceResponse]:
|
|
"""Get all devices for a user"""
|
|
if not self.db:
|
|
return []
|
|
|
|
rows = await self.db.fetch(
|
|
"""
|
|
SELECT id, user_id, device_name, platform, status, created_at, last_used_at
|
|
FROM devices
|
|
WHERE user_id = $1
|
|
ORDER BY last_used_at DESC NULLS LAST
|
|
""",
|
|
user_id
|
|
)
|
|
|
|
return [DeviceResponse(**dict(row)) for row in rows]
|
|
|
|
async def update_device(
|
|
self,
|
|
device_id: str,
|
|
update_data: DeviceUpdate
|
|
) -> Optional[Device]:
|
|
"""Update device information"""
|
|
device = await self.get_device(device_id)
|
|
if not device:
|
|
return None
|
|
|
|
# Update fields
|
|
if update_data.device_name:
|
|
device.device_name = update_data.device_name
|
|
if update_data.push_token is not None:
|
|
device.push_token = update_data.push_token
|
|
if update_data.status:
|
|
device.status = update_data.status
|
|
|
|
device.updated_at = datetime.utcnow()
|
|
|
|
if self.db:
|
|
await self._update_device_in_db(device)
|
|
|
|
return device
|
|
|
|
async def revoke_device(self, device_id: str, reason: str = "") -> bool:
|
|
"""
|
|
Revoke a device
|
|
|
|
Args:
|
|
device_id: Device to revoke
|
|
reason: Reason for revocation
|
|
|
|
Returns:
|
|
bool: True if revoked successfully
|
|
"""
|
|
device = await self.get_device(device_id)
|
|
if not device:
|
|
return False
|
|
|
|
device.status = DeviceStatus.REVOKED
|
|
device.updated_at = datetime.utcnow()
|
|
|
|
if self.db:
|
|
await self.db.execute(
|
|
"""
|
|
UPDATE devices
|
|
SET status = 'revoked', updated_at = NOW()
|
|
WHERE id = $1
|
|
""",
|
|
device_id
|
|
)
|
|
|
|
return True
|
|
|
|
async def validate_device(
|
|
self,
|
|
device_id: str,
|
|
fingerprint: Optional[str] = None
|
|
) -> bool:
|
|
"""
|
|
Validate device is active and optionally check fingerprint
|
|
|
|
Args:
|
|
device_id: Device ID
|
|
fingerprint: Optional fingerprint to validate
|
|
|
|
Returns:
|
|
bool: True if device is valid
|
|
"""
|
|
device = await self.get_device(device_id)
|
|
if not device:
|
|
return False
|
|
|
|
if device.status != DeviceStatus.ACTIVE:
|
|
return False
|
|
|
|
if fingerprint:
|
|
fingerprint_hash = security_manager.hash_fingerprint(fingerprint)
|
|
if device.device_fingerprint != fingerprint_hash:
|
|
return False
|
|
|
|
return True
|
|
|
|
async def update_last_used(self, device_id: str, ip_address: Optional[str] = None):
|
|
"""Update last used timestamp"""
|
|
if self.db:
|
|
await self.db.execute(
|
|
"""
|
|
UPDATE devices
|
|
SET last_used_at = NOW(), last_ip = $2
|
|
WHERE id = $1
|
|
""",
|
|
device_id,
|
|
ip_address
|
|
)
|
|
|
|
async def _save_device_to_db(self, device: Device):
|
|
"""Save device to database"""
|
|
await self.db.execute(
|
|
"""
|
|
INSERT INTO devices (
|
|
id, user_id, device_name, platform, public_key,
|
|
device_fingerprint, push_token, status, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
""",
|
|
device.id,
|
|
device.user_id,
|
|
device.device_name,
|
|
device.platform.value,
|
|
device.public_key,
|
|
device.device_fingerprint,
|
|
device.push_token,
|
|
device.status.value,
|
|
device.created_at,
|
|
device.updated_at
|
|
)
|
|
|
|
async def _update_device_in_db(self, device: Device):
|
|
"""Update device in database"""
|
|
await self.db.execute(
|
|
"""
|
|
UPDATE devices
|
|
SET device_name = $2, push_token = $3, status = $4, updated_at = $5
|
|
WHERE id = $1
|
|
""",
|
|
device.id,
|
|
device.device_name,
|
|
device.push_token,
|
|
device.status.value,
|
|
device.updated_at
|
|
)
|
|
|
|
|
|
# Global device service instance
|
|
device_service = DeviceService() |