Files
StarPunk/tests/test_auth_pkce.py
Phil Skentelbery 5e50330bdf feat: Implement PKCE authentication for IndieLogin.com
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>
2025-11-19 15:43:38 -07:00

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