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>
6.9 KiB
Phase 3 Token Endpoint - Clarification Responses
Date: 2025-11-20 Architect: Claude (Architect Agent) Developer Questions: 8 clarifications needed Status: All questions answered
Summary of Decisions
All 8 clarification questions have been addressed with clear, specific architectural decisions prioritizing simplicity. See ADR-0009 for formal documentation of these decisions.
Question-by-Question Responses
1. Authorization Code Storage Format (CRITICAL) ✅
Question: Phase 1 CodeStore only accepts string values, but Phase 3 needs dict metadata. Should we modify CodeStore or handle serialization elsewhere?
DECISION: Modify CodeStore to accept dict values with internal JSON serialization.
Implementation:
# Update CodeStore in Phase 1
def store(self, key: str, value: Union[str, dict], ttl: int = 600) -> None:
"""Store key-value pair. Value can be string or dict."""
if isinstance(value, dict):
value_to_store = json.dumps(value)
else:
value_to_store = value
# ... rest of implementation
def get(self, key: str) -> Optional[Union[str, dict]]:
"""Get value. Returns dict if stored value is JSON."""
# ... retrieve value
try:
return json.loads(value)
except (json.JSONDecodeError, TypeError):
return value
Rationale: Simplest approach that maintains backward compatibility while supporting Phase 2/3 needs.
2. Authorization Code Single-Use Marking ✅
Question: How to mark code as "used" before token generation? Calculate remaining TTL?
DECISION: Simplify - just check 'used' flag, then delete after successful generation. No marking.
Implementation:
# Check if already used
if metadata.get('used'):
raise HTTPException(400, {"error": "invalid_grant"})
# Generate token...
# Delete code after success (single-use enforcement)
code_storage.delete(code)
Rationale: Eliminates TTL calculation complexity and race condition concerns.
3. Token Endpoint Error Response Format ✅
Question: Does FastAPI handle dict detail correctly? Need cache headers?
DECISION: FastAPI handles dict→JSON automatically. Add cache headers explicitly.
Implementation:
@router.post("/token")
async def token_exchange(response: Response, ...):
response.headers["Cache-Control"] = "no-store"
response.headers["Pragma"] = "no-cache"
# FastAPI HTTPException with dict detail works correctly
Rationale: Use framework capabilities, ensure OAuth compliance with explicit headers.
4. Phase 2/3 Authorization Code Structure ✅
Question: Will Phase 2 include PKCE fields? Should Phase 3 handle missing keys?
DECISION: Phase 2 MUST include all fields with defaults. Phase 3 assumes complete structure.
Phase 2 Update Required:
code_data = {
'client_id': client_id,
'redirect_uri': redirect_uri,
'state': state,
'me': verified_email,
'scope': scope,
'code_challenge': code_challenge or "", # Empty if not provided
'code_challenge_method': code_challenge_method or "",
'created_at': int(time.time()),
'expires_at': int(time.time() + 600),
'used': False # Always False initially
}
Rationale: Consistency within v1.0.0 is more important than backward compatibility.
5. Database Connection Pattern ✅
Question: Does get_connection() auto-commit or need explicit commit?
DECISION: Explicit commit required (Phase 1 pattern).
Implementation:
with self.database.get_connection() as conn:
conn.execute(query, params)
conn.commit() # Required
Rationale: Matches SQLite default behavior and Phase 1 implementation.
6. Token Hash Collision Handling ✅
Question: Should we handle UNIQUE constraint violations defensively?
DECISION: NO defensive handling. Let it fail catastrophically.
Implementation:
# No try/except for UNIQUE constraint
# If 2^256 collision occurs, something is fundamentally broken
conn.execute("INSERT INTO tokens ...", params)
conn.commit()
# Let any IntegrityError propagate
Rationale: With 2^256 entropy, collision indicates fundamental system failure. Retrying won't help.
7. Logging Token Validation ✅
Question: What logging levels for token operations?
DECISION: Adopt Developer's suggestion:
- DEBUG: Successful validations (high volume)
- INFO: Token generation (important events)
- WARNING: Validation failures (potential issues)
Implementation:
# Success (frequent, not interesting)
logger.debug(f"Token validated successfully (me: {token_data['me']})")
# Generation (important)
logger.info(f"Token generated for {me} (client: {client_id})")
# Failure (potential attack/misconfiguration)
logger.warning(f"Token validation failed: {reason}")
Rationale: Appropriate visibility without log flooding.
8. Token Cleanup Configuration ✅
Question: Should cleanup_expired_tokens() be called automatically?
DECISION: Manual/cron only for v1.0.0. No automatic calling.
Implementation:
# Utility method only
def cleanup_expired_tokens(self) -> int:
"""Delete expired tokens. Call manually or via cron."""
# Implementation as designed
# Config vars exist but unused in v1.0.0:
# TOKEN_CLEANUP_ENABLED (ignored)
# TOKEN_CLEANUP_INTERVAL (ignored)
Rationale: Simplicity for v1.0.0 MVP. Small scale doesn't need automatic cleanup.
Required Changes Before Phase 3 Implementation
Phase 1 Changes
- Update CodeStore to handle dict values with JSON serialization
- Update CodeStore type hints to Union[str, dict]
Phase 2 Changes
- Add PKCE fields to authorization code metadata (even if empty)
- Add 'used' field (always False initially)
- Add created_at/expires_at as epoch integers
Phase 3 Implementation Notes
- Assume complete metadata structure from Phase 2
- No defensive programming for token collisions
- No automatic token cleanup
- Explicit cache headers for OAuth compliance
Design Updates
The original Phase 3 design document remains valid with these clarifications:
- Line 509: Remove mark-as-used step, go directly to delete after generation
- Line 685: Note that TOKEN_CLEANUP_* configs exist but aren't used in v1.0.0
- Line 1163: Simplify single-use enforcement to check-and-delete
Next Steps
- Developer implements Phase 1 CodeStore changes
- Developer updates Phase 2 authorization code structure
- Developer proceeds with Phase 3 implementation using these clarifications
- No further architectural review needed unless new issues arise
ARCHITECTURAL CLARIFICATIONS COMPLETE
All 8 questions have been answered with specific implementation guidance. The Developer can proceed with Phase 3 implementation immediately after making the minor updates to Phase 1 and Phase 2.
Remember: When in doubt, choose the simpler solution. We're building v1.0.0, not the perfect system.