CRITICAL SECURITY FIX: - Email code required EVERY login (authentication, not verification) - DNS TXT check cached separately (domain verification) - New auth_sessions table for per-login state - Codes hashed with SHA-256, constant-time comparison - Max 3 attempts, 10-minute session expiry - OAuth params stored server-side (security improvement) New files: - services/auth_session.py - migrations 004, 005 - ADR-010: domain verification vs user authentication 312 tests passing, 86.21% coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
129 lines
3.4 KiB
Python
129 lines
3.4 KiB
Python
"""FastAPI dependency injection for services."""
|
|
from functools import lru_cache
|
|
|
|
from gondulf.config import Config
|
|
from gondulf.database.connection import Database
|
|
from gondulf.dns import DNSService
|
|
from gondulf.email import EmailService
|
|
from gondulf.services.auth_session import AuthSessionService
|
|
from gondulf.services.domain_verification import DomainVerificationService
|
|
from gondulf.services.happ_parser import HAppParser
|
|
from gondulf.services.html_fetcher import HTMLFetcherService
|
|
from gondulf.services.rate_limiter import RateLimiter
|
|
from gondulf.services.relme_parser import RelMeParser
|
|
from gondulf.services.token_service import TokenService
|
|
from gondulf.storage import CodeStore
|
|
|
|
|
|
# Configuration
|
|
@lru_cache
|
|
def get_config() -> Config:
|
|
"""Get configuration instance."""
|
|
return Config
|
|
|
|
|
|
# Phase 1 Services
|
|
@lru_cache
|
|
def get_database() -> Database:
|
|
"""Get singleton database service."""
|
|
config = get_config()
|
|
db = Database(config.DATABASE_URL)
|
|
db.initialize()
|
|
return db
|
|
|
|
|
|
@lru_cache
|
|
def get_code_storage() -> CodeStore:
|
|
"""Get singleton code storage service."""
|
|
config = get_config()
|
|
return CodeStore(ttl_seconds=config.CODE_EXPIRY)
|
|
|
|
|
|
@lru_cache
|
|
def get_email_service() -> EmailService:
|
|
"""Get singleton email service."""
|
|
config = get_config()
|
|
return EmailService(
|
|
smtp_host=config.SMTP_HOST,
|
|
smtp_port=config.SMTP_PORT,
|
|
smtp_from=config.SMTP_FROM,
|
|
smtp_username=config.SMTP_USERNAME,
|
|
smtp_password=config.SMTP_PASSWORD,
|
|
smtp_use_tls=config.SMTP_USE_TLS
|
|
)
|
|
|
|
|
|
@lru_cache
|
|
def get_dns_service() -> DNSService:
|
|
"""Get singleton DNS service."""
|
|
return DNSService()
|
|
|
|
|
|
# Phase 2 Services
|
|
@lru_cache
|
|
def get_html_fetcher() -> HTMLFetcherService:
|
|
"""Get singleton HTML fetcher service."""
|
|
return HTMLFetcherService()
|
|
|
|
|
|
@lru_cache
|
|
def get_relme_parser() -> RelMeParser:
|
|
"""Get singleton rel=me parser service."""
|
|
return RelMeParser()
|
|
|
|
|
|
@lru_cache
|
|
def get_happ_parser() -> HAppParser:
|
|
"""Get singleton h-app parser service."""
|
|
return HAppParser(html_fetcher=get_html_fetcher())
|
|
|
|
|
|
@lru_cache
|
|
def get_rate_limiter() -> RateLimiter:
|
|
"""Get singleton rate limiter service."""
|
|
return RateLimiter(max_attempts=3, window_hours=1)
|
|
|
|
|
|
@lru_cache
|
|
def get_verification_service() -> DomainVerificationService:
|
|
"""Get singleton domain verification service."""
|
|
return DomainVerificationService(
|
|
dns_service=get_dns_service(),
|
|
email_service=get_email_service(),
|
|
code_storage=get_code_storage(),
|
|
html_fetcher=get_html_fetcher(),
|
|
relme_parser=get_relme_parser()
|
|
)
|
|
|
|
|
|
# Phase 3 Services
|
|
@lru_cache
|
|
def get_token_service() -> TokenService:
|
|
"""
|
|
Get TokenService singleton.
|
|
|
|
Returns cached instance for dependency injection.
|
|
"""
|
|
database = get_database()
|
|
config = get_config()
|
|
|
|
return TokenService(
|
|
database=database,
|
|
token_length=32, # 256 bits
|
|
token_ttl=config.TOKEN_EXPIRY # From environment (default: 3600)
|
|
)
|
|
|
|
|
|
# Auth Session Service (for per-login authentication)
|
|
@lru_cache
|
|
def get_auth_session_service() -> AuthSessionService:
|
|
"""
|
|
Get AuthSessionService singleton.
|
|
|
|
Handles per-login authentication via email verification.
|
|
This is separate from domain verification (DNS check).
|
|
See ADR-010 for the architectural decision.
|
|
"""
|
|
database = get_database()
|
|
return AuthSessionService(database=database)
|