working now

This commit is contained in:
HotSwapp
2025-08-10 19:06:21 -05:00
parent c2f3c4411d
commit 350af60db3
10 changed files with 248 additions and 11 deletions

View File

@@ -3,7 +3,7 @@ Authentication API endpoints
"""
from datetime import datetime, timedelta
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
@@ -24,15 +24,39 @@ from app.auth.schemas import (
ThemePreferenceUpdate
)
from app.config import settings
from app.core.logging import get_logger, log_auth_attempt
router = APIRouter()
logger = get_logger("auth")
@router.post("/login", response_model=Token)
async def login(login_data: LoginRequest, db: Session = Depends(get_db)):
async def login(login_data: LoginRequest, request: Request, db: Session = Depends(get_db)):
"""Login endpoint"""
client_ip = request.client.host if request.client else "unknown"
user_agent = request.headers.get("user-agent", "")
logger.info(
"Login attempt started",
username=login_data.username,
client_ip=client_ip,
user_agent=user_agent
)
user = authenticate_user(db, login_data.username, login_data.password)
if not user:
log_auth_attempt(
username=login_data.username,
success=False,
ip_address=client_ip,
user_agent=user_agent,
error="Invalid credentials"
)
logger.warning(
"Login failed - invalid credentials",
username=login_data.username,
client_ip=client_ip
)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
@@ -47,6 +71,20 @@ async def login(login_data: LoginRequest, db: Session = Depends(get_db)):
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
log_auth_attempt(
username=login_data.username,
success=True,
ip_address=client_ip,
user_agent=user_agent
)
logger.info(
"Login successful",
username=login_data.username,
user_id=user.id,
client_ip=client_ip
)
return {"access_token": access_token, "token_type": "bearer"}
@@ -87,6 +125,7 @@ async def register(
@router.get("/me", response_model=UserResponse)
async def read_users_me(current_user: User = Depends(get_current_user)):
"""Get current user info"""
logger.debug("User info requested", username=current_user.username, user_id=current_user.id)
return current_user

View File

@@ -40,6 +40,12 @@ class Settings(BaseSettings):
secure_cookies: bool = False
compose_project_name: Optional[str] = None
# Logging
log_level: str = "INFO"
log_to_file: bool = True
log_rotation: str = "10 MB"
log_retention: str = "30 days"
class Config:
env_file = ".env"

View File

@@ -12,18 +12,31 @@ from app.database.base import engine
from app.models import BaseModel
from app.models.user import User
from app.auth.security import get_admin_user
from app.core.logging import setup_logging, get_logger
from app.middleware.logging import LoggingMiddleware
# Initialize logging
setup_logging()
logger = get_logger("main")
# Create database tables
logger.info("Creating database tables")
BaseModel.metadata.create_all(bind=engine)
# Initialize FastAPI app
logger.info("Initializing FastAPI application", version=settings.app_version, debug=settings.debug)
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
description="Modern Python web application for Delphi Consulting Group",
)
# Add logging middleware
logger.info("Adding request logging middleware")
app.add_middleware(LoggingMiddleware, log_requests=True, log_responses=settings.debug)
# Configure CORS
logger.info("Configuring CORS middleware")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure appropriately for production
@@ -33,10 +46,12 @@ app.add_middleware(
)
# Mount static files
logger.info("Mounting static file directories")
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
# Templates
logger.info("Initializing Jinja2 templates")
templates = Jinja2Templates(directory="templates")
# Include routers
@@ -51,6 +66,7 @@ from app.api.import_data import router as import_router
from app.api.support import router as support_router
from app.api.settings import router as settings_router
logger.info("Including API routers")
app.include_router(auth_router, prefix="/api/auth", tags=["authentication"])
app.include_router(customers_router, prefix="/api/customers", tags=["customers"])
app.include_router(files_router, prefix="/api/files", tags=["files"])

View File

130
app/middleware/logging.py Normal file
View File

@@ -0,0 +1,130 @@
"""
Request/Response Logging Middleware
"""
import time
import json
from typing import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import StreamingResponse
from app.core.logging import get_logger, log_request
logger = get_logger("middleware.logging")
class LoggingMiddleware(BaseHTTPMiddleware):
"""Middleware to log HTTP requests and responses"""
def __init__(self, app, log_requests: bool = True, log_responses: bool = False):
super().__init__(app)
self.log_requests = log_requests
self.log_responses = log_responses
async def dispatch(self, request: Request, call_next: Callable) -> Response:
# Skip logging for static files and health checks
skip_paths = ["/static/", "/uploads/", "/health", "/favicon.ico"]
if any(request.url.path.startswith(path) for path in skip_paths):
return await call_next(request)
# Record start time
start_time = time.time()
# Extract request details
client_ip = self.get_client_ip(request)
user_agent = request.headers.get("user-agent", "")
# Log request
if self.log_requests:
logger.info(
"Request started",
method=request.method,
path=request.url.path,
query_params=str(request.query_params) if request.query_params else None,
client_ip=client_ip,
user_agent=user_agent
)
# Process request
try:
response = await call_next(request)
except Exception as e:
# Log exceptions
duration_ms = (time.time() - start_time) * 1000
logger.error(
"Request failed with exception",
method=request.method,
path=request.url.path,
duration_ms=duration_ms,
error=str(e),
client_ip=client_ip
)
raise
# Calculate duration
duration_ms = (time.time() - start_time) * 1000
# Extract user ID from request if available (for authenticated requests)
user_id = None
if hasattr(request.state, "user") and request.state.user:
user_id = getattr(request.state.user, "id", None) or getattr(request.state.user, "username", None)
# Log response
log_request(
method=request.method,
path=request.url.path,
status_code=response.status_code,
duration_ms=duration_ms,
user_id=user_id
)
# Log response details if enabled
if self.log_responses:
logger.debug(
"Response details",
status_code=response.status_code,
headers=dict(response.headers),
size_bytes=response.headers.get("content-length"),
content_type=response.headers.get("content-type")
)
# Log slow requests as warnings
if duration_ms > 1000: # More than 1 second
logger.warning(
"Slow request detected",
method=request.method,
path=request.url.path,
duration_ms=duration_ms,
status_code=response.status_code
)
# Log authentication-related requests to auth log
if any(path in request.url.path for path in ["/api/auth/", "/login", "/logout"]):
logger.bind(name="auth").info(
"Auth endpoint accessed",
method=request.method,
path=request.url.path,
status_code=response.status_code,
duration_ms=duration_ms,
client_ip=client_ip,
user_agent=user_agent
)
return response
def get_client_ip(self, request: Request) -> str:
"""Extract client IP address from request headers"""
# Check for IP in common proxy headers
forwarded_for = request.headers.get("x-forwarded-for")
if forwarded_for:
# Take the first IP in the chain
return forwarded_for.split(",")[0].strip()
real_ip = request.headers.get("x-real-ip")
if real_ip:
return real_ip
# Fallback to direct client IP
if request.client:
return request.client.host
return "unknown"

View File

@@ -9,6 +9,9 @@ from fastapi import Request
from app.models.audit import AuditLog, LoginAttempt
from app.models.user import User
from app.core.logging import get_logger
logger = get_logger("audit")
class AuditService:
@@ -73,7 +76,7 @@ class AuditService:
except Exception as e:
db.rollback()
# Log the error but don't fail the main operation
print(f"Failed to log audit entry: {e}")
logger.error("Failed to log audit entry", error=str(e), action=action, user_id=user_id)
return audit_log
@staticmethod
@@ -128,7 +131,7 @@ class AuditService:
except Exception as e:
db.rollback()
# Log the error but don't fail the main operation
print(f"Failed to log login attempt: {e}")
logger.error("Failed to log login attempt", error=str(e), username=username, success=success)
return login_attempt
@staticmethod