mirror of
https://github.com/Rarebuffalo/securelens-backend.git
synced 2026-06-19 07:00:30 +00:00
updated the new feature
This commit is contained in:
@@ -170,3 +170,40 @@ async def generate_threat_narrative(context_data: dict) -> str:
|
||||
|
||||
result = await call_ai(prompt, temperature=0.7)
|
||||
return result or "Could not generate threat narrative."
|
||||
|
||||
|
||||
async def generate_diff_narrative(diff_data: dict) -> str:
|
||||
"""
|
||||
Generates a plain-English summary of the changes between two scans.
|
||||
|
||||
Given the resolved, new, and persisting issues plus the score change,
|
||||
the model writes a short paragraph explaining what improved, what
|
||||
regressed, and what still needs attention — written for a developer
|
||||
who wants to understand progress at a glance.
|
||||
"""
|
||||
if not settings.effective_ai_key:
|
||||
return "AI narration is disabled because no AI API key is configured."
|
||||
|
||||
score_change = diff_data.get("score_change", 0)
|
||||
resolved = diff_data.get("resolved_issues", [])
|
||||
new_issues = diff_data.get("new_issues", [])
|
||||
persisting = diff_data.get("persisting_issues", [])
|
||||
|
||||
prompt = (
|
||||
"You are SecureLens AI, a cybersecurity assistant. "
|
||||
"A developer has run two security scans on the same URL at different points in time. "
|
||||
"Here is the comparison between the two scans:\n\n"
|
||||
f"Score change: {score_change:+d} points\n"
|
||||
f"Issues resolved since last scan ({len(resolved)}): "
|
||||
f"{json.dumps([i.get('issue') for i in resolved])}\n"
|
||||
f"New issues found ({len(new_issues)}): "
|
||||
f"{json.dumps([i.get('issue') for i in new_issues])}\n"
|
||||
f"Issues still present ({len(persisting)}): "
|
||||
f"{json.dumps([i.get('issue') for i in persisting])}\n\n"
|
||||
"Write a short, plain-English summary (2-4 sentences) for the developer. "
|
||||
"Mention what improved, flag any new regressions if they exist, and note what still needs work. "
|
||||
"Be direct and practical — no fluff."
|
||||
)
|
||||
|
||||
result = await call_ai(prompt, temperature=0.4)
|
||||
return result or "Could not generate diff narrative."
|
||||
|
||||
64
app/services/webhook_dispatcher.py
Normal file
64
app/services/webhook_dispatcher.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Webhook Dispatcher
|
||||
==================
|
||||
|
||||
Shared utility for firing HMAC-signed webhook POST requests.
|
||||
|
||||
Previously the dispatch logic lived inline inside scan.py. Moving it here
|
||||
means both the scan router and the background scheduler can call the same
|
||||
function without creating a circular import.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.webhook import Webhook
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def dispatch_webhooks(user_id: str, scan_data: dict, db: AsyncSession) -> None:
|
||||
"""
|
||||
Fetch all active webhooks for a user and POST the scan_data payload to each.
|
||||
|
||||
The payload is JSON-encoded and signed with HMAC-SHA256 if the webhook has
|
||||
a secret key set. The signature is sent in the X-SecureLens-Signature header
|
||||
so the receiving server can verify the request is genuine.
|
||||
|
||||
Failures are logged but never re-raised — a broken webhook should never
|
||||
crash or block the scan response.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(Webhook).where(
|
||||
Webhook.user_id == user_id,
|
||||
Webhook.is_active == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
hooks = result.scalars().all()
|
||||
if not hooks:
|
||||
return
|
||||
|
||||
payload = json.dumps(scan_data).encode("utf-8")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
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
|
||||
)
|
||||
logger.debug(f"Webhook fired: {hook.target_url}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Webhook {hook.target_url} failed: {e}")
|
||||
Reference in New Issue
Block a user