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

@@ -42,17 +42,41 @@ CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(session_token_has
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_sessions_me ON sessions(me);
-- Micropub access tokens
-- Micropub access tokens (secure storage with hashed tokens)
CREATE TABLE IF NOT EXISTS tokens (
token TEXT PRIMARY KEY,
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_hash TEXT UNIQUE NOT NULL,
me TEXT NOT NULL,
client_id TEXT,
scope TEXT,
scope TEXT DEFAULT 'create',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP,
revoked_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tokens_hash ON tokens(token_hash);
CREATE INDEX IF NOT EXISTS idx_tokens_me ON tokens(me);
CREATE INDEX IF NOT EXISTS idx_tokens_expires ON tokens(expires_at);
-- Authorization codes for IndieAuth token exchange
CREATE TABLE IF NOT EXISTS authorization_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code_hash TEXT UNIQUE NOT NULL,
me TEXT NOT NULL,
client_id TEXT NOT NULL,
redirect_uri TEXT NOT NULL,
scope TEXT,
state TEXT,
code_challenge TEXT,
code_challenge_method TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_auth_codes_hash ON authorization_codes(code_hash);
CREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON authorization_codes(expires_at);
-- CSRF state tokens (for IndieAuth flow)
CREATE TABLE IF NOT EXISTS auth_state (