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
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+101
View File
@@ -0,0 +1,101 @@
"""
Device model for passwordless authentication
Represents a registered mobile device (app)
"""
from datetime import datetime
from typing import Optional
from enum import Enum
from pydantic import BaseModel, Field
import uuid
class DevicePlatform(str, Enum):
"""Supported device platforms"""
IOS = "ios"
ANDROID = "android"
WEB = "web"
OTHER = "other"
class DeviceStatus(str, Enum):
"""Device status"""
ACTIVE = "active"
REVOKED = "revoked"
SUSPENDED = "suspended"
class Device(BaseModel):
"""Device database model"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str = Field(..., description="Associated user ID")
device_name: str = Field(..., description="Human-readable device name")
platform: DevicePlatform = Field(..., description="Device platform")
# Device fingerprinting
public_key: str = Field(..., description="Device public key for challenge signing")
device_fingerprint: str = Field(..., description="Unique device fingerprint hash")
# Metadata
push_token: Optional[str] = Field(None, description="Push notification token")
last_ip: Optional[str] = Field(None, description="Last known IP address")
last_user_agent: Optional[str] = Field(None, description="Last user agent")
# Status
status: DeviceStatus = Field(default=DeviceStatus.ACTIVE)
# Timestamps
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
last_used_at: Optional[datetime] = Field(None)
class Config:
json_schema_extra = {
"example": {
"id": "dev_123456789",
"user_id": "user_abc123",
"device_name": "iPhone 15 Pro",
"platform": "ios",
"public_key": "-----BEGIN PUBLIC KEY-----\n...",
"device_fingerprint": "sha256:abc123...",
"status": "active",
"created_at": "2024-01-15T10:30:00Z"
}
}
class DeviceCreate(BaseModel):
"""Request model for device registration"""
user_id: str = Field(..., description="User ID to associate with device")
device_name: str = Field(..., min_length=1, max_length=100)
platform: DevicePlatform
public_key: str = Field(..., min_length=100, description="Device public key")
device_fingerprint: str = Field(..., min_length=32, description="Device fingerprint")
push_token: Optional[str] = Field(None, max_length=500)
class DeviceUpdate(BaseModel):
"""Request model for device update"""
device_name: Optional[str] = Field(None, min_length=1, max_length=100)
push_token: Optional[str] = Field(None, max_length=500)
status: Optional[DeviceStatus] = None
class DeviceResponse(BaseModel):
"""Response model for device operations"""
id: str
user_id: str
device_name: str
platform: DevicePlatform
status: DeviceStatus
created_at: datetime
last_used_at: Optional[datetime] = None
class Config:
from_attributes = True
class DeviceListResponse(BaseModel):
"""Response model for listing devices"""
devices: list[DeviceResponse]
total: int