""" 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