security: remove hardcoded secrets, add health endpoint, add security headers

This commit is contained in:
Gan, Jimmy
2026-02-20 22:04:01 +08:00
parent 7aec5976df
commit dd3e8e0d2c
5 changed files with 44 additions and 8 deletions
+31 -3
View File
@@ -1,5 +1,8 @@
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
import asyncio
import os
import httpx
from fastapi import APIRouter, Depends, HTTPException, status, Request
from pydantic import BaseModel
import config
import auth
@@ -26,10 +29,33 @@ class Verify2FARequest(BaseModel):
secret: str
code: str
async def send_failed_login_alert(ip_address: str, username: str, reason: str):
print(f"Triggering Telegram alert for {username} from {ip_address}: {reason}", flush=True)
if not config.TELEGRAM_BOT_TOKEN or not config.TELEGRAM_CHAT_ID:
print("Skipping Telegram alert: missing config", flush=True)
return
msg = f"🚨 *Dashboard Security Alert* 🚨\nFailed login attempt detected.\n\n👤 User: `{username}`\n🌐 IP: `{ip_address}`\n❌ Reason: {reason}"
try:
if config.TELEGRAM_PROXY:
os.environ["HTTP_PROXY"] = config.TELEGRAM_PROXY
os.environ["HTTPS_PROXY"] = config.TELEGRAM_PROXY
async with httpx.AsyncClient() as client:
res = await client.post(
f"https://api.telegram.org/bot{config.TELEGRAM_BOT_TOKEN}/sendMessage",
data={"chat_id": config.TELEGRAM_CHAT_ID, "text": msg, "parse_mode": "Markdown"},
timeout=10.0
)
print(f"Telegram alert sent, status: {res.status_code}", flush=True)
except Exception as e:
print(f"Failed to send Telegram alert: {e}", flush=True)
@router.post("/login", response_model=Token)
async def login(creds: LoginRequest):
async def login(creds: LoginRequest, request: Request):
client_ip = request.client.host
# Verify username/password
if creds.username != config.ADMIN_USER or not auth.verify_password(creds.password, auth.load_password_hash()):
asyncio.create_task(send_failed_login_alert(client_ip, creds.username, "Incorrect username or password"))
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
@@ -40,13 +66,15 @@ async def login(creds: LoginRequest):
totp_secret = auth.load_totp_secret()
if totp_secret:
if not creds.totp_code:
asyncio.create_task(send_failed_login_alert(client_ip, creds.username, "Missing 2FA Code"))
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="2FA code required",
headers={"WWW-Authenticate": "Bearer"},
)
if not auth.verify_totp(creds.totp_code, totp_secret):
raise HTTPException(
asyncio.create_task(send_failed_login_alert(client_ip, creds.username, "Invalid 2FA Code"))
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid 2FA code",
headers={"WWW-Authenticate": "Bearer"},