feat(phase-3): implement token endpoint and OAuth 2.0 flow

Phase 3 Implementation:
- Token service with secure token generation and validation
- Token endpoint (POST /token) with OAuth 2.0 compliance
- Database migration 003 for tokens table
- Authorization code validation and single-use enforcement

Phase 1 Updates:
- Enhanced CodeStore to support dict values with JSON serialization
- Maintains backward compatibility

Phase 2 Updates:
- Authorization codes now include PKCE fields, used flag, timestamps
- Complete metadata structure for token exchange

Security:
- 256-bit cryptographically secure tokens (secrets.token_urlsafe)
- SHA-256 hashed storage (no plaintext)
- Constant-time comparison for validation
- Single-use code enforcement with replay detection

Testing:
- 226 tests passing (100%)
- 87.27% coverage (exceeds 80% requirement)
- OAuth 2.0 compliance verified

This completes the v1.0.0 MVP with full IndieAuth authorization code flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 14:24:06 -07:00
parent 074f74002c
commit 05b4ff7a6b
18 changed files with 4049 additions and 26 deletions

View File

@@ -40,6 +40,10 @@ class Config:
TOKEN_EXPIRY: int
CODE_EXPIRY: int
# Token Cleanup (Phase 3)
TOKEN_CLEANUP_ENABLED: bool
TOKEN_CLEANUP_INTERVAL: int
# Logging
LOG_LEVEL: str
DEBUG: bool
@@ -82,6 +86,10 @@ class Config:
cls.TOKEN_EXPIRY = int(os.getenv("GONDULF_TOKEN_EXPIRY", "3600"))
cls.CODE_EXPIRY = int(os.getenv("GONDULF_CODE_EXPIRY", "600"))
# Token Cleanup Configuration
cls.TOKEN_CLEANUP_ENABLED = os.getenv("GONDULF_TOKEN_CLEANUP_ENABLED", "false").lower() == "true"
cls.TOKEN_CLEANUP_INTERVAL = int(os.getenv("GONDULF_TOKEN_CLEANUP_INTERVAL", "3600"))
# Logging
cls.DEBUG = os.getenv("GONDULF_DEBUG", "false").lower() == "true"
# If DEBUG is true, default LOG_LEVEL to DEBUG, otherwise INFO
@@ -108,16 +116,26 @@ class Config:
f"GONDULF_SMTP_PORT must be between 1 and 65535, got {cls.SMTP_PORT}"
)
# Validate expiry times are positive
if cls.TOKEN_EXPIRY <= 0:
# Validate expiry times are positive and within bounds
if cls.TOKEN_EXPIRY < 300: # Minimum 5 minutes
raise ConfigurationError(
f"GONDULF_TOKEN_EXPIRY must be positive, got {cls.TOKEN_EXPIRY}"
"GONDULF_TOKEN_EXPIRY must be at least 300 seconds (5 minutes)"
)
if cls.TOKEN_EXPIRY > 86400: # Maximum 24 hours
raise ConfigurationError(
"GONDULF_TOKEN_EXPIRY must be at most 86400 seconds (24 hours)"
)
if cls.CODE_EXPIRY <= 0:
raise ConfigurationError(
f"GONDULF_CODE_EXPIRY must be positive, got {cls.CODE_EXPIRY}"
)
# Validate cleanup interval if enabled
if cls.TOKEN_CLEANUP_ENABLED and cls.TOKEN_CLEANUP_INTERVAL < 600:
raise ConfigurationError(
"GONDULF_TOKEN_CLEANUP_INTERVAL must be at least 600 seconds (10 minutes)"
)
# Configuration is loaded lazily or explicitly by the application
# Tests should call Config.load() explicitly in fixtures