This fixes critical IndieAuth authentication by implementing PKCE (Proof Key for Code Exchange) as required by IndieLogin.com API specification. Added: - PKCE code_verifier and code_challenge generation (RFC 7636) - Database column: auth_state.code_verifier for PKCE support - Issuer validation for authentication callbacks - Comprehensive PKCE unit tests (6 tests, all passing) - Database migration script for code_verifier column Changed: - Corrected IndieLogin.com API endpoints (/authorize and /token) - State token validation now returns code_verifier for token exchange - Authentication flow follows IndieLogin.com API specification exactly - Enhanced logging with code_verifier redaction Removed: - OAuth metadata endpoint (/.well-known/oauth-authorization-server) Added in v0.7.0 but not required by IndieLogin.com - h-app microformats markup from templates Modified in v0.7.1 but not used by IndieLogin.com - indieauth-metadata link from HTML head Security: - PKCE prevents authorization code interception attacks - Issuer validation prevents token substitution attacks - Code verifier securely stored, redacted in logs, and single-use Documentation: - Version: 0.8.0 - CHANGELOG updated with v0.8.0 entry and v0.7.x notes - ADR-016 and ADR-017 marked as superseded by ADR-019 - Implementation report created in docs/reports/ - Test update guide created in TODO_TEST_UPDATES.md Breaking Changes: - Users mid-authentication will need to restart login after upgrade - Database migration required before deployment Related: ADR-019 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
"""Tests for PKCE implementation"""
|
|
|
|
import pytest
|
|
from starpunk.auth import _generate_pkce_verifier, _generate_pkce_challenge
|
|
|
|
|
|
def test_generate_pkce_verifier():
|
|
"""Test PKCE verifier generation"""
|
|
verifier = _generate_pkce_verifier()
|
|
|
|
# Length should be 43 characters
|
|
assert len(verifier) == 43
|
|
|
|
# Should only contain URL-safe characters
|
|
assert verifier.replace('-', '').replace('_', '').isalnum()
|
|
|
|
|
|
def test_generate_pkce_verifier_unique():
|
|
"""Test that verifiers are unique"""
|
|
verifier1 = _generate_pkce_verifier()
|
|
verifier2 = _generate_pkce_verifier()
|
|
|
|
assert verifier1 != verifier2
|
|
|
|
|
|
def test_generate_pkce_challenge():
|
|
"""Test PKCE challenge generation with known values"""
|
|
# Example from RFC 7636
|
|
verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
|
challenge = _generate_pkce_challenge(verifier)
|
|
|
|
# Expected challenge for this verifier
|
|
expected = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
|
|
assert challenge == expected
|
|
|
|
|
|
def test_pkce_challenge_deterministic():
|
|
"""Test that challenge is deterministic"""
|
|
verifier = _generate_pkce_verifier()
|
|
challenge1 = _generate_pkce_challenge(verifier)
|
|
challenge2 = _generate_pkce_challenge(verifier)
|
|
|
|
assert challenge1 == challenge2
|
|
|
|
|
|
def test_different_verifiers_different_challenges():
|
|
"""Test that different verifiers produce different challenges"""
|
|
verifier1 = _generate_pkce_verifier()
|
|
verifier2 = _generate_pkce_verifier()
|
|
|
|
challenge1 = _generate_pkce_challenge(verifier1)
|
|
challenge2 = _generate_pkce_challenge(verifier2)
|
|
|
|
assert challenge1 != challenge2
|
|
|
|
|
|
def test_pkce_challenge_length():
|
|
"""Test challenge is correct length"""
|
|
verifier = _generate_pkce_verifier()
|
|
challenge = _generate_pkce_challenge(verifier)
|
|
|
|
# SHA256 hash -> 32 bytes -> 43 characters base64url (no padding)
|
|
assert len(challenge) == 43
|