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:
93
test_client_id_spec_compliance.py
Normal file
93
test_client_id_spec_compliance.py
Normal 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())
|
||||
Reference in New Issue
Block a user