feat: Implement Phase 3 authentication module with IndieLogin support
Implement complete authentication system following ADR-010 and Phase 3 design specs. This is a MINOR version increment (0.3.0 -> 0.4.0) as it adds new functionality. Authentication Features: - IndieLogin authentication flow via indielogin.com - Secure session management with SHA-256 token hashing - CSRF protection with single-use state tokens - Session lifecycle (create, verify, destroy) - require_auth decorator for protected routes - Automatic cleanup of expired sessions - IP address and user agent tracking Security Measures: - Cryptographically secure token generation (secrets module) - Token hashing for storage (never plaintext) - SQL injection prevention (prepared statements) - Single-use CSRF state tokens - 30-day session expiry with activity refresh - Comprehensive security logging Implementation Details: - starpunk/auth.py: 406 lines, 6 core functions, 4 helpers, 4 exceptions - tests/test_auth.py: 648 lines, 37 tests, 96% coverage - Database schema updates for sessions and auth_state tables - URL validation utility added to utils.py Test Coverage: - 37 authentication tests - 96% code coverage (exceeds 90% target) - All security features tested - Edge cases and error paths covered Documentation: - Implementation report in docs/reports/ - Updated CHANGELOG.md with detailed changes - Version incremented to 0.4.0 - ADR-010 and Phase 3 design docs included Follows project standards: - Black code formatting (88 char lines) - Flake8 linting (no errors) - Python coding standards - Type hints on all functions - Comprehensive docstrings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
218
docs/decisions/ADR-010-authentication-module-design.md
Normal file
218
docs/decisions/ADR-010-authentication-module-design.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# ADR-010: Authentication Module Design
|
||||
|
||||
## Status
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
With the core utilities and notes management complete, StarPunk needs an authentication system for the admin interface. ADR-005 already decided on using IndieLogin.com as the authentication provider. This ADR defines the specific module architecture and implementation approach for the authentication system.
|
||||
|
||||
The authentication module must:
|
||||
1. Integrate with IndieLogin.com for identity verification
|
||||
2. Manage secure sessions for authenticated users
|
||||
3. Protect admin routes from unauthorized access
|
||||
4. Handle CSRF protection for the authentication flow
|
||||
5. Support single-user authorization (V1 requirement)
|
||||
|
||||
## Decision
|
||||
|
||||
### Module Architecture
|
||||
|
||||
**Single Module Approach**: Implement all authentication logic in a single `starpunk/auth.py` module.
|
||||
|
||||
**Rationale**:
|
||||
- Authentication is ~200-300 lines of focused code
|
||||
- Single responsibility: manage authentication and sessions
|
||||
- No need to split into multiple files for V1
|
||||
- Easier to understand and maintain
|
||||
|
||||
### Session Storage Strategy
|
||||
|
||||
**Database-Backed Sessions** with these characteristics:
|
||||
- Session tokens stored in SQLite (not Redis/Memcached)
|
||||
- Cryptographically secure token generation (32 bytes)
|
||||
- 30-day expiry with activity-based refresh
|
||||
- Automatic cleanup of expired sessions
|
||||
|
||||
**Schema**:
|
||||
```sql
|
||||
CREATE TABLE sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_token TEXT UNIQUE NOT NULL,
|
||||
me TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
last_used_at TIMESTAMP,
|
||||
user_agent TEXT,
|
||||
ip_address TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE auth_state (
|
||||
state TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
redirect_uri TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### Security Architecture
|
||||
|
||||
**Token Security**:
|
||||
- Use `secrets.token_urlsafe(32)` for session tokens
|
||||
- Store token hash in database (SHA-256), not plaintext
|
||||
- HttpOnly, Secure, SameSite=Lax cookies
|
||||
- No JavaScript access to session tokens
|
||||
|
||||
**CSRF Protection**:
|
||||
- Generate state token for each auth request
|
||||
- Store in `auth_state` table with 5-minute expiry
|
||||
- Verify state on callback before code exchange
|
||||
- Single-use tokens (delete after verification)
|
||||
|
||||
**Authorization Model**:
|
||||
- Single admin user (configured via ADMIN_ME environment variable)
|
||||
- Strict equality check (no substring matching)
|
||||
- Reject any user whose `me` URL doesn't exactly match ADMIN_ME
|
||||
- Log all authentication attempts for security audit
|
||||
|
||||
### Function Architecture
|
||||
|
||||
**Core Functions** (6 total):
|
||||
1. `initiate_login(me_url: str) -> str` - Start IndieLogin flow
|
||||
2. `handle_callback(code: str, state: str) -> Optional[str]` - Process callback
|
||||
3. `create_session(me: str) -> str` - Create new session
|
||||
4. `verify_session(token: str) -> Optional[Dict]` - Validate session
|
||||
5. `destroy_session(token: str) -> None` - Logout
|
||||
6. `require_auth(f)` - Decorator for protected routes
|
||||
|
||||
**Helper Functions** (4 total):
|
||||
1. `_generate_state_token() -> str` - CSRF token generation
|
||||
2. `_verify_state_token(state: str) -> bool` - CSRF validation
|
||||
3. `_cleanup_expired_sessions() -> None` - Maintenance
|
||||
4. `_hash_token(token: str) -> str` - Security hashing
|
||||
|
||||
### Error Handling Strategy
|
||||
|
||||
**Custom Exceptions**:
|
||||
```python
|
||||
class AuthError(Exception): pass
|
||||
class InvalidStateError(AuthError): pass
|
||||
class UnauthorizedError(AuthError): pass
|
||||
class IndieLoginError(AuthError): pass
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- Invalid state: 400 Bad Request with clear error
|
||||
- Unauthorized user: 403 Forbidden
|
||||
- IndieLogin failure: 502 Bad Gateway
|
||||
- Session expired: Redirect to login
|
||||
|
||||
### Integration Points
|
||||
|
||||
**Flask Integration**:
|
||||
- Use Flask's `g` object for request-scoped user data
|
||||
- Integrate with Flask's session for flash messages
|
||||
- Use Flask's `before_request` for session refresh
|
||||
- Leverage Flask's error handlers for auth errors
|
||||
|
||||
**Database Integration**:
|
||||
- Use existing `get_db()` connection management
|
||||
- Transactions for session creation/deletion
|
||||
- Prepared statements to prevent SQL injection
|
||||
|
||||
## Rationale
|
||||
|
||||
### Why Database Sessions Over Flask-Session?
|
||||
|
||||
**Database sessions chosen**:
|
||||
- Already have SQLite, no new dependencies
|
||||
- Persistent across server restarts
|
||||
- Can query active sessions
|
||||
- Supports session invalidation
|
||||
- Better security (server-side storage)
|
||||
|
||||
**Flask-Session rejected**:
|
||||
- Adds Redis/Memcached dependency
|
||||
- Overkill for single-user system
|
||||
- More complex deployment
|
||||
|
||||
### Why Token Hashing?
|
||||
|
||||
Even though we're single-user, proper security practices:
|
||||
- Defense in depth principle
|
||||
- Prevents token leakage if database exposed
|
||||
- Industry best practice
|
||||
- No performance impact for our scale
|
||||
|
||||
### Why 30-Day Sessions?
|
||||
|
||||
Balance between security and usability:
|
||||
- Long enough to avoid frequent re-authentication
|
||||
- Short enough to limit exposure window
|
||||
- Activity-based refresh keeps active sessions alive
|
||||
- Matches common web application patterns
|
||||
|
||||
### Why Single Module?
|
||||
|
||||
**Simplicity principle**:
|
||||
- Authentication is cohesive functionality
|
||||
- ~300 lines doesn't justify splitting
|
||||
- Easier to audit security in one file
|
||||
- Reduces import complexity
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
1. **Simple implementation** - Single file, clear responsibilities
|
||||
2. **Secure by default** - Industry best practices applied
|
||||
3. **Zero dependencies** - Uses existing stack (SQLite, httpx)
|
||||
4. **Maintainable** - All auth logic in one place
|
||||
5. **Testable** - Clear function boundaries, mockable
|
||||
6. **Production-ready** - Proper session management, security
|
||||
7. **IndieWeb compliant** - Full IndieAuth specification support
|
||||
|
||||
### Negative
|
||||
1. **Manual session cleanup** - Need periodic expired session removal
|
||||
2. **No rate limiting** - Could add in V2 if needed
|
||||
3. **Single admin limitation** - Architectural constraint for V1
|
||||
4. **No 2FA** - Relies entirely on IndieLogin's security
|
||||
|
||||
### Mitigations
|
||||
|
||||
**For session cleanup**:
|
||||
- Run cleanup on each login attempt
|
||||
- Add admin command for manual cleanup
|
||||
- Document in operations guide
|
||||
|
||||
**For rate limiting**:
|
||||
- Deploy behind reverse proxy (nginx/Caddy)
|
||||
- Add to V2 if abuse detected
|
||||
- Log attempts for monitoring
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Create `starpunk/auth.py` module
|
||||
- [ ] Add session tables to database schema
|
||||
- [ ] Implement core authentication functions
|
||||
- [ ] Add custom exception classes
|
||||
- [ ] Create require_auth decorator
|
||||
- [ ] Write comprehensive tests (target: 90% coverage)
|
||||
- [ ] Add security logging
|
||||
- [ ] Document configuration requirements
|
||||
- [ ] Create integration tests with IndieLogin
|
||||
- [ ] Security audit checklist
|
||||
|
||||
## References
|
||||
|
||||
- [ADR-005: IndieLogin Authentication](/home/phil/Projects/starpunk/docs/decisions/ADR-005-indielogin-authentication.md)
|
||||
- [IndieAuth Specification](https://indieauth.spec.indieweb.org/)
|
||||
- [OWASP Session Management](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
|
||||
- [Flask Security Best Practices](https://flask.palletsprojects.com/en/3.0.x/security/)
|
||||
|
||||
---
|
||||
|
||||
**ADR**: 010
|
||||
**Date**: 2025-11-18
|
||||
**Status**: Accepted
|
||||
**Decision**: Single-module database-backed session authentication with IndieLogin
|
||||
**Supersedes**: None
|
||||
Reference in New Issue
Block a user