Implement MVP: Anonymous feedback submission (User Story 1)

Complete implementation of Phase 1-3 (64 tasks):
- Phase 1: Project setup with Flask, pytest, configuration
- Phase 2: Core infrastructure (auth, models, services, testing)
- Phase 3: Anonymous feedback submission with file uploads

Features:
- Anonymous feedback submission (text and/or up to 3 file attachments)
- Multi-language support (any language accepted)
- File validation (type, size) and virus scanning (ClamAV)
- Product management with active/archived status
- File-based storage with YAML metadata
- User authentication system (Flask-Login)
- CSRF protection and rate limiting
- Test coverage: 10 passing tests (contract + integration)

Security:
- No IP address logging (FR-055 compliance)
- File type whitelist and size limits (10MB max)
- Virus scanning with graceful degradation
- Filename sanitization and secure storage

Test Results:
- 8 contract tests passed
- 2 integration tests passed
- End-to-end workflow verified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-16 15:14:51 +02:00
co-authored by Claude
parent 07e51d7468
commit b301def134
37 changed files with 2416 additions and 39 deletions
+52
View File
@@ -0,0 +1,52 @@
"""Authentication service"""
import bcrypt
from app.models.user import User
def verify_credentials(username, password):
"""Verify username and password
Args:
username: Username to check
password: Plain text password to verify
Returns:
User or None: User object if credentials valid, None otherwise
"""
if not username or not password:
return None
user = User.get_by_username(username)
if not user or not user.is_active:
return None
if user.check_password(password):
return user
return None
def hash_password(password):
"""Hash password using bcrypt
Args:
password: Plain text password
Returns:
str: Hashed password
"""
return User.hash_password(password)
def check_password(password, password_hash):
"""Check password against hash
Args:
password: Plain text password
password_hash: Bcrypt hash to check against
Returns:
bool: True if password matches, False otherwise
"""
return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))