fix: Add trailing slash to SITE_URL and enhance debug logging (v0.9.1)
Fix 1: SITE_URL trailing slash normalization - IndieLogin.com requires client_id URLs to have trailing slash for root domains - Added automatic normalization in load_config() after env loading - Added secondary normalization after config overrides (for test compatibility) - Fixes "client_id is not registered" authentication errors - Updated redirect_uri construction to avoid double slashes Fix 2: Enhanced httpx debug logging - Added detailed request logging before token exchange POST - Added detailed response logging after token exchange POST - Shows exact HTTP method, URL, headers, and body for troubleshooting - All sensitive data (tokens, verifiers) automatically redacted - Supplements existing _log_http_request/_log_http_response helpers Version: 0.9.1 (PATCH - bug fixes) - Updated __version__ in starpunk/__init__.py - Added CHANGELOG entry for v0.9.1 Tests: 486/514 passing (28 pre-existing failures from v0.8.0) - No new test failures introduced - Trailing slash normalization verified in config - Debug logging outputs verified Related: IndieLogin.com authentication flow Following: docs/standards/git-branching-strategy.md Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.1] - 2025-11-19
|
||||
|
||||
### Fixed
|
||||
- **IndieAuth client_id trailing slash**: Added automatic trailing slash normalization to SITE_URL
|
||||
- IndieLogin.com spec requires client_id URLs to have trailing slash for root domains
|
||||
- Fixes "client_id is not registered" authentication errors
|
||||
- Normalizes https://example.com to https://example.com/
|
||||
- **Enhanced debug logging**: Added detailed httpx request/response logging for token exchange
|
||||
- Shows exact HTTP method, URL, headers, and body being sent to IndieLogin.com
|
||||
- Helps troubleshoot authentication issues with full visibility
|
||||
- All sensitive data (tokens, verifiers) automatically redacted
|
||||
|
||||
### Changed
|
||||
- SITE_URL configuration now automatically adds trailing slash if missing
|
||||
|
||||
## [0.9.0] - 2025-11-19
|
||||
|
||||
### Added
|
||||
|
||||
@@ -153,5 +153,5 @@ def create_app(config=None):
|
||||
|
||||
# Package version (Semantic Versioning 2.0.0)
|
||||
# See docs/standards/versioning-strategy.md for details
|
||||
__version__ = "0.9.0"
|
||||
__version_info__ = (0, 9, 0)
|
||||
__version__ = "0.9.1"
|
||||
__version_info__ = (0, 9, 1)
|
||||
|
||||
@@ -322,7 +322,7 @@ def initiate_login(me_url: str) -> str:
|
||||
# Store state and verifier in database (5-minute expiry)
|
||||
db = get_db(current_app)
|
||||
expires_at = datetime.utcnow() + timedelta(minutes=5)
|
||||
redirect_uri = f"{current_app.config['SITE_URL']}/auth/callback"
|
||||
redirect_uri = f"{current_app.config['SITE_URL']}auth/callback"
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
@@ -404,26 +404,52 @@ def handle_callback(code: str, state: str, iss: Optional[str] = None) -> Optiona
|
||||
token_exchange_data = {
|
||||
"code": code,
|
||||
"client_id": current_app.config["SITE_URL"],
|
||||
"redirect_uri": f"{current_app.config['SITE_URL']}/auth/callback",
|
||||
"redirect_uri": f"{current_app.config['SITE_URL']}auth/callback",
|
||||
"code_verifier": code_verifier, # PKCE verification
|
||||
}
|
||||
|
||||
token_url = f"{current_app.config['INDIELOGIN_URL']}/token"
|
||||
|
||||
# Log the request (code_verifier will be redacted)
|
||||
_log_http_request(
|
||||
method="POST",
|
||||
url=f"{current_app.config['INDIELOGIN_URL']}/token",
|
||||
url=token_url,
|
||||
data=token_exchange_data,
|
||||
)
|
||||
|
||||
# Log detailed httpx request info for debugging
|
||||
current_app.logger.debug(
|
||||
"Auth: Sending token exchange request:\n"
|
||||
" Method: POST\n"
|
||||
" URL: %s\n"
|
||||
" Data: code=%s, client_id=%s, redirect_uri=%s, code_verifier=%s",
|
||||
token_url,
|
||||
_redact_token(code),
|
||||
token_exchange_data["client_id"],
|
||||
token_exchange_data["redirect_uri"],
|
||||
_redact_token(code_verifier),
|
||||
)
|
||||
|
||||
# Exchange code for identity (CORRECT ENDPOINT: /token)
|
||||
try:
|
||||
response = httpx.post(
|
||||
f"{current_app.config['INDIELOGIN_URL']}/token",
|
||||
token_url,
|
||||
data=token_exchange_data,
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
# Log the response
|
||||
# Log detailed httpx response info for debugging
|
||||
current_app.logger.debug(
|
||||
"Auth: Received token exchange response:\n"
|
||||
" Status: %d\n"
|
||||
" Headers: %s\n"
|
||||
" Body: %s",
|
||||
response.status_code,
|
||||
{k: v for k, v in dict(response.headers).items() if k.lower() not in ["set-cookie", "authorization"]},
|
||||
_redact_token(response.text) if response.text else "(empty)",
|
||||
)
|
||||
|
||||
# Log the response (legacy helper)
|
||||
_log_http_response(
|
||||
status_code=response.status_code,
|
||||
headers=dict(response.headers),
|
||||
|
||||
@@ -20,7 +20,10 @@ def load_config(app, config_override=None):
|
||||
load_dotenv()
|
||||
|
||||
# Site configuration
|
||||
app.config["SITE_URL"] = os.getenv("SITE_URL", "http://localhost:5000")
|
||||
# IndieWeb/OAuth specs require trailing slash for root URLs used as client_id
|
||||
# See: https://indielogin.com/ OAuth client requirements
|
||||
site_url = os.getenv("SITE_URL", "http://localhost:5000")
|
||||
app.config["SITE_URL"] = site_url if site_url.endswith('/') else site_url + '/'
|
||||
app.config["SITE_NAME"] = os.getenv("SITE_NAME", "StarPunk")
|
||||
app.config["SITE_AUTHOR"] = os.getenv("SITE_AUTHOR", "Unknown")
|
||||
app.config["SITE_DESCRIPTION"] = os.getenv(
|
||||
@@ -73,6 +76,11 @@ def load_config(app, config_override=None):
|
||||
if config_override:
|
||||
app.config.update(config_override)
|
||||
|
||||
# Normalize SITE_URL trailing slash (in case override provided URL without slash)
|
||||
if "SITE_URL" in app.config:
|
||||
site_url = app.config["SITE_URL"]
|
||||
app.config["SITE_URL"] = site_url if site_url.endswith('/') else site_url + '/'
|
||||
|
||||
# Convert path strings to Path objects (in case overrides provided strings)
|
||||
if isinstance(app.config["DATA_PATH"], str):
|
||||
app.config["DATA_PATH"] = Path(app.config["DATA_PATH"])
|
||||
|
||||
Reference in New Issue
Block a user