Phase 2 Step 4: Implement Authentication System
Implemented comprehensive Flask-Login authentication with single-user support. New Features: - Flask-Login integration with User model - Bcrypt password hashing via PasswordManager - Login, logout, and initial password setup routes - @login_required and @api_auth_required decorators - All API endpoints now require authentication - Bootstrap 5 dark theme UI templates - Dashboard with navigation - Remember me and next parameter redirect support Files Created (12): - web/auth/__init__.py, models.py, decorators.py, routes.py - web/routes/__init__.py, main.py - web/templates/login.html, setup.html, dashboard.html, scans.html, scan_detail.html - tests/test_authentication.py (30+ tests) Files Modified (6): - web/app.py: Added Flask-Login initialization and main routes - web/api/scans.py: Protected all endpoints with @api_auth_required - web/api/settings.py: Protected all endpoints with @api_auth_required - web/api/schedules.py: Protected all endpoints with @api_auth_required - web/api/alerts.py: Protected all endpoints with @api_auth_required - tests/conftest.py: Added authentication test fixtures Security: - Session-based authentication for both web UI and API - Secure password storage with bcrypt - Protected routes redirect to login page - Protected API endpoints return 401 Unauthorized - Health check endpoints remain accessible for monitoring Testing: - User model authentication and properties - Login success/failure flows - Logout and session management - Password setup workflow - API endpoint authentication requirements - Session persistence and remember me functionality - Next parameter redirect behavior Total: ~1,200 lines of code added 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from web.app import create_app
|
||||
from web.models import Base, Scan
|
||||
from web.utils.settings import PasswordManager, SettingsManager
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@@ -283,3 +284,101 @@ def sample_scan(db):
|
||||
db.refresh(scan)
|
||||
|
||||
return scan
|
||||
|
||||
|
||||
# Authentication Fixtures
|
||||
|
||||
@pytest.fixture
|
||||
def app_password():
|
||||
"""
|
||||
Test password for authentication tests.
|
||||
|
||||
Returns:
|
||||
Test password string
|
||||
"""
|
||||
return 'testpassword123'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_with_password(db, app_password):
|
||||
"""
|
||||
Database session with application password set.
|
||||
|
||||
Args:
|
||||
db: Database session fixture
|
||||
app_password: Test password fixture
|
||||
|
||||
Returns:
|
||||
Database session with password configured
|
||||
"""
|
||||
settings_manager = SettingsManager(db)
|
||||
PasswordManager.set_app_password(settings_manager, app_password)
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_no_password(app):
|
||||
"""
|
||||
Database session without application password set.
|
||||
|
||||
Args:
|
||||
app: Flask application fixture
|
||||
|
||||
Returns:
|
||||
Database session without password
|
||||
"""
|
||||
with app.app_context():
|
||||
# Clear any password that might be set
|
||||
settings_manager = SettingsManager(app.db_session)
|
||||
settings_manager.delete('app_password')
|
||||
yield app.db_session
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def authenticated_client(client, db_with_password, app_password):
|
||||
"""
|
||||
Flask test client with authenticated session.
|
||||
|
||||
Args:
|
||||
client: Flask test client fixture
|
||||
db_with_password: Database with password set
|
||||
app_password: Test password fixture
|
||||
|
||||
Returns:
|
||||
Test client with active session
|
||||
"""
|
||||
# Log in
|
||||
client.post('/auth/login', data={
|
||||
'password': app_password
|
||||
})
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client_no_password(app):
|
||||
"""
|
||||
Flask test client with no password set (for setup testing).
|
||||
|
||||
Args:
|
||||
app: Flask application fixture
|
||||
|
||||
Returns:
|
||||
Test client for testing setup flow
|
||||
"""
|
||||
# Create temporary database without password
|
||||
db_fd, db_path = tempfile.mkstemp(suffix='.db')
|
||||
|
||||
test_config = {
|
||||
'TESTING': True,
|
||||
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
|
||||
'SECRET_KEY': 'test-secret-key'
|
||||
}
|
||||
|
||||
test_app = create_app(test_config)
|
||||
test_client = test_app.test_client()
|
||||
|
||||
yield test_client
|
||||
|
||||
# Cleanup
|
||||
os.close(db_fd)
|
||||
os.unlink(db_path)
|
||||
|
||||
Reference in New Issue
Block a user