Add a score to DNS

This commit is contained in:
nemunaire 2025-10-22 14:06:18 +07:00
commit abfd1f0155
6 changed files with 143 additions and 5 deletions

View file

@ -296,12 +296,19 @@ components:
ScoreSummary:
type: object
required:
- dns_score
- authentication_score
- spam_score
- blacklist_score
- content_score
- header_score
properties:
dns_score:
type: integer
minimum: 0
maximum: 100
description: DNS records score (in percentage)
example: 42
authentication_score:
type: integer
minimum: 0

View file

@ -441,3 +441,103 @@ func (d *DNSAnalyzer) validateBIMI(record string) bool {
return true
}
// CalculateDNSScore calculates the DNS score from records results
// Returns a score from 0-100 where higher is better
func (d *DNSAnalyzer) CalculateDNSScore(results *api.DNSResults) int {
if results == nil {
return 0
}
score := 0
// TODO: 20 points for correct PTR and A/AAAA
// MX Records: 20 points
// Having valid MX records is critical for email deliverability
if results.MxRecords != nil && len(*results.MxRecords) > 0 {
hasValidMX := false
for _, mx := range *results.MxRecords {
if mx.Valid {
hasValidMX = true
break
}
}
if hasValidMX {
score += 20
}
}
// SPF Record: 20 points
// SPF is essential for email authentication
if results.SpfRecord != nil {
if results.SpfRecord.Valid {
score += 20
} else if results.SpfRecord.Record != nil {
// Partial credit if SPF record exists but has issues
score += 5
}
}
// DKIM Records: 20 points
// DKIM provides strong email authentication
if results.DkimRecords != nil && len(*results.DkimRecords) > 0 {
hasValidDKIM := false
for _, dkim := range *results.DkimRecords {
if dkim.Valid {
hasValidDKIM = true
break
}
}
if hasValidDKIM {
score += 20
} else {
// Partial credit if DKIM record exists but has issues
score += 5
}
}
// DMARC Record: 20 points
// DMARC ties SPF and DKIM together and provides policy
if results.DmarcRecord != nil {
if results.DmarcRecord.Valid {
score += 15
// Bonus points for stricter policies
if results.DmarcRecord.Policy != nil {
switch *results.DmarcRecord.Policy {
case "reject":
// Strictest policy - full points already awarded
score += 5
case "quarantine":
// Good policy - no deduction
case "none":
// Weakest policy - deduct 5 points
score -= 5
}
}
} else if results.DmarcRecord.Record != nil {
// Partial credit if DMARC record exists but has issues
score += 5
}
}
// BIMI Record: 5 bonus points
// BIMI is optional but indicates advanced email branding
if results.BimiRecord != nil && results.BimiRecord.Valid {
if score >= 100 {
return 100
}
}
// Ensure score doesn't exceed maximum
if score > 100 {
score = 100
}
// Ensure score is non-negative
if score < 0 {
score = 0
}
return score
}

View file

@ -95,6 +95,11 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu
}
// Calculate scores directly from analyzers (no more checks array)
dnsScore := 0
if results.DNS != nil {
dnsScore = r.dnsAnalyzer.CalculateDNSScore(results.DNS)
}
authScore := 0
if results.Authentication != nil {
authScore = r.authAnalyzer.CalculateAuthenticationScore(results.Authentication)
@ -121,6 +126,7 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu
}
report.Summary = &api.ScoreSummary{
DnsScore: dnsScore,
AuthenticationScore: authScore,
BlacklistScore: blacklistScore,
ContentScore: contentScore,

View file

@ -3,16 +3,24 @@
interface Props {
dnsResults?: DNSResults;
dnsScore?: number;
}
let { dnsResults }: Props = $props();
let { dnsResults, dnsScore }: Props = $props();
</script>
<div class="card shadow-sm">
<div class="card-header bg-white">
<h4 class="mb-0">
<i class="bi bi-diagram-3 me-2"></i>
DNS Records
<h4 class="mb-0 d-flex justify-content-between align-items-center">
<span>
<i class="bi bi-diagram-3 me-2"></i>
DNS Records
</span>
{#if dnsScore !== undefined}
<span class="badge bg-secondary">
{dnsScore}%
</span>
{/if}
</h4>
</div>
<div class="card-body">

View file

@ -36,6 +36,20 @@
{#if summary}
<div class="row g-3 text-start">
<div class="col-md-6 col-lg">
<div class="p-2 bg-light rounded text-center">
<strong
class="fs-2"
class:text-success={summary.dns_score >= 100}
class:text-warning={summary.dns_score < 100 &&
summary.dns_score >= 50}
class:text-danger={summary.dns_score < 50}
>
{summary.dns_score}%
</strong>
<small class="text-muted d-block">DNS</small>
</div>
</div>
<div class="col-md-6 col-lg">
<div class="p-2 bg-light rounded text-center">
<strong

View file

@ -148,7 +148,10 @@
{#if report.dns_results}
<div class="row mb-4" id="dns">
<div class="col-12">
<DnsRecordsCard dnsResults={report.dns_results} />
<DnsRecordsCard
dnsResults={report.dns_results}
dnsScore={report.summary?.dns_score}
/>
</div>
</div>
{/if}