that initial commit

This commit is contained in:
2025-11-18 19:21:31 -07:00
commit a68fd570c7
69 changed files with 31070 additions and 0 deletions

View File

@@ -0,0 +1,421 @@
# ADR-005: IndieLogin Authentication Integration
## Status
Accepted
## Context
The user has explicitly required external IndieLogin authentication via indielogin.com for V1. This is different from implementing a full IndieAuth server (which CLAUDE.MD mentions). The distinction is important:
- **IndieAuth Server**: Host your own authentication endpoint (complex)
- **IndieLogin Service**: Use indielogin.com as an external authentication provider (simple)
The user wants the simpler approach: delegate authentication to indielogin.com using their API (https://indielogin.com/api).
IndieLogin.com is a service that:
1. Handles the OAuth 2.0 / IndieAuth flow
2. Verifies user identity via their website
3. Returns authenticated identity to our application
4. Supports multiple authentication methods (RelMeAuth, email, etc.)
## Decision
### Use IndieLogin.com as External Authentication Provider
**Authentication Flow**: OAuth 2.0 Authorization Code flow via indielogin.com
**API Endpoint**: https://indielogin.com/auth
**Token Validation**: Server-side session tokens (not IndieAuth tokens)
**User Identity**: URL (me parameter) verified by indielogin.com
### Architecture
```
User Browser → StarPunk → indielogin.com → User's Website
↑ ↓
└──────────────────────────────┘
(Authenticated session)
```
## Authentication Flow
### 1. Login Initiation
```
User clicks "Login"
StarPunk generates state token (CSRF protection)
Redirect to: https://indielogin.com/auth?
- me={user_website}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
- state={random_token}
```
### 2. IndieLogin Processing
```
indielogin.com verifies user identity:
- Checks for rel="me" links on user's website
- Or sends email verification
- Or uses other IndieAuth methods
User authenticates via their chosen method
indielogin.com redirects back to StarPunk
```
### 3. Callback Verification
```
indielogin.com → StarPunk callback with:
- code={authorization_code}
- state={original_state}
StarPunk verifies state matches
StarPunk exchanges code for verified identity:
POST https://indielogin.com/auth
- code={authorization_code}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
indielogin.com responds with:
{ "me": "https://user-website.com" }
StarPunk creates authenticated session
```
### 4. Session Management
```
StarPunk stores session token in cookie
Session token maps to authenticated user URL
Admin routes check for valid session
```
## Implementation Requirements
### Configuration Variables
```
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=random_secret_key
```
### Database Schema Addition
```sql
-- Add to existing schema
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_token TEXT UNIQUE NOT NULL,
me TEXT NOT NULL, -- Authenticated user URL
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP
);
CREATE INDEX idx_sessions_token ON sessions(session_token);
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
CREATE TABLE auth_state (
state TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL -- Short-lived (5 minutes)
);
```
### HTTP Client for API Calls
Use **httpx** (already selected in ADR-002) for:
- POST to https://indielogin.com/auth to exchange code
- Verify response contains valid "me" URL
- Handle network errors gracefully
### Routes Required
```
GET /admin/login - Display login form
POST /admin/login - Initiate IndieLogin flow
GET /auth/callback - Handle IndieLogin redirect
POST /admin/logout - Destroy session
```
### Login Flow Implementation
#### Step 1: Login Form
```python
# /admin/login (GET)
# Display simple form asking for user's website URL
# Form submits to POST /admin/login with "me" parameter
```
#### Step 2: Initiate Authentication
```python
# /admin/login (POST)
def initiate_login(me_url):
# Validate me_url format
if not is_valid_url(me_url):
return error("Invalid URL")
# Generate and store state token
state = generate_random_token()
store_state(state, expires_in_minutes=5)
# Build IndieLogin authorization URL
params = {
'me': me_url,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback",
'state': state
}
auth_url = f"https://indielogin.com/auth?{urlencode(params)}"
# Redirect user to IndieLogin
return redirect(auth_url)
```
#### Step 3: Handle Callback
```python
# /auth/callback (GET)
def handle_callback(code, state):
# Verify state token (CSRF protection)
if not verify_state(state):
return error("Invalid state")
# Exchange code for verified identity
response = httpx.post('https://indielogin.com/auth', data={
'code': code,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback"
})
if response.status_code != 200:
return error("Authentication failed")
data = response.json()
me = data.get('me')
# Verify this is the authorized admin
if me != ADMIN_ME:
return error("Unauthorized user")
# Create session
session_token = generate_random_token()
create_session(session_token, me, expires_in_days=30)
# Set session cookie
set_cookie('session', session_token, httponly=True, secure=True)
# Redirect to admin dashboard
return redirect('/admin')
```
#### Step 4: Session Validation
```python
# Decorator for protected routes
def require_auth(f):
def wrapper(*args, **kwargs):
session_token = request.cookies.get('session')
if not session_token:
return redirect('/admin/login')
session = get_session(session_token)
if not session or session.expired:
return redirect('/admin/login')
# Update last_used_at
update_session_activity(session_token)
# Store user info in request context
g.user_me = session.me
return f(*args, **kwargs)
return wrapper
# Usage
@app.route('/admin')
@require_auth
def admin_dashboard():
return render_template('admin/dashboard.html')
```
## Rationale
### Why IndieLogin.com Instead of Self-Hosted IndieAuth?
**Simplicity Score: 10/10 (IndieLogin) vs 4/10 (Self-hosted)**
- IndieLogin.com handles all complexity of:
- Discovering user's auth endpoints
- Verifying user identity
- Supporting multiple auth methods (RelMeAuth, email, etc.)
- PKCE implementation
- Self-hosted would require implementing full IndieAuth spec (complex)
**Fitness Score: 10/10**
- Perfect for single-user system
- User controls their identity via their own website
- No password management needed
- Aligns with IndieWeb principles
**Maintenance Score: 10/10**
- indielogin.com is maintained by IndieWeb community
- No auth code to maintain ourselves
- Security updates handled externally
- Well-tested service
**Standards Compliance: Pass**
- Uses OAuth 2.0 / IndieAuth standards
- Compatible with IndieWeb ecosystem
- User identity is their URL (IndieWeb principle)
### Why Session Cookies Instead of Access Tokens?
For admin interface (not Micropub):
- **Simpler**: Standard web session pattern
- **Secure**: HttpOnly cookies prevent XSS
- **Appropriate**: Admin is human using browser, not API client
- **Note**: Micropub will still use access tokens (separate ADR needed)
## Consequences
### Positive
- Extremely simple implementation (< 100 lines of code)
- No authentication code to maintain
- Secure by default (delegated to trusted service)
- True IndieWeb authentication (user owns identity)
- No passwords to manage
- Works immediately without setup
- Community-maintained service
### Negative
- Dependency on external service (indielogin.com)
- Requires internet connection to authenticate
- Single point of failure for login (mitigated: session stays valid)
- User must have their own website/URL
### Mitigation
- Sessions last 30 days, so brief indielogin.com outages don't lock out user
- Document fallback: edit database to create session manually if needed
- IndieLogin.com is stable, community-run service with good uptime
- For V2: Consider optional email fallback or self-hosted IndieAuth
## Security Considerations
### State Token (CSRF Protection)
- Generate cryptographically random state token
- Store in database with short expiry (5 minutes)
- Verify state matches on callback
- Delete state after use (single-use tokens)
### Session Token Security
- Generate with secrets.token_urlsafe(32) or similar
- Store hash in database (not plaintext)
- Mark cookies as HttpOnly and Secure
- Set SameSite=Lax for CSRF protection
- Implement session expiry (30 days)
- Support manual logout (session deletion)
### Identity Verification
- Only allow ADMIN_ME URL to authenticate
- Verify "me" URL from indielogin.com exactly matches config
- Reject any other authenticated users
- Log authentication attempts
### Network Security
- Use HTTPS for all communication
- Verify SSL certificates on httpx requests
- Handle network timeouts gracefully
- Log authentication failures
## Testing Strategy
### Unit Tests
- State token generation and validation
- Session creation and expiry
- URL validation
- Cookie handling
### Integration Tests
- Mock indielogin.com API responses
- Test full authentication flow
- Test session expiry
- Test unauthorized user rejection
- Test CSRF protection (invalid state)
### Manual Testing
- Authenticate with real indielogin.com
- Verify session persistence
- Test logout functionality
- Test session expiry
- Test with wrong "me" URL
## Alternatives Considered
### Self-Hosted IndieAuth Server (Rejected)
- **Complexity**: Must implement full IndieAuth spec
- **Maintenance**: Security updates, endpoint discovery, token generation
- **Verdict**: Too complex for V1, violates simplicity principle
### Password Authentication (Rejected)
- **Security**: Must hash passwords, handle resets, prevent brute force
- **IndieWeb**: Violates IndieWeb principle of URL-based identity
- **Verdict**: Not aligned with project goals
### OAuth via GitHub/Google (Rejected)
- **Simplicity**: Easy to implement
- **IndieWeb**: Not IndieWeb-compatible, user doesn't own identity
- **Verdict**: Violates IndieWeb requirements
### Email Magic Links (Rejected)
- **Simplicity**: Requires email sending infrastructure
- **IndieWeb**: Not standard IndieWeb authentication
- **Verdict**: Deferred to V2 as fallback option
### Multi-User IndieAuth (Rejected for V1)
- **Scope**: V1 is explicitly single-user
- **Complexity**: Would require user management
- **Verdict**: Out of scope, defer to V2
## Implementation Checklist
- [ ] Add SESSION_SECRET and ADMIN_ME to configuration
- [ ] Create sessions and auth_state database tables
- [ ] Implement state token generation and storage
- [ ] Create login form template
- [ ] Implement /admin/login routes (GET and POST)
- [ ] Implement /auth/callback route
- [ ] Implement session creation and validation
- [ ] Create require_auth decorator
- [ ] Implement logout functionality
- [ ] Set secure cookie parameters
- [ ] Add authentication error handling
- [ ] Write unit tests for auth flow
- [ ] Write integration tests with mocked indielogin.com
- [ ] Test with real indielogin.com
- [ ] Document setup process for users
## Configuration Example
```bash
# .env file
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=your-random-secret-key-here
```
## User Setup Documentation
1. Deploy StarPunk to your server at `https://starpunk.example.com`
2. Configure `ADMIN_ME` to your personal website URL
3. Visit `/admin/login`
4. Enter your website URL (must match ADMIN_ME)
5. indielogin.com will verify your identity
6. Authenticate via your chosen method
7. Redirected back to StarPunk admin interface
## References
- IndieLogin.com: https://indielogin.com/
- IndieLogin API Documentation: https://indielogin.com/api
- IndieAuth Specification: https://indieauth.spec.indieweb.org/
- OAuth 2.0 Spec: https://oauth.net/2/
- Web Authentication Best Practices: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html