Fix code formatting and linting issues

- Auto-format all Python files with black and isort
- Remove unused imports with autoflake
- Fix flake8 issues (missing newlines, blank lines, etc.)
- Fix nonlocal/global scope issues in main.py
- Fix security.py import order (E402)
- Remove f-string without placeholders
- Add nosec comment for intentional exception handling
- Fix test imports to match refactored DMARCParser API

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-09 12:08:51 +00:00
parent f6908fe9ec
commit 6ae017b142
28 changed files with 999 additions and 956 deletions
+19 -19
View File
@@ -1,12 +1,12 @@
from functools import lru_cache
from typing import Optional, List, Union
import secrets
import logging
import secrets
from functools import lru_cache
from typing import List, Optional, Union
# Try to import from pydantic_settings first (newer versions)
try:
from pydantic_settings import BaseSettings
from pydantic import EmailStr, validator
from pydantic_settings import BaseSettings
except ImportError:
# Fall back to older pydantic version
from pydantic import BaseSettings, EmailStr, validator
@@ -16,42 +16,42 @@ logger = logging.getLogger(__name__)
class Settings(BaseSettings):
"""Application settings"""
# Base
PROJECT_NAME: str = "DMARQ"
API_V1_STR: str = "/api/v1"
# Database
DATABASE_URL: str = "sqlite:///./dmarq.db"
# JWT Authentication
SECRET_KEY: Optional[str] = None
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 # 1 hour
# CORS
BACKEND_CORS_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:5173"]
# IMAP Settings
IMAP_SERVER: Optional[str] = None
IMAP_PORT: int = 993
IMAP_USERNAME: Optional[str] = None
IMAP_PASSWORD: Optional[str] = None
# Admin User
FIRST_SUPERUSER: Optional[EmailStr] = None
FIRST_SUPERUSER_PASSWORD: Optional[str] = None
# Optional Cloudflare Integration
CLOUDFLARE_API_TOKEN: Optional[str] = None
CLOUDFLARE_ZONE_ID: Optional[str] = None
@validator("SECRET_KEY", pre=True, always=True)
def validate_secret_key(cls, v: Optional[str]) -> str:
"""Validate and generate SECRET_KEY if not provided."""
# Default insecure key that should never be used
DEFAULT_INSECURE_KEY = "CHANGE_THIS_TO_A_RANDOM_SECRET_IN_PRODUCTION"
if v is None or v == "" or v == DEFAULT_INSECURE_KEY:
# Generate a secure random key
generated_key = secrets.token_hex(32)
@@ -59,19 +59,19 @@ class Settings(BaseSettings):
"SECRET_KEY not configured or using default value! "
"Generated a random key for this session. "
"For production, set SECRET_KEY in your .env file using: "
f"openssl rand -hex 32"
"openssl rand -hex 32"
)
return generated_key
# Check if key is too short
if len(v) < 32:
logger.warning(
f"SECRET_KEY is too short ({len(v)} characters). "
"Recommended minimum is 32 characters for security."
)
return v
@validator("BACKEND_CORS_ORIGINS", pre=True)
def assemble_cors_origins(cls, v: Union[str, List[str]]) -> List[str]:
if isinstance(v, str) and not v.startswith("["):
@@ -79,7 +79,7 @@ class Settings(BaseSettings):
elif isinstance(v, (list, str)):
return v
raise ValueError(v)
class Config:
env_file = ".env"
case_sensitive = True
@@ -90,4 +90,4 @@ def get_settings() -> Settings:
"""
Get application settings from environment variables or .env file
"""
return Settings()
return Settings()
+2 -3
View File
@@ -1,11 +1,10 @@
from typing import Generator
from app.core.config import get_settings
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core.config import get_settings
settings = get_settings()
# Configure SQLAlchemy
@@ -24,4 +23,4 @@ def get_db() -> Generator:
try:
yield db
finally:
db.close()
db.close()
+39 -44
View File
@@ -1,14 +1,14 @@
from datetime import datetime, timedelta
from typing import Any, Union, Optional
import secrets
import logging
from fastapi import HTTPException, Security, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader
from jose import jwt, JWTError
from passlib.context import CryptContext
import os
import secrets
from datetime import datetime, timedelta
from typing import Any, Optional, Union
from app.core.config import get_settings
from fastapi import HTTPException, Security, status
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
settings = get_settings()
logger = logging.getLogger(__name__)
@@ -24,7 +24,7 @@ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
# - Development and testing environments
# - Single-instance deployments
# - MVP/prototype applications
#
#
# ⚠️ NOT SUITABLE FOR PRODUCTION when:
# - Running multiple application instances (keys not shared)
# - Requiring key persistence across restarts
@@ -44,7 +44,6 @@ logger.warning(
)
# Check if running in production mode and warn
import os
if os.getenv("ENVIRONMENT", "development").lower() == "production":
logger.error(
"CRITICAL: Running in PRODUCTION mode with in-memory API key storage! "
@@ -56,7 +55,7 @@ if os.getenv("ENVIRONMENT", "development").lower() == "production":
def generate_api_key() -> str:
"""
Generate a secure random API key.
Returns:
A 32-character hexadecimal API key
"""
@@ -66,10 +65,10 @@ def generate_api_key() -> str:
def add_api_key(api_key: str) -> bool:
"""
Add an API key to the valid keys set.
Args:
api_key: The API key to add
Returns:
True if key was added, False if it already existed
"""
@@ -83,28 +82,26 @@ def add_api_key(api_key: str) -> bool:
def verify_api_key(api_key: str) -> bool:
"""
Verify an API key is valid.
Args:
api_key: The API key to verify
Returns:
True if key is valid, False otherwise
"""
return api_key in _api_keys
async def get_api_key(
api_key_header: Optional[str] = Security(api_key_header)
) -> str:
async def get_api_key(api_key_header: Optional[str] = Security(api_key_header)) -> str:
"""
Dependency to verify API key authentication.
Args:
api_key_header: API key from X-API-Key header
Returns:
The validated API key
Raises:
HTTPException: If API key is missing or invalid
"""
@@ -114,30 +111,32 @@ async def get_api_key(
detail="Missing API key",
headers={"WWW-Authenticate": "ApiKey"},
)
if not verify_api_key(api_key_header):
logger.warning(f"Invalid API key attempt: ...{api_key_header[-8:] if len(api_key_header) >= 8 else 'invalid'}")
logger.warning(
f"Invalid API key attempt: ...{api_key_header[-8:] if len(api_key_header) >= 8 else 'invalid'}"
)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API key",
headers={"WWW-Authenticate": "ApiKey"},
)
return api_key_header
async def verify_token(
credentials: Optional[HTTPAuthorizationCredentials] = Security(security_bearer)
credentials: Optional[HTTPAuthorizationCredentials] = Security(security_bearer),
) -> dict:
"""
Dependency to verify JWT token authentication.
Args:
credentials: Bearer token from Authorization header
Returns:
Decoded token payload
Raises:
HTTPException: If token is missing or invalid
"""
@@ -147,9 +146,9 @@ async def verify_token(
detail="Missing authentication token",
headers={"WWW-Authenticate": "Bearer"},
)
token = credentials.credentials
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
return payload
@@ -164,39 +163,37 @@ async def verify_token(
async def require_admin_auth(
api_key: Optional[str] = Security(api_key_header),
bearer: Optional[HTTPAuthorizationCredentials] = Security(security_bearer)
bearer: Optional[HTTPAuthorizationCredentials] = Security(security_bearer),
) -> dict:
"""
Dependency to require either API key or JWT token authentication for admin endpoints.
Checks API key first, then falls back to JWT token.
Args:
api_key: Optional API key from X-API-Key header
bearer: Optional JWT token from Authorization header
Returns:
Authentication context (api_key or token payload)
Raises:
HTTPException: If no valid authentication is provided
"""
# Try API key first
if api_key and verify_api_key(api_key):
return {"auth_type": "api_key", "api_key": api_key}
# Try JWT token
if bearer:
try:
payload = jwt.decode(
bearer.credentials,
settings.SECRET_KEY,
algorithms=[settings.ALGORITHM]
bearer.credentials, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
return {"auth_type": "jwt", "payload": payload}
except JWTError as e:
logger.warning(f"Invalid JWT token: {str(e)}")
# No valid authentication provided
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -205,9 +202,7 @@ async def require_admin_auth(
)
def create_access_token(
subject: Union[str, Any], expires_delta: timedelta = None
) -> str:
def create_access_token(subject: Union[str, Any], expires_delta: timedelta = None) -> str:
"""
Create a JWT access token for authentication
"""
@@ -231,4 +226,4 @@ def get_password_hash(password: str) -> str:
"""
Hash a password
"""
return pwd_context.hash(password)
return pwd_context.hash(password)