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
129 lines
4.7 KiB
Python
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 |