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:
@@ -29,15 +29,18 @@ CREATE INDEX IF NOT EXISTS idx_notes_deleted_at ON notes(deleted_at);
|
||||
-- Authentication sessions (IndieLogin)
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_token TEXT UNIQUE NOT NULL,
|
||||
session_token_hash TEXT UNIQUE NOT NULL,
|
||||
me TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
last_used_at TIMESTAMP
|
||||
last_used_at TIMESTAMP,
|
||||
user_agent TEXT,
|
||||
ip_address TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(session_token_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_me ON sessions(me);
|
||||
|
||||
-- Micropub access tokens
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
@@ -55,7 +58,8 @@ CREATE INDEX IF NOT EXISTS idx_tokens_me ON tokens(me);
|
||||
CREATE TABLE IF NOT EXISTS auth_state (
|
||||
state TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP NOT NULL
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
redirect_uri TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_auth_state_expires ON auth_state(expires_at);
|
||||
@@ -70,10 +74,10 @@ def init_db(app=None):
|
||||
app: Flask application instance (optional, for config access)
|
||||
"""
|
||||
if app:
|
||||
db_path = app.config['DATABASE_PATH']
|
||||
db_path = app.config["DATABASE_PATH"]
|
||||
else:
|
||||
# Fallback to default path
|
||||
db_path = Path('./data/starpunk.db')
|
||||
db_path = Path("./data/starpunk.db")
|
||||
|
||||
# Ensure parent directory exists
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -98,7 +102,7 @@ def get_db(app):
|
||||
Returns:
|
||||
sqlite3.Connection
|
||||
"""
|
||||
db_path = app.config['DATABASE_PATH']
|
||||
db_path = app.config["DATABASE_PATH"]
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row # Return rows as dictionaries
|
||||
return conn
|
||||
|
||||
Reference in New Issue
Block a user