feat: Implement secure token management for Micropub

Implements token security and management as specified in ADR-029:

Database Changes (BREAKING):
- Add secure tokens table with SHA256 hashed storage
- Add authorization_codes table for IndieAuth token exchange
- Drop old insecure tokens table (invalidates existing tokens)
- Update SCHEMA_SQL to match post-migration state

Token Management (starpunk/tokens.py):
- Generate cryptographically secure tokens
- Hash tokens with SHA256 for secure storage
- Create and verify access tokens
- Create and exchange authorization codes
- PKCE support (optional but recommended)
- Scope validation (V1: only 'create' scope)
- Token expiry and revocation support

Testing:
- Comprehensive test suite for all token operations
- Test authorization code replay protection
- Test PKCE validation
- Test parameter validation
- Test token expiry

Security:
- Tokens never stored in plain text
- Authorization codes single-use with replay protection
- Optional PKCE for enhanced security
- Proper UTC datetime handling for expiry

Related:
- ADR-029: Micropub IndieAuth Integration Strategy
- Migration 002: Secure tokens and authorization codes

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-24 11:52:09 -07:00
parent e2333cb31d
commit 3b41029c75
5 changed files with 924 additions and 9 deletions

View File

@@ -52,7 +52,7 @@ def is_schema_current(conn):
Check if database schema is current (matches SCHEMA_SQL)
Uses heuristic: Check for presence of latest schema features
Currently checks for code_verifier column in auth_state table
Currently checks for authorization_codes table and token_hash column in tokens table
Args:
conn: SQLite connection
@@ -61,11 +61,17 @@ def is_schema_current(conn):
bool: True if schema appears current, False if legacy
"""
try:
cursor = conn.execute("PRAGMA table_info(auth_state)")
columns = [row[1] for row in cursor.fetchall()]
return 'code_verifier' in columns
# Check for authorization_codes table (added in migration 002)
if not table_exists(conn, 'authorization_codes'):
return False
# Check for token_hash column in tokens table (migration 002)
if not column_exists(conn, 'tokens', 'token_hash'):
return False
return True
except sqlite3.OperationalError:
# Table doesn't exist - definitely not current
# Schema check failed - definitely not current
return False