feat: Complete IndieAuth server removal (Phases 2-4)
Completed all remaining phases of ADR-030 IndieAuth provider removal. StarPunk no longer acts as an authorization server - all IndieAuth operations delegated to external providers. Phase 2 - Remove Token Issuance: - Deleted /auth/token endpoint - Removed token_endpoint() function from routes/auth.py - Deleted tests/test_routes_token.py Phase 3 - Remove Token Storage: - Deleted starpunk/tokens.py module entirely - Created migration 004 to drop tokens and authorization_codes tables - Deleted tests/test_tokens.py - Removed all internal token CRUD operations Phase 4 - External Token Verification: - Created starpunk/auth_external.py module - Implemented verify_external_token() for external IndieAuth providers - Updated Micropub endpoint to use external verification - Added TOKEN_ENDPOINT configuration - Updated all Micropub tests to mock external verification - HTTP timeout protection (5s) for external requests Additional Changes: - Created migration 003 to remove code_verifier from auth_state - Fixed 5 migration tests that referenced obsolete code_verifier column - Updated 11 Micropub tests for external verification - Fixed test fixture and app context issues - All 501 tests passing Breaking Changes: - Micropub clients must use external IndieAuth providers - TOKEN_ENDPOINT configuration now required - Existing internal tokens invalid (tables dropped) Migration Impact: - Simpler codebase: -500 lines of code - Fewer database tables: -2 tables (tokens, authorization_codes) - More secure: External providers handle token security - More maintainable: Less authentication code to maintain Standards Compliance: - W3C IndieAuth specification - OAuth 2.0 Bearer token authentication - IndieWeb principle: delegate to external services Related: - ADR-030: IndieAuth Provider Removal Strategy - ADR-050: Remove Custom IndieAuth Server - Migration 003: Remove code_verifier from auth_state - Migration 004: Drop tokens and authorization_codes tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
267
docs/architecture/indieauth-questions-answered.md
Normal file
267
docs/architecture/indieauth-questions-answered.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# IndieAuth Implementation Questions - Answered
|
||||
|
||||
## Quick Reference
|
||||
|
||||
All architectural questions have been answered. This document provides the concrete guidance needed for implementation.
|
||||
|
||||
## Questions & Answers
|
||||
|
||||
### ✅ Q1: External Token Endpoint Response Format
|
||||
|
||||
**Answer**: Follow the IndieAuth spec exactly (W3C TR).
|
||||
|
||||
**Expected Response**:
|
||||
```json
|
||||
{
|
||||
"me": "https://user.example.net/",
|
||||
"client_id": "https://app.example.com/",
|
||||
"scope": "create update delete"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**: HTTP 400, 401, or 403 for invalid tokens.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Q2: HTML Discovery Headers
|
||||
|
||||
**Answer**: These are links users add to THEIR websites, not StarPunk.
|
||||
|
||||
**User's HTML** (on their personal domain):
|
||||
```html
|
||||
<link rel="authorization_endpoint" href="https://indielogin.com/auth">
|
||||
<link rel="token_endpoint" href="https://tokens.indieauth.com/token">
|
||||
<link rel="micropub" href="https://your-starpunk.example.com/api/micropub">
|
||||
```
|
||||
|
||||
**StarPunk's Role**: Discover these endpoints from the user's URL, don't generate them.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Q3: Migration Strategy
|
||||
|
||||
**Architectural Decision**: Keep migration 002, document it as future-use.
|
||||
|
||||
**Action Items**:
|
||||
1. Keep the migration file as-is
|
||||
2. Add comment: "Tables created for future V2 internal provider support"
|
||||
3. Don't use these tables in V1 (external verification only)
|
||||
4. No impact on existing production databases
|
||||
|
||||
**Rationale**: Empty tables cause no harm, avoid migration complexity later.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Q4: Error Handling
|
||||
|
||||
**Answer**: Show clear, informative error messages.
|
||||
|
||||
**Error Messages**:
|
||||
- **Auth server down**: "Authorization server is unreachable. Please try again later."
|
||||
- **Invalid token**: "Access token is invalid or expired. Please re-authorize."
|
||||
- **Network error**: "Cannot connect to authorization server."
|
||||
|
||||
**HTTP Status Codes**:
|
||||
- 401: No token provided
|
||||
- 403: Invalid/expired token
|
||||
- 503: Auth server unreachable
|
||||
|
||||
---
|
||||
|
||||
### ✅ Q5: Cache Revocation Delay
|
||||
|
||||
**Architectural Decision**: Use 5-minute cache with configuration options.
|
||||
|
||||
**Implementation**:
|
||||
```python
|
||||
# Default: 5-minute cache
|
||||
MICROPUB_TOKEN_CACHE_TTL=300
|
||||
MICROPUB_TOKEN_CACHE_ENABLED=true
|
||||
|
||||
# High security: disable cache
|
||||
MICROPUB_TOKEN_CACHE_ENABLED=false
|
||||
```
|
||||
|
||||
**Security Notes**:
|
||||
- SHA256 hash tokens before caching
|
||||
- Memory-only cache (not persisted)
|
||||
- Document 5-minute delay in security guide
|
||||
- Allow disabling for high-security needs
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Immediate Actions
|
||||
|
||||
1. **Remove Internal Provider Code**:
|
||||
- Delete `/auth/authorize` endpoint
|
||||
- Delete `/auth/token` endpoint
|
||||
- Remove token issuance logic
|
||||
- Remove authorization code generation
|
||||
|
||||
2. **Implement External Verification**:
|
||||
```python
|
||||
# Core verification function
|
||||
def verify_micropub_token(bearer_token, expected_me):
|
||||
# 1. Check cache (if enabled)
|
||||
# 2. Discover token endpoint from expected_me
|
||||
# 3. Verify with external endpoint
|
||||
# 4. Cache result (if enabled)
|
||||
# 5. Return validation result
|
||||
```
|
||||
|
||||
3. **Add Configuration**:
|
||||
```ini
|
||||
# Required
|
||||
ADMIN_ME=https://user.example.com
|
||||
|
||||
# Optional (with defaults)
|
||||
MICROPUB_TOKEN_CACHE_ENABLED=true
|
||||
MICROPUB_TOKEN_CACHE_TTL=300
|
||||
```
|
||||
|
||||
4. **Update Error Handling**:
|
||||
```python
|
||||
try:
|
||||
response = httpx.get(endpoint, timeout=5.0)
|
||||
except httpx.TimeoutError:
|
||||
return error(503, "Authorization server is unreachable")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Token Verification
|
||||
```python
|
||||
def verify_token(bearer_token: str, token_endpoint: str, expected_me: str) -> Optional[dict]:
|
||||
"""Verify token with external endpoint"""
|
||||
try:
|
||||
response = httpx.get(
|
||||
token_endpoint,
|
||||
headers={'Authorization': f'Bearer {bearer_token}'},
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get('me') == expected_me and 'create' in data.get('scope', ''):
|
||||
return data
|
||||
return None
|
||||
|
||||
except httpx.TimeoutError:
|
||||
raise TokenEndpointError("Authorization server is unreachable")
|
||||
```
|
||||
|
||||
### Endpoint Discovery
|
||||
```python
|
||||
def discover_token_endpoint(me_url: str) -> str:
|
||||
"""Discover token endpoint from user's URL"""
|
||||
response = httpx.get(me_url)
|
||||
|
||||
# 1. Check HTTP Link header
|
||||
if link := parse_link_header(response.headers.get('Link'), 'token_endpoint'):
|
||||
return urljoin(me_url, link)
|
||||
|
||||
# 2. Check HTML <link> tags
|
||||
if 'text/html' in response.headers.get('content-type', ''):
|
||||
if link := parse_html_link(response.text, 'token_endpoint'):
|
||||
return urljoin(me_url, link)
|
||||
|
||||
raise DiscoveryError(f"No token endpoint found at {me_url}")
|
||||
```
|
||||
|
||||
### Micropub Endpoint
|
||||
```python
|
||||
@app.route('/api/micropub', methods=['POST'])
|
||||
def micropub_endpoint():
|
||||
# Extract token
|
||||
auth = request.headers.get('Authorization', '')
|
||||
if not auth.startswith('Bearer '):
|
||||
return {'error': 'unauthorized'}, 401
|
||||
|
||||
token = auth[7:] # Remove "Bearer "
|
||||
|
||||
# Verify token
|
||||
try:
|
||||
token_info = verify_micropub_token(token, app.config['ADMIN_ME'])
|
||||
if not token_info:
|
||||
return {'error': 'forbidden'}, 403
|
||||
except TokenEndpointError as e:
|
||||
return {'error': 'temporarily_unavailable', 'error_description': str(e)}, 503
|
||||
|
||||
# Process Micropub request
|
||||
# ... create note ...
|
||||
|
||||
return '', 201, {'Location': note_url}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Guide
|
||||
|
||||
### Manual Testing
|
||||
1. Configure your domain with IndieAuth links
|
||||
2. Set ADMIN_ME in StarPunk config
|
||||
3. Use Quill (https://quill.p3k.io) to test posting
|
||||
4. Verify token caching works (check logs)
|
||||
5. Test with auth server down (block network)
|
||||
|
||||
### Automated Tests
|
||||
```python
|
||||
def test_token_verification():
|
||||
# Mock external token endpoint
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.add(responses.GET, 'https://tokens.example.com/token',
|
||||
json={'me': 'https://user.com', 'scope': 'create'})
|
||||
|
||||
result = verify_token('test-token', 'https://tokens.example.com/token', 'https://user.com')
|
||||
assert result['me'] == 'https://user.com'
|
||||
|
||||
def test_auth_server_unreachable():
|
||||
# Mock timeout
|
||||
with pytest.raises(TokenEndpointError, match="unreachable"):
|
||||
verify_token('test-token', 'https://timeout.example.com/token', 'https://user.com')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Documentation Template
|
||||
|
||||
### For Users: Setting Up IndieAuth
|
||||
|
||||
1. **Add to your website's HTML**:
|
||||
```html
|
||||
<link rel="authorization_endpoint" href="https://indielogin.com/auth">
|
||||
<link rel="token_endpoint" href="https://tokens.indieauth.com/token">
|
||||
<link rel="micropub" href="[YOUR-STARPUNK-URL]/api/micropub">
|
||||
```
|
||||
|
||||
2. **Configure StarPunk**:
|
||||
```ini
|
||||
ADMIN_ME=https://your-website.com
|
||||
```
|
||||
|
||||
3. **Test with a Micropub client**:
|
||||
- Visit https://quill.p3k.io
|
||||
- Enter your website URL
|
||||
- Authorize and post!
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
All architectural questions have been answered:
|
||||
|
||||
1. **Token Format**: Follow IndieAuth spec exactly
|
||||
2. **HTML Headers**: Users configure their own domains
|
||||
3. **Migration**: Keep tables for future use
|
||||
4. **Errors**: Clear messages about connectivity
|
||||
5. **Cache**: 5-minute TTL with disable option
|
||||
|
||||
The implementation path is clear: remove internal provider code, implement external verification with caching, and provide good error messages. This aligns with StarPunk's philosophy of minimal code and IndieWeb principles.
|
||||
|
||||
---
|
||||
|
||||
**Ready for Implementation**: All questions answered, examples provided, architecture documented.
|
||||
Reference in New Issue
Block a user