-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
85 lines (71 loc) · 3.25 KB
/
main.py
File metadata and controls
85 lines (71 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import os
from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def get_real_ip(request: Request) -> str:
if "x-real-ip" in request.headers:
return request.headers["x-real-ip"]
if "x-forwarded-for" in request.headers:
return request.headers["x-forwarded-for"].split(",")[0]
if request.client and request.client.host:
return request.client.host
return "127.0.0.1"
limiter = Limiter(key_func=get_real_ip)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# 1. Trusted Host Middleware
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["*"] # Allow all on Vercel to avoid 400 errors for custom domains
)
# 2. CORS Middleware
origins = [
"http://localhost",
"http://localhost:8000",
"http://127.0.0.1",
"http://127.0.0.1:8000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 3. Security Headers Middleware
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://images.unsplash.com https://plus.unsplash.com; font-src 'self' https://fonts.gstatic.com; media-src 'self' blob:;"
return response
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
@app.get("/", response_class=HTMLResponse)
@limiter.limit("60/minute")
async def read_landing(request: Request):
return templates.TemplateResponse(request=request, name="index.html", context={"request": request})
@app.get("/robots.txt", response_class=FileResponse)
async def robots_txt():
return FileResponse(os.path.join(BASE_DIR, "static", "landing", "robots.txt"), media_type="text/plain")
@app.get("/sitemap.xml", response_class=FileResponse)
async def sitemap_xml():
return FileResponse(os.path.join(BASE_DIR, "static", "landing", "sitemap.xml"), media_type="application/xml")
@app.get("/tool", response_class=HTMLResponse)
@limiter.limit("60/minute")
async def read_tool(request: Request):
return templates.TemplateResponse(request=request, name="tool.html", context={"request": request})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)