- Add logging helper functions with automatic token redaction - Implement comprehensive logging throughout auth flow - Add production warning for DEBUG logging - Add 14 new tests for logging functionality - Update version to v0.7.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
27 KiB
ADR-018: IndieAuth Detailed Logging Strategy
Status
Accepted
Context
StarPunk uses IndieLogin.com as a delegated IndieAuth provider for admin authentication. During development and production deployments, authentication issues can be difficult to debug because we lack visibility into the OAuth flow between StarPunk and IndieLogin.com.
Authentication Flow Overview
The IndieAuth flow involves multiple HTTP requests:
- Authorization Request: Browser redirects user to IndieLogin.com
- User Authentication: IndieLogin.com verifies user identity
- Callback: IndieLogin.com redirects back to StarPunk with authorization code
- Token Exchange: StarPunk exchanges code for verified identity via POST to IndieLogin.com
- Session Creation: StarPunk creates local session
Current Logging Limitations
The current implementation (starpunk/auth.py) has minimal logging:
- Line 194:
current_app.logger.info(f"Auth initiated for {me_url}") - Line 232:
current_app.logger.error(f"IndieLogin request failed: {e}") - Line 235:
current_app.logger.error(f"IndieLogin returned error: {e}") - Line 299:
current_app.logger.info(f"Session created for {me}")
Problems:
- No visibility into HTTP request/response details
- Cannot see what is being sent to IndieLogin.com
- Cannot see what IndieLogin.com responds with
- Difficult to debug state token issues
- Hard to troubleshoot OAuth flow problems
Use Cases for Detailed Logging
- Debugging Authentication Failures: See exact error responses from IndieLogin.com
- Verifying Request Format: Ensure parameters are correctly formatted
- State Token Debugging: Track state token lifecycle
- Production Troubleshooting: Diagnose issues without exposing sensitive data
- Compliance Verification: Confirm IndieAuth spec compliance
Decision
Implement structured, security-aware logging for IndieAuth authentication flows
We will add detailed logging to the authentication module that captures HTTP requests and responses while protecting sensitive data through automatic redaction.
Logging Architecture
1. Log Level Strategy
DEBUG: Verbose HTTP details (requests, responses, headers, bodies)
INFO: Authentication flow milestones (initiate, callback, session created)
WARNING: Suspicious activity (unauthorized attempts, invalid states)
ERROR: Authentication failures and exceptions
2. Configuration-Based Control
Logging verbosity controlled via LOG_LEVEL environment variable:
LOG_LEVEL=DEBUG: Full HTTP request/response logging with redactionLOG_LEVEL=INFO: Flow milestones only (default)LOG_LEVEL=WARNING: Only warnings and errorsLOG_LEVEL=ERROR: Only errors
3. Security-First Design
Automatic Redaction:
- Authorization codes: Show first 6 and last 4 characters only
- State tokens: Show first 8 and last 4 characters only
- Session tokens: Never log (already hashed before storage)
- Authorization headers: Redact token values
Production Warning:
- Log clear warning if DEBUG logging enabled in production
- Recommend INFO level for production environments
Implementation Specification
Files to Modify
- starpunk/auth.py - Add logging to authentication functions
- starpunk/config.py - Already has LOG_LEVEL configuration (line 58)
- starpunk/app.py - Configure logger based on LOG_LEVEL (if not already done)
Where to Add Logging
Function: initiate_login(me_url: str) (lines 148-196)
- After line 163: DEBUG log validated URL
- After line 166: DEBUG log generated state token (redacted)
- After line 191: DEBUG log full authorization URL being constructed
- Before line 194: DEBUG log redirect URI and parameters
Function: handle_callback(code: str, state: str) (lines 199-258)
- After line 216: DEBUG log state token verification (redacted tokens)
- Before line 221: DEBUG log token exchange request preparation
- After line 229: DEBUG log complete HTTP request to IndieLogin.com
- After line 239: DEBUG log complete HTTP response from IndieLogin.com
- After line 240: DEBUG log parsed identity (me URL)
- After line 246: INFO log admin verification check
Function: create_session(me: str) (lines 261-301)
- After line 272: DEBUG log session token generation (do NOT log plaintext)
- After line 277: DEBUG log session expiry calculation
- After line 280: DEBUG log request metadata (IP, user agent)
Logging Helper Functions
Add these helper functions to starpunk/auth.py:
def _redact_token(token: str, prefix_len: int = 6, suffix_len: int = 4) -> str:
"""
Redact sensitive token for logging
Shows first N and last M characters with asterisks in between.
Args:
token: Token to redact
prefix_len: Number of characters to show at start
suffix_len: Number of characters to show at end
Returns:
Redacted token string like "abc123...****...xyz9"
"""
if not token or len(token) <= (prefix_len + suffix_len):
return "***REDACTED***"
return f"{token[:prefix_len]}...{'*' * 8}...{token[-suffix_len:]}"
def _log_http_request(method: str, url: str, data: dict, headers: dict = None) -> None:
"""
Log HTTP request details at DEBUG level
Automatically redacts sensitive parameters (code, state, authorization)
Args:
method: HTTP method (GET, POST, etc.)
url: Request URL
data: Request data/parameters
headers: Optional request headers
"""
if not current_app.logger.isEnabledFor(logging.DEBUG):
return
# Redact sensitive data
safe_data = data.copy()
if 'code' in safe_data:
safe_data['code'] = _redact_token(safe_data['code'])
if 'state' in safe_data:
safe_data['state'] = _redact_token(safe_data['state'], 8, 4)
current_app.logger.debug(
f"IndieAuth HTTP Request:\n"
f" Method: {method}\n"
f" URL: {url}\n"
f" Data: {safe_data}"
)
if headers:
safe_headers = {k: v for k, v in headers.items()
if k.lower() not in ['authorization', 'cookie']}
current_app.logger.debug(f" Headers: {safe_headers}")
def _log_http_response(status_code: int, headers: dict, body: str) -> None:
"""
Log HTTP response details at DEBUG level
Automatically redacts sensitive response data
Args:
status_code: HTTP status code
headers: Response headers
body: Response body (JSON string or text)
"""
if not current_app.logger.isEnabledFor(logging.DEBUG):
return
# Parse and redact JSON body if present
safe_body = body
try:
import json
data = json.loads(body)
if 'access_token' in data:
data['access_token'] = _redact_token(data['access_token'])
if 'code' in data:
data['code'] = _redact_token(data['code'])
safe_body = json.dumps(data, indent=2)
except (json.JSONDecodeError, TypeError):
# Not JSON or parsing failed, log as-is (likely error message)
pass
# Redact sensitive headers
safe_headers = {k: v for k, v in headers.items()
if k.lower() not in ['set-cookie', 'authorization']}
current_app.logger.debug(
f"IndieAuth HTTP Response:\n"
f" Status: {status_code}\n"
f" Headers: {safe_headers}\n"
f" Body: {safe_body}"
)
Integration with httpx Requests
Modify the token exchange in handle_callback() (lines 221-236):
# Before making request
_log_http_request(
method="POST",
url=f"{current_app.config['INDIELOGIN_URL']}/auth",
data={
"code": code,
"client_id": current_app.config["SITE_URL"],
"redirect_uri": f"{current_app.config['SITE_URL']}/auth/callback",
}
)
# Exchange code for identity
try:
response = httpx.post(
f"{current_app.config['INDIELOGIN_URL']}/auth",
data={
"code": code,
"client_id": current_app.config["SITE_URL"],
"redirect_uri": f"{current_app.config['SITE_URL']}/auth/callback",
},
timeout=10.0,
)
# Log response
_log_http_response(
status_code=response.status_code,
headers=dict(response.headers),
body=response.text
)
response.raise_for_status()
except httpx.RequestError as e:
current_app.logger.error(f"IndieLogin request failed: {e}")
raise IndieLoginError(f"Failed to verify code: {e}")
Log Message Formats
DEBUG Level Examples
DEBUG - Auth: Validating me URL: https://example.com
DEBUG - Auth: Generated state token: a1b2c3d4...********...xyz9
DEBUG - Auth: Building authorization URL with params: {
'me': 'https://example.com',
'client_id': 'https://starpunk.example.com',
'redirect_uri': 'https://starpunk.example.com/auth/callback',
'state': 'a1b2c3d4...********...xyz9',
'response_type': 'code'
}
DEBUG - Auth: IndieAuth HTTP Request:
Method: POST
URL: https://indielogin.com/auth
Data: {
'code': 'abc123...********...def9',
'client_id': 'https://starpunk.example.com',
'redirect_uri': 'https://starpunk.example.com/auth/callback'
}
DEBUG - Auth: IndieAuth HTTP Response:
Status: 200
Headers: {'content-type': 'application/json', 'content-length': '42'}
Body: {
"me": "https://example.com"
}
INFO Level Examples
INFO - Auth: Authentication initiated for https://example.com
INFO - Auth: Verifying admin authorization for me=https://example.com
INFO - Auth: Session created for https://example.com
WARNING Level Examples
WARNING - Auth: Unauthorized login attempt: https://unauthorized.example.com (expected https://authorized.example.com)
WARNING - Auth: Invalid state token received (possible CSRF or expired token)
WARNING - Auth: Multiple failed authentication attempts from IP 192.168.1.100
ERROR Level Examples
ERROR - Auth: IndieLogin request failed: Connection timeout
ERROR - Auth: IndieLogin returned error: 400
ERROR - Auth: Invalid state error: Invalid or expired state token
Configuration Approach
Environment Variable
Already implemented in config.py (line 58):
app.config["LOG_LEVEL"] = os.getenv("LOG_LEVEL", "INFO")
Logger Configuration
Add to starpunk/app.py (or wherever Flask app is initialized):
import logging
def configure_logging(app):
"""Configure application logging based on LOG_LEVEL"""
log_level = app.config.get("LOG_LEVEL", "INFO").upper()
# Set Flask logger level
app.logger.setLevel(getattr(logging, log_level, logging.INFO))
# Configure handler with detailed format for DEBUG
handler = logging.StreamHandler()
if log_level == "DEBUG":
formatter = logging.Formatter(
'[%(asctime)s] %(levelname)s - %(name)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Warn if DEBUG enabled in production
if not app.debug and app.config.get("ENV") != "development":
app.logger.warning(
"=" * 70 + "\n"
"WARNING: DEBUG logging enabled in production!\n"
"This logs detailed HTTP requests/responses.\n"
"Sensitive data is redacted, but consider using INFO level.\n"
"Set LOG_LEVEL=INFO in production for normal operation.\n"
+ "=" * 70
)
else:
formatter = logging.Formatter(
'[%(asctime)s] %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
app.logger.addHandler(handler)
Security Safeguards
1. Automatic Redaction
- All logging helper functions redact sensitive data by default
- No way to log unredacted tokens (by design)
- Redaction applies even at DEBUG level
2. Production Warning
- Clear warning logged if DEBUG enabled in non-development environment
- Recommends INFO level for production
- Does not prevent DEBUG (allows troubleshooting), just warns
3. Minimal Data Exposure
- Only log what is necessary for debugging
- Prefer logging outcomes over raw data
- Session tokens never logged in plaintext (always hashed)
4. Structured Logging
- Consistent format makes parsing easier
- Clear prefixes identify auth-related logs
- Machine-readable for log aggregation tools
5. Level-Based Control
- DEBUG: Maximum visibility (development/troubleshooting)
- INFO: Normal operation (production default)
- WARNING: Security events only
- ERROR: Failures only
Rationale
Why This Approach?
Simplicity Score: 8/10
- Uses Python's built-in logging module
- No additional dependencies
- Helper functions are straightforward
- Configuration via single environment variable
Fitness Score: 10/10
- Solves exact problem: debugging IndieAuth flows
- Security-aware by design (automatic redaction)
- Developer-friendly output format
- Production-safe with appropriate configuration
Maintenance Score: 9/10
- Standard Python logging patterns
- Self-contained helper functions
- No external logging services required
- Easy to extend for future needs
Standards Compliance: Pass
- Follows Python logging best practices
- Compatible with standard log aggregation tools
- No proprietary logging formats
- OWASP-compliant sensitive data handling
Why Redaction Over Disabling?
We choose to redact sensitive data rather than completely disable logging because:
- Partial visibility is valuable: Seeing token prefixes/suffixes helps identify which token is being used
- Format verification: Can verify tokens are properly formatted without seeing full value
- Troubleshooting: Can track token lifecycle through redacted values
- Safe default: Developers can enable DEBUG without accidentally exposing secrets
Why Not Use External Logging Service?
For V1, we explicitly reject external logging services (Sentry, LogRocket, etc.) because:
- Simplicity: Adds dependency and complexity
- Privacy: Sends data to third-party service
- Self-hosting: Violates principle of self-contained system
- Unnecessary: Standard logging sufficient for single-user system
This could be reconsidered for V2 if needed.
Consequences
Positive
- ✅ Debuggability: Easy to diagnose IndieAuth issues
- ✅ Security-Aware: Automatic redaction prevents accidental exposure
- ✅ Configurable: Single environment variable controls verbosity
- ✅ Production-Safe: INFO level appropriate for production
- ✅ No Dependencies: Uses built-in Python logging
- ✅ Developer-Friendly: Clear, readable log output
- ✅ Standards-Compliant: Follows logging best practices
- ✅ Maintainable: Simple helper functions, easy to extend
Negative
- ⚠️ Log Volume: DEBUG level produces significant output
- Mitigation: Use INFO level in production, DEBUG only for troubleshooting
- ⚠️ Performance: String formatting has minor overhead
- Mitigation: Logging helpers check if DEBUG enabled before formatting
- ⚠️ Partial Visibility: Redaction means full tokens not visible
- Mitigation: Intentional trade-off for security; redacted portions still useful
Neutral
-
Storage Requirements: DEBUG logs require more disk space
- Expected: Temporary DEBUG usage for troubleshooting only
- Production INFO logs are minimal
-
Learning Curve: Developers must understand log levels
- Documented in configuration and inline comments
- Standard Python logging concepts
Examples
Example 1: Successful Authentication Flow (DEBUG)
[2025-11-19 14:30:00] DEBUG - Auth: Validating me URL: https://thesatelliteoflove.com
[2025-11-19 14:30:00] DEBUG - Auth: Generated state token: a1b2c3d4...********...wxyz
[2025-11-19 14:30:00] DEBUG - Auth: Building authorization URL with params: {
'me': 'https://thesatelliteoflove.com',
'client_id': 'https://starpunk.thesatelliteoflove.com',
'redirect_uri': 'https://starpunk.thesatelliteoflove.com/auth/callback',
'state': 'a1b2c3d4...********...wxyz',
'response_type': 'code'
}
[2025-11-19 14:30:00] INFO - Auth: Authentication initiated for https://thesatelliteoflove.com
[2025-11-19 14:30:15] DEBUG - Auth: Verifying state token: a1b2c3d4...********...wxyz
[2025-11-19 14:30:15] DEBUG - Auth: State token valid and consumed
[2025-11-19 14:30:15] DEBUG - Auth: IndieAuth HTTP Request:
Method: POST
URL: https://indielogin.com/auth
Data: {
'code': 'xyz789...********...abc1',
'client_id': 'https://starpunk.thesatelliteoflove.com',
'redirect_uri': 'https://starpunk.thesatelliteoflove.com/auth/callback'
}
[2025-11-19 14:30:16] DEBUG - Auth: IndieAuth HTTP Response:
Status: 200
Headers: {
'content-type': 'application/json',
'content-length': '52'
}
Body: {
"me": "https://thesatelliteoflove.com"
}
[2025-11-19 14:30:16] DEBUG - Auth: Received identity from IndieLogin: https://thesatelliteoflove.com
[2025-11-19 14:30:16] INFO - Auth: Verifying admin authorization for me=https://thesatelliteoflove.com
[2025-11-19 14:30:16] DEBUG - Auth: Admin verification passed
[2025-11-19 14:30:16] DEBUG - Auth: Session token generated (hash will be stored)
[2025-11-19 14:30:16] DEBUG - Auth: Session expiry: 2025-12-19 14:30:16 (30 days)
[2025-11-19 14:30:16] DEBUG - Auth: Request metadata - IP: 192.168.1.100, User-Agent: Mozilla/5.0...
[2025-11-19 14:30:16] INFO - Auth: Session created for https://thesatelliteoflove.com
Example 2: Failed Authentication (INFO Level)
[2025-11-19 14:35:00] INFO - Auth: Authentication initiated for https://unauthorized.example.com
[2025-11-19 14:35:15] WARNING - Auth: Unauthorized login attempt: https://unauthorized.example.com (expected https://thesatelliteoflove.com)
Example 3: IndieLogin Service Error (DEBUG)
[2025-11-19 14:40:00] INFO - Auth: Authentication initiated for https://thesatelliteoflove.com
[2025-11-19 14:40:15] DEBUG - Auth: Verifying state token: def456...********...ghi9
[2025-11-19 14:40:15] DEBUG - Auth: State token valid and consumed
[2025-11-19 14:40:15] DEBUG - Auth: IndieAuth HTTP Request:
Method: POST
URL: https://indielogin.com/auth
Data: {
'code': 'pqr789...********...stu1',
'client_id': 'https://starpunk.thesatelliteoflove.com',
'redirect_uri': 'https://starpunk.thesatelliteoflove.com/auth/callback'
}
[2025-11-19 14:40:16] DEBUG - Auth: IndieAuth HTTP Response:
Status: 400
Headers: {
'content-type': 'application/json',
'content-length': '78'
}
Body: {
"error": "invalid_grant",
"error_description": "The authorization code is invalid or has expired"
}
[2025-11-19 14:40:16] ERROR - Auth: IndieLogin returned error: 400
Testing Strategy
Unit Tests
Add to tests/test_auth.py:
def test_redact_token():
"""Test token redaction for logging"""
from starpunk.auth import _redact_token
# Normal token
assert _redact_token("abcdefghijklmnop", 6, 4) == "abcdef...********...mnop"
# Short token (fully redacted)
assert _redact_token("short", 6, 4) == "***REDACTED***"
# Empty token
assert _redact_token("", 6, 4) == "***REDACTED***"
def test_log_http_request_redacts_code(caplog):
"""Test that code parameter is redacted in request logs"""
import logging
from starpunk.auth import _log_http_request
with caplog.at_level(logging.DEBUG):
_log_http_request(
method="POST",
url="https://indielogin.com/auth",
data={"code": "sensitive_code_12345"}
)
# Should log but with redacted code
assert "sensitive_code_12345" not in caplog.text
assert "sensit...********...2345" in caplog.text
def test_log_http_response_redacts_tokens(caplog):
"""Test that response tokens are redacted"""
import logging
from starpunk.auth import _log_http_response
with caplog.at_level(logging.DEBUG):
_log_http_response(
status_code=200,
headers={"content-type": "application/json"},
body='{"access_token": "secret_token_xyz789"}'
)
# Should log but with redacted token
assert "secret_token_xyz789" not in caplog.text
assert "secret...********...x789" in caplog.text
Integration Tests
Add to tests/test_auth_integration.py:
def test_auth_flow_logging_at_debug(client, app, caplog):
"""Test that DEBUG logging captures full auth flow"""
import logging
# Set DEBUG logging
app.logger.setLevel(logging.DEBUG)
with caplog.at_level(logging.DEBUG):
# Initiate authentication
response = client.post('/admin/login', data={'me': 'https://example.com'})
# Should see DEBUG logs
assert "Validating me URL" in caplog.text
assert "Generated state token" in caplog.text
assert "Building authorization URL" in caplog.text
# Should NOT see full token values
assert any(
"...********..." in record.message
for record in caplog.records
if "state token" in record.message
)
def test_auth_flow_logging_at_info(client, app, caplog):
"""Test that INFO logging only shows milestones"""
import logging
# Set INFO logging
app.logger.setLevel(logging.INFO)
with caplog.at_level(logging.INFO):
# Initiate authentication
response = client.post('/admin/login', data={'me': 'https://example.com'})
# Should see INFO milestone
assert "Authentication initiated" in caplog.text
# Should NOT see DEBUG details
assert "Generated state token" not in caplog.text
assert "Building authorization URL" not in caplog.text
Manual Testing
-
Enable DEBUG Logging:
export LOG_LEVEL=DEBUG uv run flask run -
Attempt Authentication:
- Go to
/admin/login - Enter your URL
- Observe console output
- Go to
-
Verify Logging:
- ✅ State token is redacted
- ✅ Authorization code is redacted
- ✅ HTTP request details visible
- ✅ HTTP response details visible
- ✅ Identity (me URL) visible
- ✅ No plaintext session tokens
-
Test Production Mode:
export LOG_LEVEL=INFO export FLASK_ENV=production uv run flask run- ✅ Warning appears if DEBUG was enabled
- ✅ Only milestone logs appear
- ✅ No HTTP details logged
Alternatives Considered
Alternative 1: No Redaction (Rejected)
Approach: Log everything including full tokens
Rejected Because:
- Security risk: Tokens in logs could be compromised
- OWASP violation: Sensitive data in logs
- Production unsafe: Cannot enable DEBUG safely
- Risk of accidental exposure if logs shared
Alternative 2: Complete Disabling at DEBUG (Rejected)
Approach: Don't log sensitive data at all, even redacted
Rejected Because:
- Loses debugging value: Cannot track token lifecycle
- Harder to troubleshoot: No visibility into requests/responses
- Format issues invisible: Cannot verify parameter format
- Redaction provides good balance
Alternative 3: External Logging Service (Rejected)
Approach: Use Sentry, LogRocket, or similar service
Rejected Because:
- Violates simplicity: Additional dependency
- Privacy concern: Data sent to third party
- Self-hosting principle: Requires external service
- Unnecessary complexity: Built-in logging sufficient
- Cost: Most services require payment
Alternative 4: Separate Debug Module (Rejected)
Approach: Create separate debugging module that must be explicitly imported
Rejected Because:
- Extra complexity: Additional module to maintain
- Friction: Developer must remember to import
- Configuration better: Environment variable is simpler
- Built-in logging: Python logging module is standard
Alternative 5: Conditional Compilation (Rejected)
Approach: Use environment variable to enable/disable debug code at startup
Rejected Because:
- Inflexible: Cannot change without restart
- Complexity: Conditional code paths
- Python idiom: Log level checking is standard pattern
- Testing harder: Multiple code paths to test
Migration Path
No migration required:
- No database changes
- No configuration changes required (LOG_LEVEL already optional)
- Backward compatible: Existing code continues working
- Purely additive: New logging functions added
Deployment Steps
- Deploy updated code with logging helpers
- Existing systems continue with INFO logging (default)
- Enable DEBUG logging when troubleshooting needed
- No restart required to change log level (if using dynamic config)
Future Considerations
V2 Potential Enhancements
- Structured JSON Logging: Machine-readable format for log aggregation
- Request ID Tracking: Trace requests across multiple log entries
- Performance Metrics: Log timing for each auth step
- Log Rotation: Automatic log file management
- Audit Trail: Separate audit log for security events
- OpenTelemetry: Distributed tracing support
Logging Best Practices for Future Development
- Consistent Prefixes: All auth logs start with "Auth:"
- Action-Oriented Messages: Use verbs (Validating, Generated, Verifying)
- Context Included: Include relevant identifiers (URLs, IPs)
- Error Details: Include exception messages and stack traces
- Security Events: Log all authentication attempts (success and failure)
Compliance
Security Standards
- ✅ OWASP Logging Cheat Sheet: Sensitive data redaction
- ✅ GDPR: No unnecessary PII in logs (IP addresses justified for security)
- ✅ OAuth 2.0 Security: Token redaction in logs
- ✅ IndieAuth Spec: No spec requirements violated by logging
Project Standards
- ✅ ADR-001: No additional dependencies (uses built-in logging)
- ✅ "Every line of code must justify its existence": Logging justified for debugging
- ✅ Standards-first approach: Python logging standards followed
- ✅ Security-first: Automatic redaction protects sensitive data
Configuration Documentation
Environment Variables
# Logging configuration
LOG_LEVEL=INFO # Options: DEBUG, INFO, WARNING, ERROR (default: INFO)
# For development/troubleshooting
LOG_LEVEL=DEBUG # Enable detailed HTTP logging
# For production (recommended)
LOG_LEVEL=INFO # Standard operation logging
Recommended Settings
Development:
LOG_LEVEL=DEBUG
Staging:
LOG_LEVEL=INFO
Production:
LOG_LEVEL=INFO
Troubleshooting Production Issues:
LOG_LEVEL=DEBUG
# Temporarily enable for debugging, then revert to INFO
References
- Python Logging Documentation
- OWASP Logging Cheat Sheet
- OAuth 2.0 Security Best Current Practice
- IndieAuth Specification
- Flask Logging Documentation
Related Documents
- ADR-005: IndieLogin Authentication (
docs/decisions/ADR-005-indielogin-authentication.md) - ADR-010: Authentication Module Design (
docs/decisions/ADR-010-authentication-module-design.md) - ADR-016: IndieAuth Client Discovery (
docs/decisions/ADR-016-indieauth-client-discovery.md)
Version Impact
Classification: Enhancement Version Increment: Minor (v0.X.0 → v0.X+1.0) Reason: New debugging capability, backward compatible, no breaking changes
Decided: 2025-11-19 Author: StarPunk Architect Agent Supersedes: None Superseded By: None (current)