- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest). - Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting. - Updated `requirements.txt` to include Ruff and remove replaced tools. - Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
151 lines
4.1 KiB
Python
151 lines
4.1 KiB
Python
"""
|
|
Security tests for configuration validation (app/core/config.py).
|
|
|
|
Critical security tests covering:
|
|
- SECRET_KEY minimum length validation (prevents weak JWT signing keys)
|
|
|
|
These tests prevent security misconfigurations.
|
|
"""
|
|
|
|
import os
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
|
|
class TestSecretKeySecurityValidation:
|
|
"""
|
|
Test SECRET_KEY security validation (config.py line 109).
|
|
|
|
Attack Prevention:
|
|
Short SECRET_KEYs can be brute-forced, compromising JWT token security.
|
|
System must enforce minimum 32-character requirement.
|
|
|
|
Covers: config.py:109
|
|
"""
|
|
|
|
def test_secret_key_too_short_rejected(self):
|
|
"""
|
|
Test that SECRET_KEY shorter than 32 characters is rejected.
|
|
|
|
Security Risk:
|
|
Short keys (e.g., "password123") can be brute-forced, allowing
|
|
attackers to forge JWT tokens.
|
|
|
|
Covers line 109.
|
|
"""
|
|
# Save original SECRET_KEY
|
|
original_secret = os.environ.get("SECRET_KEY")
|
|
|
|
try:
|
|
# Try to set a short SECRET_KEY (only 20 characters)
|
|
short_key = "a" * 20 # Too short!
|
|
os.environ["SECRET_KEY"] = short_key
|
|
|
|
# Import Settings class fresh (to pick up new env var)
|
|
# The ValidationError should be raised during reload when Settings() is instantiated
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
# Reload will raise ValidationError because Settings() is instantiated at module level
|
|
with pytest.raises(ValidationError, match="at least 32 characters"):
|
|
importlib.reload(config)
|
|
|
|
finally:
|
|
# Restore original SECRET_KEY
|
|
if original_secret:
|
|
os.environ["SECRET_KEY"] = original_secret
|
|
else:
|
|
os.environ.pop("SECRET_KEY", None)
|
|
|
|
# Reload config to restore original settings
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
importlib.reload(config)
|
|
|
|
def test_secret_key_exactly_32_characters_accepted(self):
|
|
"""
|
|
Test that SECRET_KEY with exactly 32 characters is accepted.
|
|
|
|
Minimum secure length.
|
|
"""
|
|
original_secret = os.environ.get("SECRET_KEY")
|
|
|
|
try:
|
|
# Set exactly 32-character key
|
|
key_32 = "a" * 32
|
|
os.environ["SECRET_KEY"] = key_32
|
|
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
importlib.reload(config)
|
|
|
|
# Should work
|
|
settings = config.Settings()
|
|
assert len(settings.SECRET_KEY) == 32
|
|
|
|
finally:
|
|
if original_secret:
|
|
os.environ["SECRET_KEY"] = original_secret
|
|
else:
|
|
os.environ.pop("SECRET_KEY", None)
|
|
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
importlib.reload(config)
|
|
|
|
def test_secret_key_long_enough_accepted(self):
|
|
"""
|
|
Test that SECRET_KEY with 32+ characters is accepted.
|
|
|
|
Sanity check that valid keys work.
|
|
"""
|
|
original_secret = os.environ.get("SECRET_KEY")
|
|
|
|
try:
|
|
# Set long key (64 characters)
|
|
key_64 = "a" * 64
|
|
os.environ["SECRET_KEY"] = key_64
|
|
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
importlib.reload(config)
|
|
|
|
# Should work
|
|
settings = config.Settings()
|
|
assert len(settings.SECRET_KEY) >= 32
|
|
|
|
finally:
|
|
if original_secret:
|
|
os.environ["SECRET_KEY"] = original_secret
|
|
else:
|
|
os.environ.pop("SECRET_KEY", None)
|
|
|
|
import importlib
|
|
|
|
from app.core import config
|
|
|
|
importlib.reload(config)
|
|
|
|
def test_default_secret_key_meets_requirements(self):
|
|
"""
|
|
Test that the default SECRET_KEY (if no env var) meets requirements.
|
|
|
|
Ensures our defaults are secure.
|
|
"""
|
|
from app.core.config import settings
|
|
|
|
# Current settings should have valid SECRET_KEY
|
|
assert len(settings.SECRET_KEY) >= 32, (
|
|
"Default SECRET_KEY must be at least 32 chars"
|
|
)
|