- SecurityReportService generates standalone branded HTML reports from
stored audit data (grade badge, simulated hacked site, detailed
findings, business impact, call-to-action with contact info)
- GET /security-audit/report/{prospect_id} returns HTMLResponse
- "Generate Report" button on prospect detail security tab opens
report in new browser tab (printable to PDF)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
242 lines
14 KiB
Python
242 lines
14 KiB
Python
# app/modules/prospecting/services/security_report_service.py
|
|
"""
|
|
Generate branded HTML security audit reports from stored audit data.
|
|
|
|
Produces a standalone HTML document suitable for viewing in a browser,
|
|
printing to PDF, or emailing to prospects. Reports include:
|
|
- Security grade and score
|
|
- "What could happen" fear section with simulated hacked site
|
|
- Detailed findings grouped by category
|
|
- Business impact summary
|
|
- Call to action with contact info
|
|
"""
|
|
|
|
import html as html_module
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from app.modules.prospecting.models import ProspectSecurityAudit
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SEVERITY_COLORS = {
|
|
"critical": ("#dc2626", "#fef2f2", "#991b1b"),
|
|
"high": ("#ea580c", "#fff7ed", "#9a3412"),
|
|
"medium": ("#ca8a04", "#fefce8", "#854d0e"),
|
|
"low": ("#2563eb", "#eff6ff", "#1e40af"),
|
|
"info": ("#6b7280", "#f9fafb", "#374151"),
|
|
}
|
|
|
|
GRADE_COLORS = {
|
|
"A+": "#16a34a", "A": "#22c55e", "B": "#eab308",
|
|
"C": "#f97316", "D": "#ef4444", "F": "#991b1b",
|
|
}
|
|
|
|
CATEGORY_LABELS = {
|
|
"transport": "Transport Security (HTTPS/SSL)",
|
|
"headers": "Security Headers",
|
|
"exposure": "Information Exposure",
|
|
"cookies": "Cookie Security",
|
|
"config": "Server Configuration",
|
|
"technology": "Technology & Versions",
|
|
}
|
|
|
|
DEFAULT_CONTACT = {
|
|
"name": "Samir Boulahtit",
|
|
"email": "contact@wizard.lu",
|
|
"phone": "+352 XXX XXX XXX",
|
|
"company": "Wizard",
|
|
"website": "https://wizard.lu",
|
|
"tagline": "Professional Web Development & Security",
|
|
}
|
|
|
|
|
|
class SecurityReportService:
|
|
"""Generate branded HTML security audit reports."""
|
|
|
|
def generate_html_report(
|
|
self,
|
|
audit: ProspectSecurityAudit,
|
|
domain: str,
|
|
contact: dict | None = None,
|
|
) -> str:
|
|
"""Generate a standalone HTML report from stored audit data."""
|
|
contact = contact or DEFAULT_CONTACT
|
|
esc = html_module.escape
|
|
|
|
findings = json.loads(audit.findings_json) if audit.findings_json else []
|
|
technologies = json.loads(audit.technologies_json) if audit.technologies_json else []
|
|
grade = audit.grade
|
|
score = audit.score
|
|
grade_color = GRADE_COLORS.get(grade, "#6b7280")
|
|
|
|
# Severity counts
|
|
sev_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
|
|
for f in findings:
|
|
if not f.get("is_positive") and f["severity"] in sev_counts:
|
|
sev_counts[f["severity"]] += 1
|
|
|
|
# Group findings by category
|
|
categories = ["transport", "headers", "exposure", "cookies", "config", "technology"]
|
|
grouped = {cat: [f for f in findings if f["category"] == cat] for cat in categories}
|
|
|
|
# Build findings HTML
|
|
findings_html = ""
|
|
for cat in categories:
|
|
cat_findings = grouped.get(cat, [])
|
|
if not cat_findings:
|
|
continue
|
|
label = CATEGORY_LABELS.get(cat, cat)
|
|
findings_html += f'<h3 style="margin-top:32px;font-size:18px;color:#1e293b;border-bottom:2px solid #e2e8f0;padding-bottom:8px;">{esc(label)}</h3>\n'
|
|
|
|
for f in cat_findings:
|
|
if f.get("is_positive"):
|
|
findings_html += f"""
|
|
<div style="margin:12px 0;padding:12px 16px;background:#f0fdf4;border-left:4px solid #16a34a;border-radius:0 8px 8px 0;">
|
|
<span style="color:#16a34a;font-weight:600;">✓ {esc(f["title"])}</span>
|
|
<span style="color:#166534;font-size:13px;margin-left:8px;">{esc(f["detail"])}</span>
|
|
</div>"""
|
|
else:
|
|
sev_color, sev_bg, sev_text = SEVERITY_COLORS.get(f["severity"], SEVERITY_COLORS["info"])
|
|
findings_html += f"""
|
|
<div style="margin:16px 0;background:{sev_bg};border:1px solid {sev_color}20;border-radius:12px;overflow:hidden;">
|
|
<div style="padding:16px 20px;">
|
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
|
|
<span style="background:{sev_color};color:white;padding:2px 10px;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase;">{esc(f["severity"])}</span>
|
|
<span style="font-weight:700;color:#1e293b;font-size:15px;">{esc(f["title"])}</span>
|
|
</div>
|
|
<div style="font-size:14px;color:#334155;margin-top:8px;">{esc(f["detail"])}</div>
|
|
</div>
|
|
</div>"""
|
|
|
|
# Technologies
|
|
tech_html = ""
|
|
if technologies:
|
|
tech_items = " ".join(
|
|
f'<span style="background:#f1f5f9;padding:4px 12px;border-radius:20px;font-size:13px;color:#475569;border:1px solid #e2e8f0;">{esc(t)}</span>'
|
|
for t in technologies
|
|
)
|
|
tech_html = f'<div style="margin-top:16px;"><span style="font-size:13px;color:#64748b;font-weight:600;">Technologies:</span><div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;">{tech_items}</div></div>'
|
|
|
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
|
|
return f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Security Audit Report — {esc(domain)}</title>
|
|
<style>
|
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8fafc; color: #1e293b; line-height: 1.6; }}
|
|
.container {{ max-width: 800px; margin: 0 auto; padding: 40px 24px; }}
|
|
@media print {{ body {{ background: white; }} .container {{ padding: 20px; }} .no-print {{ display: none !important; }} .page-break {{ page-break-before: always; }} }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
|
|
<!-- Header -->
|
|
<div style="text-align:center;margin-bottom:48px;">
|
|
<div style="font-size:12px;letter-spacing:3px;color:#94a3b8;text-transform:uppercase;margin-bottom:8px;">🔒 Website Security Audit Report</div>
|
|
<h1 style="font-size:28px;font-weight:800;color:#0f172a;margin-bottom:8px;">{esc(domain)}</h1>
|
|
<div style="font-size:14px;color:#64748b;">Confidential — Prepared for {esc(domain)}</div>
|
|
<div style="font-size:13px;color:#94a3b8;margin-top:4px;">Report generated on {now}</div>
|
|
</div>
|
|
|
|
<!-- Score Card -->
|
|
<div style="background:white;border-radius:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);padding:32px;margin-bottom:32px;text-align:center;">
|
|
<h2 style="font-size:16px;color:#64748b;margin-bottom:24px;">Overall Security Grade</h2>
|
|
<div style="display:inline-flex;align-items:center;justify-content:center;width:120px;height:120px;border-radius:50%;background:{grade_color};margin-bottom:16px;">
|
|
<span style="font-size:48px;font-weight:900;color:white;">{grade}</span>
|
|
</div>
|
|
<div style="font-size:14px;color:#64748b;">Security Score: {score}/100</div>
|
|
<div style="margin-top:16px;height:8px;background:#e2e8f0;border-radius:4px;overflow:hidden;">
|
|
<div style="width:{score}%;height:100%;background:{grade_color};border-radius:4px;"></div>
|
|
</div>
|
|
<div style="margin-top:20px;display:flex;justify-content:center;gap:16px;flex-wrap:wrap;">
|
|
<span style="font-size:13px;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#dc2626;margin-right:4px;"></span>{sev_counts['critical']} Critical</span>
|
|
<span style="font-size:13px;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#ea580c;margin-right:4px;"></span>{sev_counts['high']} High</span>
|
|
<span style="font-size:13px;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#ca8a04;margin-right:4px;"></span>{sev_counts['medium']} Medium</span>
|
|
<span style="font-size:13px;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#2563eb;margin-right:4px;"></span>{sev_counts['low']} Low</span>
|
|
</div>
|
|
{tech_html}
|
|
</div>
|
|
|
|
<!-- What Could Happen -->
|
|
<div class="page-break" style="background:#1e293b;border-radius:16px;padding:32px;margin-bottom:32px;color:white;">
|
|
<h2 style="font-size:20px;font-weight:700;color:#f87171;margin-bottom:24px;text-align:center;">⚠️ What Could Happen To Your Website</h2>
|
|
<div style="background:#0f172a;border-radius:12px;overflow:hidden;border:1px solid #334155;margin-bottom:24px;">
|
|
<div style="background:#1e293b;padding:8px 16px;display:flex;align-items:center;gap:8px;border-bottom:1px solid #334155;">
|
|
<span style="width:10px;height:10px;border-radius:50%;background:#ef4444;"></span>
|
|
<span style="width:10px;height:10px;border-radius:50%;background:#eab308;"></span>
|
|
<span style="width:10px;height:10px;border-radius:50%;background:#22c55e;"></span>
|
|
<div style="flex:1;margin-left:8px;background:#0f172a;border-radius:6px;padding:4px 12px;">
|
|
<span style="font-size:12px;color:#64748b;">🔒 https://{esc(domain)}</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:40px 24px;text-align:center;">
|
|
<div style="font-size:64px;margin-bottom:16px;">💀</div>
|
|
<div style="font-size:28px;font-weight:900;color:#ef4444;margin-bottom:16px;letter-spacing:2px;">WEBSITE COMPROMISED</div>
|
|
<div style="font-size:14px;color:#94a3b8;max-width:500px;margin:0 auto;">This website has been hacked. All customer data including names, emails, phone numbers, and payment information has been stolen.</div>
|
|
</div>
|
|
</div>
|
|
<div style="padding:16px;background:#dc262615;border-radius:8px;border:1px solid #dc262630;margin-bottom:24px;">
|
|
<p style="font-size:13px;color:#fca5a5;">This is a simulation based on real-world attacks. With the vulnerabilities found on your site, this scenario is technically possible.</p>
|
|
</div>
|
|
<h3 style="font-size:16px;color:#f1f5f9;margin-bottom:16px;">Business Impact</h3>
|
|
<ul style="list-style:none;padding:0;">
|
|
<li style="margin-bottom:12px;padding-left:24px;position:relative;font-size:14px;color:#cbd5e1;"><span style="position:absolute;left:0;">❌</span> Reputation Damage — Customers lose trust</li>
|
|
<li style="margin-bottom:12px;padding-left:24px;position:relative;font-size:14px;color:#cbd5e1;"><span style="position:absolute;left:0;">❌</span> GDPR Fines — Up to 4% of annual turnover or €20 million</li>
|
|
<li style="margin-bottom:12px;padding-left:24px;position:relative;font-size:14px;color:#cbd5e1;"><span style="position:absolute;left:0;">❌</span> Google Blacklist — "This site may be hacked" warning kills traffic</li>
|
|
<li style="margin-bottom:12px;padding-left:24px;position:relative;font-size:14px;color:#cbd5e1;"><span style="position:absolute;left:0;">❌</span> Business Downtime — Revenue loss during recovery</li>
|
|
<li style="margin-bottom:12px;padding-left:24px;position:relative;font-size:14px;color:#cbd5e1;"><span style="position:absolute;left:0;">❌</span> Legal Liability — Liable for customers' stolen data</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Detailed Findings -->
|
|
<div style="background:white;border-radius:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);padding:32px;margin-bottom:32px;">
|
|
<h2 style="font-size:20px;font-weight:700;color:#0f172a;margin-bottom:24px;">Detailed Findings</h2>
|
|
{findings_html}
|
|
</div>
|
|
|
|
<!-- Call to Action -->
|
|
<div style="background:linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);border-radius:16px;padding:40px;margin-bottom:32px;color:white;">
|
|
<h2 style="font-size:24px;font-weight:800;margin-bottom:16px;text-align:center;">🛡️ Protect Your Business</h2>
|
|
<p style="font-size:15px;color:#e9d5ff;text-align:center;margin-bottom:24px;max-width:600px;margin-left:auto;margin-right:auto;">
|
|
Every day these vulnerabilities remain unfixed is another day your business and your customers are at risk.
|
|
</p>
|
|
<div style="background:rgba(255,255,255,0.1);border-radius:12px;padding:24px;margin-bottom:24px;">
|
|
<h3 style="font-size:16px;margin-bottom:12px;">What We Offer</h3>
|
|
<ul style="list-style:none;padding:0;">
|
|
<li style="margin-bottom:8px;padding-left:24px;position:relative;font-size:14px;color:#e9d5ff;"><span style="position:absolute;left:0;">✔</span> Complete security audit and remediation plan</li>
|
|
<li style="margin-bottom:8px;padding-left:24px;position:relative;font-size:14px;color:#e9d5ff;"><span style="position:absolute;left:0;">✔</span> Modern, secure website built with best practices</li>
|
|
<li style="margin-bottom:8px;padding-left:24px;position:relative;font-size:14px;color:#e9d5ff;"><span style="position:absolute;left:0;">✔</span> Ongoing security monitoring and maintenance</li>
|
|
<li style="margin-bottom:8px;padding-left:24px;position:relative;font-size:14px;color:#e9d5ff;"><span style="position:absolute;left:0;">✔</span> GDPR compliance and data protection</li>
|
|
</ul>
|
|
</div>
|
|
<div style="text-align:center;">
|
|
<p style="font-size:14px;color:#c4b5fd;margin-bottom:16px;">Contact us today for a free consultation:</p>
|
|
<div style="display:inline-block;background:white;border-radius:12px;padding:20px 32px;text-align:left;">
|
|
<div style="font-size:18px;font-weight:700;color:#6d28d9;margin-bottom:8px;">{esc(contact['name'])}</div>
|
|
<div style="font-size:14px;color:#475569;margin-bottom:4px;">📧 {esc(contact['email'])}</div>
|
|
<div style="font-size:14px;color:#475569;margin-bottom:4px;">📞 {esc(contact['phone'])}</div>
|
|
<div style="font-size:14px;color:#475569;">🌐 {esc(contact['website'])}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Disclaimer -->
|
|
<div style="text-align:center;padding:24px;font-size:12px;color:#94a3b8;line-height:1.7;">
|
|
<p>This report was generated using passive, non-intrusive analysis techniques only. No active exploitation or unauthorized access was attempted.</p>
|
|
<p style="margin-top:8px;">© {datetime.now().year} {esc(contact['company'])} — {esc(contact['tagline'])}</p>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>"""
|
|
|
|
|
|
security_report_service = SecurityReportService()
|