Files
Gondulf/src/gondulf/dependencies.py
Phil Skentelbery 9135edfe84 fix(auth): require email authentication every login
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>
2025-11-22 15:16:26 -07:00

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)