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:
@@ -35,6 +35,15 @@ CONTENT_HASH_ALGORITHM = "sha256"
|
||||
SLUG_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
SAFE_SLUG_PATTERN = re.compile(r"[^a-z0-9-]")
|
||||
MULTIPLE_HYPHENS_PATTERN = re.compile(r"-+")
|
||||
URL_PATTERN = re.compile(
|
||||
r"^https?://" # http:// or https://
|
||||
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain...
|
||||
r"localhost|" # localhost...
|
||||
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
|
||||
r"(?::\d+)?" # optional port
|
||||
r"(?:/?|[/?]\S+)$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Character set for random suffix generation
|
||||
RANDOM_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
@@ -43,6 +52,36 @@ RANDOM_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
# Helper Functions
|
||||
|
||||
|
||||
def is_valid_url(url: str) -> bool:
|
||||
"""
|
||||
Validate URL format
|
||||
|
||||
Checks if a string is a valid HTTP or HTTPS URL.
|
||||
|
||||
Args:
|
||||
url: URL string to validate
|
||||
|
||||
Returns:
|
||||
True if valid URL, False otherwise
|
||||
|
||||
Examples:
|
||||
>>> is_valid_url("https://example.com")
|
||||
True
|
||||
|
||||
>>> is_valid_url("http://localhost:5000")
|
||||
True
|
||||
|
||||
>>> is_valid_url("not-a-url")
|
||||
False
|
||||
|
||||
>>> is_valid_url("ftp://example.com")
|
||||
False
|
||||
"""
|
||||
if not url or not isinstance(url, str):
|
||||
return False
|
||||
return bool(URL_PATTERN.match(url))
|
||||
|
||||
|
||||
def extract_first_words(text: str, max_words: int = 5) -> str:
|
||||
"""
|
||||
Extract first N words from text
|
||||
|
||||
Reference in New Issue
Block a user