fix(templates): handle optional PKCE in retry links

The "Request a new code" and "Try Again" links were outputting
code_challenge=None when PKCE was not used, causing the retry
to fail with validation errors.

Now only includes PKCE parameters if code_challenge has a value.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-17 15:40:39 -07:00
parent 404d723ef8
commit d63040b014
6 changed files with 284 additions and 4 deletions

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Test script to verify client_id validation compliance with W3C IndieAuth spec Section 3.2."""
import sys
from urllib.parse import urlparse
# Import the validation function
sys.path.insert(0, '/home/phil/Projects/Gondulf/src')
from gondulf.utils.validation import normalize_client_id
def test_client_id(client_id: str, should_pass: bool, reason: str):
"""Test a single client_id against the spec."""
try:
result = normalize_client_id(client_id)
passed = True
print(f"✓ Accepted: {client_id} -> {result}")
except ValueError as e:
passed = False
print(f"✗ Rejected: {client_id} ({str(e)})")
if passed != should_pass:
print(f" ERROR: Expected {'pass' if should_pass else 'fail'}: {reason}")
return False
return True
def main():
"""Run comprehensive client_id validation tests."""
print("Testing client_id validation against W3C IndieAuth spec Section 3.2")
print("=" * 70)
tests = [
# Valid client_ids per spec
("https://example.com", True, "Valid HTTPS URL with domain"),
("https://example.com/", True, "Valid HTTPS URL with path /"),
("https://example.com/app", True, "Valid HTTPS URL with path"),
("https://example.com/app/client", True, "Valid HTTPS URL with multiple path segments"),
("https://example.com?foo=bar", True, "Valid HTTPS URL with query string"),
("https://example.com:8080", True, "Valid HTTPS URL with port"),
("https://example.com:8080/app", True, "Valid HTTPS URL with port and path"),
("http://localhost", True, "Valid HTTP localhost (loopback allowed)"),
("http://localhost:3000", True, "Valid HTTP localhost with port"),
("http://127.0.0.1", True, "Valid HTTP IPv4 loopback"),
("http://127.0.0.1:3000", True, "Valid HTTP IPv4 loopback with port"),
("http://[::1]", True, "Valid HTTP IPv6 loopback"),
("http://[::1]:3000", True, "Valid HTTP IPv6 loopback with port"),
# Invalid client_ids per spec
("http://example.com", False, "HTTP not allowed for non-loopback"),
("https://example.com#fragment", False, "Fragment not allowed"),
("https://user:pass@example.com", False, "Username/password not allowed"),
("https://192.168.1.1", False, "IPv4 address not allowed (except 127.0.0.1)"),
("https://[2001:db8::1]", False, "IPv6 address not allowed (except ::1)"),
("https://10.0.0.1", False, "Private IPv4 not allowed"),
("https://172.16.0.1", False, "Private IPv4 not allowed"),
("example.com", False, "Missing scheme"),
("ftp://example.com", False, "Invalid scheme (not http/https)"),
("https://", False, "Missing hostname"),
("https://example.com/../secret", False, "Path with .. segments not allowed"),
("https://example.com/./secret", False, "Path with . segments not allowed"),
# Edge cases that spec might not explicitly cover
("HTTPS://EXAMPLE.COM", True, "Uppercase scheme/host should be normalized"),
("https://example.com:443", True, "Default HTTPS port should be normalized"),
("http://localhost:80", True, "Default HTTP port on localhost"),
("https://xn--e1afmkfd.xn--p1ai", True, "Internationalized domain names (IDN)"),
]
all_passed = True
pass_count = 0
fail_count = 0
for client_id, should_pass, reason in tests:
if test_client_id(client_id, should_pass, reason):
pass_count += 1
else:
fail_count += 1
all_passed = False
print("\n" + "=" * 70)
print(f"Results: {pass_count} passed, {fail_count} failed")
if all_passed:
print("\n✅ All tests passed! client_id validation appears spec-compliant.")
else:
print("\n❌ Some tests failed. Review the validation logic against the spec.")
return 0 if all_passed else 1
if __name__ == "__main__":
sys.exit(main())