mirror of
https://github.com/Rarebuffalo/securelens-backend.git
synced 2026-06-19 07:00:30 +00:00
updated the architecture
This commit is contained in:
154
app/routers/scan.py
Normal file
154
app/routers/scan.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, Request, BackgroundTasks
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.middleware.auth import get_optional_user
|
||||
from app.middleware.rate_limiter import limiter
|
||||
from app.models.scan import ScanResult
|
||||
from app.models.user import User
|
||||
from app.models.webhook import Webhook
|
||||
from app.schemas.scan import ScanRequest, ScanResponse
|
||||
from app.services.scanner.cookies import CookieScanner
|
||||
from app.services.scanner.exposure import ExposureScanner
|
||||
from app.services.scanner.headers import HeaderScanner
|
||||
from app.services.scanner.ssl_checker import SSLScanner
|
||||
from app.services.scanner.transport import TransportScanner
|
||||
from app.services.scanner.dns import DNSScanner
|
||||
from app.services.scanner.ports import PortScanner
|
||||
from app.services.scoring import calculate_layer_statuses, calculate_score
|
||||
from app.services.ai import enhance_security_issues
|
||||
from app.utils.validators import validate_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["scan"])
|
||||
|
||||
transport_scanner = TransportScanner()
|
||||
ssl_scanner = SSLScanner()
|
||||
header_scanner = HeaderScanner()
|
||||
cookie_scanner = CookieScanner()
|
||||
exposure_scanner = ExposureScanner()
|
||||
dns_scanner = DNSScanner()
|
||||
port_scanner = PortScanner()
|
||||
|
||||
|
||||
async def dispatch_webhooks(user_id: str, scan_data: dict, db_session):
|
||||
import hmac, hashlib, json
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await db_session.execute(
|
||||
select(Webhook).where(Webhook.user_id == user_id, Webhook.is_active == True)
|
||||
)
|
||||
hooks = result.scalars().all()
|
||||
if not hooks:
|
||||
return
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
payload = json.dumps(scan_data).encode("utf-8")
|
||||
for hook in hooks:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if hook.secret_key:
|
||||
sig = hmac.new(hook.secret_key.encode(), payload, hashlib.sha256).hexdigest()
|
||||
headers["X-SecureLens-Signature"] = sig
|
||||
|
||||
try:
|
||||
await client.post(hook.target_url, content=payload, headers=headers, timeout=5.0)
|
||||
except Exception as e:
|
||||
logger.warning(f"Webhook {hook.target_url} failed: {e}")
|
||||
|
||||
|
||||
@router.post("/scan", response_model=ScanResponse)
|
||||
@limiter.limit(settings.rate_limit)
|
||||
async def scan_website(
|
||||
data: ScanRequest,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User | None = Depends(get_optional_user),
|
||||
):
|
||||
url = validate_url(data.url)
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
dns_task = asyncio.create_task(dns_scanner.scan(url))
|
||||
port_task = asyncio.create_task(port_scanner.scan(url))
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
timeout=httpx.Timeout(settings.scan_timeout),
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
response = await client.get(url)
|
||||
|
||||
all_issues = []
|
||||
all_issues.extend(await transport_scanner.scan(url, response))
|
||||
all_issues.extend(await ssl_scanner.scan(url, response))
|
||||
all_issues.extend(await header_scanner.scan(url, response))
|
||||
all_issues.extend(await cookie_scanner.scan(url, response))
|
||||
all_issues.extend(await exposure_scanner.scan(url, response))
|
||||
|
||||
# Await infrastructure scans
|
||||
all_issues.extend(await dns_task)
|
||||
all_issues.extend(await port_task)
|
||||
|
||||
score = calculate_score(all_issues)
|
||||
layers = calculate_layer_statuses(all_issues)
|
||||
|
||||
if settings.openai_api_key and all_issues:
|
||||
issues_dict_list = [i.model_dump() for i in all_issues]
|
||||
ai_data = await enhance_security_issues(issues_dict_list)
|
||||
enhanced_list = ai_data.get("enhanced_issues", [])
|
||||
enhancement_map = {e.get("issue"): e for e in enhanced_list}
|
||||
for original in all_issues:
|
||||
enh = enhancement_map.get(original.issue)
|
||||
if enh:
|
||||
original.contextual_severity = enh.get("contextual_severity")
|
||||
original.explanation = enh.get("explanation")
|
||||
original.remediation_snippet = enh.get("remediation_snippet")
|
||||
|
||||
scan_id = None
|
||||
created_at = None
|
||||
|
||||
if current_user is not None:
|
||||
layers_dict = {k: v.model_dump() for k, v in layers.items()}
|
||||
issues_list = [i.model_dump() for i in all_issues]
|
||||
|
||||
scan_record = ScanResult(
|
||||
user_id=current_user.id,
|
||||
url=url,
|
||||
security_score=score,
|
||||
layers=layers_dict,
|
||||
issues=issues_list,
|
||||
)
|
||||
db.add(scan_record)
|
||||
await db.flush()
|
||||
scan_id = scan_record.id
|
||||
created_at = scan_record.created_at
|
||||
|
||||
scan_summary = {
|
||||
"scan_id": scan_id,
|
||||
"url": url,
|
||||
"score": score
|
||||
}
|
||||
background_tasks.add_task(dispatch_webhooks, current_user.id, scan_summary, db)
|
||||
|
||||
return ScanResponse(
|
||||
id=scan_id,
|
||||
url=url,
|
||||
security_score=score,
|
||||
layers=layers,
|
||||
issues=all_issues,
|
||||
created_at=created_at,
|
||||
)
|
||||
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"Scan failed for {url}: {e}")
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={"error": f"Could not reach {url}: {str(e)}"},
|
||||
)
|
||||
Reference in New Issue
Block a user