import time import os from datetime import datetime from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from database import engine, Base from routes import auth_routes, user_routes Base.metadata.create_all(bind=engine) app = FastAPI( title="SMTP-Enabled Authentication System", description="A beginner-friendly secure authentication system with modular routes and email verification.", version="1.0.0" ) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173", "http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ─── SIMPLE RATE LIMITING & REQUEST LOGGING ─── RATE_LIMIT_WINDOW = 60 RATE_LIMIT_MAX = 60 client_requests = {} @app.middleware("http") async def rate_limiting_and_logging_middleware(request: Request, call_next): client_ip = request.client.host if request.client else "unknown" now = time.time() if client_ip not in client_requests: client_requests[client_ip] = [] client_requests[client_ip] = [t for t in client_requests[client_ip] if now - t < RATE_LIMIT_WINDOW] if len(client_requests[client_ip]) >= RATE_LIMIT_MAX: return JSONResponse( status_code=429, content={"detail": "Too many requests. Please slow down and try again in a minute."} ) client_requests[client_ip].append(now) start_time = time.time() try: response = await call_next(request) except Exception as exc: log_line = f"{datetime.utcnow().isoformat()} - {client_ip} - {request.method} {request.url.path} - ERROR: {str(exc)}\n" with open("requests.log", "a") as log_file: log_file.write(log_line) return JSONResponse( status_code=500, content={"detail": "An internal server error occurred. Please contact the administrator."} ) duration = time.time() - start_time log_line = ( f"{datetime.utcnow().isoformat()} - IP: {client_ip} - " f"{request.method} {request.url.path} - Status: {response.status_code} - " f"Took: {duration:.4f}s\n" ) with open("requests.log", "a") as log_file: log_file.write(log_line) return response app.include_router(auth_routes.router) app.include_router(user_routes.router) @app.get("/api") def home(): return { "status": "online", "message": "Welcome to the SMTP Auth API! Go to /docs to test all endpoints." } # ─── SERVE FRONTEND STATIC FILES (HF Spaces & Production) ─── if os.path.exists("static"): app.mount("/", StaticFiles(directory="static", html=True), name="static") # ─── SPA FALLBACK HANDLER (Serves React Router paths on direct visits/refreshes) ─── @app.exception_handler(404) async def spa_fallback_404_handler(request: Request, exc: Exception): # If the request is not an API call, serve the frontend index.html path = request.url.path if not path.startswith("/auth") and not path.startswith("/admin") and path != "/api": if os.path.exists("static/index.html"): with open("static/index.html", "r") as f: return HTMLResponse(content=f.read(), status_code=200) return JSONResponse(status_code=404, content={"detail": "Not Found"})