Files
boc/iom/quixzoom-auth-service/models/auth_request.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

129 lines
4.7 KiB
Python

"""
Auth Request model for passwordless cross-device authentication
Represents an ongoing authentication request between web and device
"""
from datetime import datetime
from typing import Optional
from enum import Enum
from pydantic import BaseModel, Field
import uuid
class AuthRequestStatus(str, Enum):
"""Authentication request status"""
PENDING = "pending" # Waiting for device approval
APPROVED = "approved" # Device approved, ready to complete
REJECTED = "rejected" # Device rejected
EXPIRED = "expired" # Timed out
COMPLETED = "completed" # Successfully completed
CANCELLED = "cancelled" # Cancelled by user
class AuthRequest(BaseModel):
"""Authentication request database model"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
# Request identifiers
request_token: str = Field(..., description="Unique request token (nonce)")
qr_code_data: str = Field(..., description="Data encoded in QR code")
# User and device
user_id: Optional[str] = Field(None, description="User ID if known")
device_id: Optional[str] = Field(None, description="Device that approved/rejected")
# Status tracking
status: AuthRequestStatus = Field(default=AuthRequestStatus.PENDING)
# Security
nonce: str = Field(..., description="Cryptographic nonce for this request")
challenge: str = Field(..., description="Challenge to be signed by device")
challenge_response: Optional[str] = Field(None, description="Device's signed response")
# Device fingerprint at time of request
web_fingerprint: Optional[str] = Field(None, description="Browser fingerprint")
device_fingerprint: Optional[str] = Field(None, description="Device fingerprint at approval")
# IP and location info
web_ip: Optional[str] = Field(None, description="Web client IP")
device_ip: Optional[str] = Field(None, description="Device IP at approval")
# Metadata
web_user_agent: Optional[str] = Field(None)
device_user_agent: Optional[str] = Field(None)
# Timestamps
created_at: datetime = Field(default_factory=datetime.utcnow)
expires_at: datetime = Field(..., description="Request expiration time")
approved_at: Optional[datetime] = Field(None)
completed_at: Optional[datetime] = Field(None)
# Result
session_token: Optional[str] = Field(None, description="Session token after completion")
error_message: Optional[str] = Field(None)
class Config:
json_schema_extra = {
"example": {
"id": "auth_req_123",
"request_token": "tkn_abc123",
"qr_code_data": "quixzoom://auth?token=tkn_abc123&nonce=nonce123",
"status": "pending",
"nonce": "nonce123",
"challenge": "ch_abc123...",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2024-01-15T10:35:00Z"
}
}
class AuthRequestCreate(BaseModel):
"""Request model for initiating authentication"""
user_id: Optional[str] = Field(None, description="User ID if pre-authenticated")
device_fingerprint: Optional[str] = Field(None, description="Browser fingerprint")
user_agent: Optional[str] = Field(None)
ip_address: Optional[str] = Field(None)
class AuthRequestApprove(BaseModel):
"""Request model for device approval"""
request_token: str = Field(..., description="Token from QR code")
device_id: str = Field(..., description="Registered device ID")
challenge_response: str = Field(..., description="Signed challenge response")
device_fingerprint: str = Field(..., description="Current device fingerprint")
user_agent: Optional[str] = Field(None)
ip_address: Optional[str] = Field(None)
approved: bool = Field(default=True, description="Approve or reject")
class AuthRequestResponse(BaseModel):
"""Response model for auth request"""
id: str
request_token: str
qr_code_data: str
status: AuthRequestStatus
user_id: Optional[str] = None
device_id: Optional[str] = None
created_at: datetime
expires_at: datetime
approved_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
class Config:
from_attributes = True
class AuthRequestStatusResponse(BaseModel):
"""Response model for polling auth status"""
status: AuthRequestStatus
request_token: str
device_name: Optional[str] = None
device_platform: Optional[str] = None
approved_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
session_token: Optional[str] = None
error_message: Optional[str] = None
expires_at: datetime
class Config:
from_attributes = True