Rework DNS results

This commit is contained in:
nemunaire 2025-10-21 17:00:15 +07:00
commit 8fbea49334
5 changed files with 441 additions and 286 deletions

View file

@ -267,10 +267,8 @@ components:
$ref: '#/components/schemas/AuthenticationResults'
spamassassin:
$ref: '#/components/schemas/SpamAssassinResult'
dns_records:
type: array
items:
$ref: '#/components/schemas/DNSRecord'
dns_results:
$ref: '#/components/schemas/DNSResults'
blacklists:
type: object
additionalProperties:
@ -694,31 +692,168 @@ components:
type: string
description: Full SpamAssassin report
DNSRecord:
DNSResults:
type: object
required:
- domain
- record_type
- status
properties:
domain:
type: string
description: Domain name
example: "example.com"
record_type:
mx_records:
type: array
items:
$ref: '#/components/schemas/MXRecord'
description: MX records for the domain
spf_record:
$ref: '#/components/schemas/SPFRecord'
dkim_records:
type: array
items:
$ref: '#/components/schemas/DKIMRecord'
description: DKIM records found
dmarc_record:
$ref: '#/components/schemas/DMARCRecord'
bimi_record:
$ref: '#/components/schemas/BIMIRecord'
errors:
type: array
items:
type: string
description: DNS lookup errors
MXRecord:
type: object
required:
- host
- priority
- valid
properties:
host:
type: string
enum: [MX, SPF, DKIM, DMARC, BIMI]
description: DNS record type
example: "SPF"
status:
description: MX hostname
example: "mail.example.com"
priority:
type: integer
format: uint16
description: MX priority (lower is higher priority)
example: 10
valid:
type: boolean
description: Whether the MX record is valid
example: true
error:
type: string
enum: [found, missing, invalid]
description: Record status
example: "found"
value:
description: Error message if validation failed
example: "Failed to lookup MX records"
SPFRecord:
type: object
required:
- valid
properties:
record:
type: string
description: Record value
description: SPF record content
example: "v=spf1 include:_spf.example.com ~all"
valid:
type: boolean
description: Whether the SPF record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No SPF record found"
DKIMRecord:
type: object
required:
- selector
- domain
- valid
properties:
selector:
type: string
description: DKIM selector
example: "default"
domain:
type: string
description: Domain name
example: "example.com"
record:
type: string
description: DKIM record content
example: "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA..."
valid:
type: boolean
description: Whether the DKIM record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No DKIM record found"
DMARCRecord:
type: object
required:
- valid
properties:
record:
type: string
description: DMARC record content
example: "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
policy:
type: string
enum: [none, quarantine, reject, unknown]
description: DMARC policy
example: "quarantine"
valid:
type: boolean
description: Whether the DMARC record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No DMARC record found"
BIMIRecord:
type: object
required:
- selector
- domain
- valid
properties:
selector:
type: string
description: BIMI selector
example: "default"
domain:
type: string
description: Domain name
example: "example.com"
record:
type: string
description: BIMI record content
example: "v=BIMI1; l=https://example.com/logo.svg"
logo_url:
type: string
format: uri
description: URL to the brand logo (SVG)
example: "https://example.com/logo.svg"
vmc_url:
type: string
format: uri
description: URL to Verified Mark Certificate (optional)
example: "https://example.com/vmc.pem"
valid:
type: boolean
description: Whether the BIMI record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No BIMI record found"
BlacklistCheck:
type: object

View file

@ -51,79 +51,25 @@ func NewDNSAnalyzer(timeout time.Duration) *DNSAnalyzer {
}
}
// DNSResults represents DNS validation results for an email
type DNSResults struct {
Domain string
MXRecords []MXRecord
SPFRecord *SPFRecord
DKIMRecords []DKIMRecord
DMARCRecord *DMARCRecord
BIMIRecord *BIMIRecord
Errors []string
}
// MXRecord represents an MX record
type MXRecord struct {
Host string
Priority uint16
Valid bool
Error string
}
// SPFRecord represents an SPF record
type SPFRecord struct {
Record string
Valid bool
Error string
}
// DKIMRecord represents a DKIM record
type DKIMRecord struct {
Selector string
Domain string
Record string
Valid bool
Error string
}
// DMARCRecord represents a DMARC record
type DMARCRecord struct {
Record string
Policy string // none, quarantine, reject
Valid bool
Error string
}
// BIMIRecord represents a BIMI record
type BIMIRecord struct {
Selector string
Domain string
Record string
LogoURL string // URL to the brand logo (SVG)
VMCURL string // URL to Verified Mark Certificate (optional)
Valid bool
Error string
}
// AnalyzeDNS performs DNS validation for the email's domain
func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, authResults *api.AuthenticationResults) *DNSResults {
func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, authResults *api.AuthenticationResults) *api.DNSResults {
// Extract domain from From address
domain := d.extractDomain(email)
if domain == "" {
return &DNSResults{
Errors: []string{"Unable to extract domain from email"},
return &api.DNSResults{
Errors: &[]string{"Unable to extract domain from email"},
}
}
results := &DNSResults{
results := &api.DNSResults{
Domain: domain,
}
// Check MX records
results.MXRecords = d.checkMXRecords(domain)
results.MxRecords = d.checkMXRecords(domain)
// Check SPF record
results.SPFRecord = d.checkSPFRecord(domain)
results.SpfRecord = d.checkSPFRecord(domain)
// Check DKIM records (from authentication results)
if authResults != nil && authResults.Dkim != nil {
@ -131,17 +77,20 @@ func (d *DNSAnalyzer) AnalyzeDNS(email *EmailMessage, authResults *api.Authentic
if dkim.Domain != nil && dkim.Selector != nil {
dkimRecord := d.checkDKIMRecord(*dkim.Domain, *dkim.Selector)
if dkimRecord != nil {
results.DKIMRecords = append(results.DKIMRecords, *dkimRecord)
if results.DkimRecords == nil {
results.DkimRecords = new([]api.DKIMRecord)
}
*results.DkimRecords = append(*results.DkimRecords, *dkimRecord)
}
}
}
}
// Check DMARC record
results.DMARCRecord = d.checkDMARCRecord(domain)
results.DmarcRecord = d.checkDMARCRecord(domain)
// Check BIMI record (using default selector)
results.BIMIRecord = d.checkBIMIRecord(domain, "default")
results.BimiRecord = d.checkBIMIRecord(domain, "default")
return results
}
@ -158,51 +107,51 @@ func (d *DNSAnalyzer) extractDomain(email *EmailMessage) string {
}
// checkMXRecords looks up MX records for a domain
func (d *DNSAnalyzer) checkMXRecords(domain string) []MXRecord {
func (d *DNSAnalyzer) checkMXRecords(domain string) *[]api.MXRecord {
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout)
defer cancel()
mxRecords, err := d.resolver.LookupMX(ctx, domain)
if err != nil {
return []MXRecord{
return &[]api.MXRecord{
{
Valid: false,
Error: fmt.Sprintf("Failed to lookup MX records: %v", err),
Error: api.PtrTo(fmt.Sprintf("Failed to lookup MX records: %v", err)),
},
}
}
if len(mxRecords) == 0 {
return []MXRecord{
return &[]api.MXRecord{
{
Valid: false,
Error: "No MX records found",
Error: api.PtrTo("No MX records found"),
},
}
}
var results []MXRecord
var results []api.MXRecord
for _, mx := range mxRecords {
results = append(results, MXRecord{
results = append(results, api.MXRecord{
Host: mx.Host,
Priority: mx.Pref,
Valid: true,
})
}
return results
return &results
}
// checkSPFRecord looks up and validates SPF record for a domain
func (d *DNSAnalyzer) checkSPFRecord(domain string) *SPFRecord {
func (d *DNSAnalyzer) checkSPFRecord(domain string) *api.SPFRecord {
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout)
defer cancel()
txtRecords, err := d.resolver.LookupTXT(ctx, domain)
if err != nil {
return &SPFRecord{
return &api.SPFRecord{
Valid: false,
Error: fmt.Sprintf("Failed to lookup TXT records: %v", err),
Error: api.PtrTo(fmt.Sprintf("Failed to lookup TXT records: %v", err)),
}
}
@ -217,31 +166,31 @@ func (d *DNSAnalyzer) checkSPFRecord(domain string) *SPFRecord {
}
if spfCount == 0 {
return &SPFRecord{
return &api.SPFRecord{
Valid: false,
Error: "No SPF record found",
Error: api.PtrTo("No SPF record found"),
}
}
if spfCount > 1 {
return &SPFRecord{
Record: spfRecord,
return &api.SPFRecord{
Record: &spfRecord,
Valid: false,
Error: "Multiple SPF records found (RFC violation)",
Error: api.PtrTo("Multiple SPF records found (RFC violation)"),
}
}
// Basic validation
if !d.validateSPF(spfRecord) {
return &SPFRecord{
Record: spfRecord,
return &api.SPFRecord{
Record: &spfRecord,
Valid: false,
Error: "SPF record appears malformed",
Error: api.PtrTo("SPF record appears malformed"),
}
}
return &SPFRecord{
Record: spfRecord,
return &api.SPFRecord{
Record: &spfRecord,
Valid: true,
}
}
@ -267,8 +216,8 @@ func (d *DNSAnalyzer) validateSPF(record string) bool {
return hasValidEnding
}
// checkDKIMRecord looks up and validates DKIM record for a domain and selector
func (d *DNSAnalyzer) checkDKIMRecord(domain, selector string) *DKIMRecord {
// checkapi.DKIMRecord looks up and validates DKIM record for a domain and selector
func (d *DNSAnalyzer) checkDKIMRecord(domain, selector string) *api.DKIMRecord {
// DKIM records are at: selector._domainkey.domain
dkimDomain := fmt.Sprintf("%s._domainkey.%s", selector, domain)
@ -277,20 +226,20 @@ func (d *DNSAnalyzer) checkDKIMRecord(domain, selector string) *DKIMRecord {
txtRecords, err := d.resolver.LookupTXT(ctx, dkimDomain)
if err != nil {
return &DKIMRecord{
return &api.DKIMRecord{
Selector: selector,
Domain: domain,
Valid: false,
Error: fmt.Sprintf("Failed to lookup DKIM record: %v", err),
Error: api.PtrTo(fmt.Sprintf("Failed to lookup DKIM record: %v", err)),
}
}
if len(txtRecords) == 0 {
return &DKIMRecord{
return &api.DKIMRecord{
Selector: selector,
Domain: domain,
Valid: false,
Error: "No DKIM record found",
Error: api.PtrTo("No DKIM record found"),
}
}
@ -299,19 +248,19 @@ func (d *DNSAnalyzer) checkDKIMRecord(domain, selector string) *DKIMRecord {
// Basic validation - should contain "v=DKIM1" and "p=" (public key)
if !d.validateDKIM(dkimRecord) {
return &DKIMRecord{
return &api.DKIMRecord{
Selector: selector,
Domain: domain,
Record: dkimRecord,
Record: api.PtrTo(dkimRecord),
Valid: false,
Error: "DKIM record appears malformed",
Error: api.PtrTo("DKIM record appears malformed"),
}
}
return &DKIMRecord{
return &api.DKIMRecord{
Selector: selector,
Domain: domain,
Record: dkimRecord,
Record: &dkimRecord,
Valid: true,
}
}
@ -332,8 +281,8 @@ func (d *DNSAnalyzer) validateDKIM(record string) bool {
return true
}
// checkDMARCRecord looks up and validates DMARC record for a domain
func (d *DNSAnalyzer) checkDMARCRecord(domain string) *DMARCRecord {
// checkapi.DMARCRecord looks up and validates DMARC record for a domain
func (d *DNSAnalyzer) checkDMARCRecord(domain string) *api.DMARCRecord {
// DMARC records are at: _dmarc.domain
dmarcDomain := fmt.Sprintf("_dmarc.%s", domain)
@ -342,9 +291,9 @@ func (d *DNSAnalyzer) checkDMARCRecord(domain string) *DMARCRecord {
txtRecords, err := d.resolver.LookupTXT(ctx, dmarcDomain)
if err != nil {
return &DMARCRecord{
return &api.DMARCRecord{
Valid: false,
Error: fmt.Sprintf("Failed to lookup DMARC record: %v", err),
Error: api.PtrTo(fmt.Sprintf("Failed to lookup DMARC record: %v", err)),
}
}
@ -358,9 +307,9 @@ func (d *DNSAnalyzer) checkDMARCRecord(domain string) *DMARCRecord {
}
if dmarcRecord == "" {
return &DMARCRecord{
return &api.DMARCRecord{
Valid: false,
Error: "No DMARC record found",
Error: api.PtrTo("No DMARC record found"),
}
}
@ -369,17 +318,17 @@ func (d *DNSAnalyzer) checkDMARCRecord(domain string) *DMARCRecord {
// Basic validation
if !d.validateDMARC(dmarcRecord) {
return &DMARCRecord{
Record: dmarcRecord,
Policy: policy,
return &api.DMARCRecord{
Record: &dmarcRecord,
Policy: api.PtrTo(api.DMARCRecordPolicy(policy)),
Valid: false,
Error: "DMARC record appears malformed",
Error: api.PtrTo("DMARC record appears malformed"),
}
}
return &DMARCRecord{
Record: dmarcRecord,
Policy: policy,
return &api.DMARCRecord{
Record: &dmarcRecord,
Policy: api.PtrTo(api.DMARCRecordPolicy(policy)),
Valid: true,
}
}
@ -411,7 +360,7 @@ func (d *DNSAnalyzer) validateDMARC(record string) bool {
}
// checkBIMIRecord looks up and validates BIMI record for a domain and selector
func (d *DNSAnalyzer) checkBIMIRecord(domain, selector string) *BIMIRecord {
func (d *DNSAnalyzer) checkBIMIRecord(domain, selector string) *api.BIMIRecord {
// BIMI records are at: selector._bimi.domain
bimiDomain := fmt.Sprintf("%s._bimi.%s", selector, domain)
@ -420,20 +369,20 @@ func (d *DNSAnalyzer) checkBIMIRecord(domain, selector string) *BIMIRecord {
txtRecords, err := d.resolver.LookupTXT(ctx, bimiDomain)
if err != nil {
return &BIMIRecord{
return &api.BIMIRecord{
Selector: selector,
Domain: domain,
Valid: false,
Error: fmt.Sprintf("Failed to lookup BIMI record: %v", err),
Error: api.PtrTo(fmt.Sprintf("Failed to lookup BIMI record: %v", err)),
}
}
if len(txtRecords) == 0 {
return &BIMIRecord{
return &api.BIMIRecord{
Selector: selector,
Domain: domain,
Valid: false,
Error: "No BIMI record found",
Error: api.PtrTo("No BIMI record found"),
}
}
@ -446,23 +395,23 @@ func (d *DNSAnalyzer) checkBIMIRecord(domain, selector string) *BIMIRecord {
// Basic validation - should contain "v=BIMI1" and "l=" (logo URL)
if !d.validateBIMI(bimiRecord) {
return &BIMIRecord{
return &api.BIMIRecord{
Selector: selector,
Domain: domain,
Record: bimiRecord,
LogoURL: logoURL,
VMCURL: vmcURL,
Record: &bimiRecord,
LogoUrl: &logoURL,
VmcUrl: &vmcURL,
Valid: false,
Error: "BIMI record appears malformed",
Error: api.PtrTo("BIMI record appears malformed"),
}
}
return &BIMIRecord{
return &api.BIMIRecord{
Selector: selector,
Domain: domain,
Record: bimiRecord,
LogoURL: logoURL,
VMCURL: vmcURL,
Record: &bimiRecord,
LogoUrl: &logoURL,
VmcUrl: &vmcURL,
Valid: true,
}
}

View file

@ -62,7 +62,7 @@ type AnalysisResults struct {
Email *EmailMessage
Authentication *api.AuthenticationResults
Content *ContentResults
DNS *DNSResults
DNS *api.DNSResults
Headers *api.HeaderAnalysis
RBL *RBLResults
SpamAssassin *SpamAssassinResult
@ -141,10 +141,7 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu
// Add DNS records
if results.DNS != nil {
dnsRecords := r.buildDNSRecords(results.DNS)
if len(dnsRecords) > 0 {
report.DnsRecords = &dnsRecords
}
report.DnsResults = results.DNS
}
// Add headers results
@ -204,118 +201,6 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu
return report
}
// buildDNSRecords converts DNS analysis results to API DNS records
func (r *ReportGenerator) buildDNSRecords(dns *DNSResults) []api.DNSRecord {
records := []api.DNSRecord{}
if dns == nil {
return records
}
// MX records
if len(dns.MXRecords) > 0 {
for _, mx := range dns.MXRecords {
status := api.Found
if !mx.Valid {
if mx.Error != "" {
status = api.Missing
} else {
status = api.Invalid
}
}
record := api.DNSRecord{
Domain: dns.Domain,
RecordType: api.MX,
Status: status,
}
if mx.Host != "" {
value := mx.Host
record.Value = &value
}
records = append(records, record)
}
}
// SPF record
if dns.SPFRecord != nil {
status := api.Found
if !dns.SPFRecord.Valid {
if dns.SPFRecord.Record == "" {
status = api.Missing
} else {
status = api.Invalid
}
}
record := api.DNSRecord{
Domain: dns.Domain,
RecordType: api.SPF,
Status: status,
}
if dns.SPFRecord.Record != "" {
record.Value = &dns.SPFRecord.Record
}
records = append(records, record)
}
// DKIM records
for _, dkim := range dns.DKIMRecords {
status := api.Found
if !dkim.Valid {
if dkim.Record == "" {
status = api.Missing
} else {
status = api.Invalid
}
}
record := api.DNSRecord{
Domain: dkim.Domain,
RecordType: api.DKIM,
Status: status,
}
if dkim.Record != "" {
// Include selector in value for clarity
value := dkim.Record
record.Value = &value
}
records = append(records, record)
}
// DMARC record
if dns.DMARCRecord != nil {
status := api.Found
if !dns.DMARCRecord.Valid {
if dns.DMARCRecord.Record == "" {
status = api.Missing
} else {
status = api.Invalid
}
}
record := api.DNSRecord{
Domain: dns.Domain,
RecordType: api.DMARC,
Status: status,
}
if dns.DMARCRecord.Record != "" {
record.Value = &dns.DMARCRecord.Record
}
records = append(records, record)
}
return records
}
// GenerateRawEmail returns the raw email message as a string
func (r *ReportGenerator) GenerateRawEmail(email *EmailMessage) string {
if email == nil {

View file

@ -1,11 +1,11 @@
<script lang="ts">
import type { DNSRecord } from "$lib/api/types.gen";
import type { DNSResults } from "$lib/api/types.gen";
interface Props {
dnsRecords: DNSRecord[];
dnsResults?: DNSResults;
}
let { dnsRecords }: Props = $props();
let { dnsResults }: Props = $props();
</script>
<div class="card shadow-sm">
@ -16,31 +16,217 @@
</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Domain</th>
<th>Type</th>
<th>Status</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{#each dnsRecords as record}
<tr>
<td><code>{record.domain}</code></td>
<td><span class="badge bg-secondary">{record.record_type}</span></td>
<td>
<span class="badge {record.status === 'found' ? 'bg-success' : record.status === 'missing' ? 'bg-danger' : 'bg-warning'}">
{record.status}
</span>
</td>
<td><small class="text-muted">{record.value || '-'}</small></td>
</tr>
{#if !dnsResults}
<p class="text-muted mb-0">No DNS results available</p>
{:else}
<div class="mb-3">
<strong>Domain:</strong> <code>{dnsResults.domain}</code>
</div>
{#if dnsResults.errors && dnsResults.errors.length > 0}
<div class="alert alert-warning mb-3">
<strong>Errors:</strong>
<ul class="mb-0">
{#each dnsResults.errors as error}
<li>{error}</li>
{/each}
</ul>
</div>
{/if}
<!-- MX Records -->
{#if dnsResults.mx_records && dnsResults.mx_records.length > 0}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">MX</span> Mail Exchange Records
</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Priority</th>
<th>Host</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{#each dnsResults.mx_records as mx}
<tr>
<td>{mx.priority}</td>
<td><code>{mx.host}</code></td>
<td>
{#if mx.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{#if mx.error}
<br><small class="text-danger">{mx.error}</small>
{/if}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
<!-- SPF Record -->
{#if dnsResults.spf_record}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">SPF</span> Sender Policy Framework
</h5>
<div class="card">
<div class="card-body">
<div class="mb-2">
<strong>Status:</strong>
{#if dnsResults.spf_record.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dnsResults.spf_record.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break">{dnsResults.spf_record.record}</code>
</div>
{/if}
{#if dnsResults.spf_record.error}
<div class="text-danger">
<strong>Error:</strong> {dnsResults.spf_record.error}
</div>
{/if}
</div>
</div>
</div>
{/if}
<!-- DKIM Records -->
{#if dnsResults.dkim_records && dnsResults.dkim_records.length > 0}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">DKIM</span> DomainKeys Identified Mail
</h5>
{#each dnsResults.dkim_records as dkim}
<div class="card mb-2">
<div class="card-body">
<div class="mb-2">
<strong>Selector:</strong> <code>{dkim.selector}</code>
<strong class="ms-3">Domain:</strong> <code>{dkim.domain}</code>
</div>
<div class="mb-2">
<strong>Status:</strong>
{#if dkim.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dkim.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break small">{dkim.record}</code>
</div>
{/if}
{#if dkim.error}
<div class="text-danger">
<strong>Error:</strong> {dkim.error}
</div>
{/if}
</div>
</div>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
<!-- DMARC Record -->
{#if dnsResults.dmarc_record}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">DMARC</span> Domain-based Message Authentication
</h5>
<div class="card">
<div class="card-body">
<div class="mb-2">
<strong>Status:</strong>
{#if dnsResults.dmarc_record.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dnsResults.dmarc_record.policy}
<div class="mb-2">
<strong>Policy:</strong>
<span class="badge {dnsResults.dmarc_record.policy === 'reject' ? 'bg-success' : dnsResults.dmarc_record.policy === 'quarantine' ? 'bg-warning' : 'bg-secondary'}">
{dnsResults.dmarc_record.policy}
</span>
</div>
{/if}
{#if dnsResults.dmarc_record.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break">{dnsResults.dmarc_record.record}</code>
</div>
{/if}
{#if dnsResults.dmarc_record.error}
<div class="text-danger">
<strong>Error:</strong> {dnsResults.dmarc_record.error}
</div>
{/if}
</div>
</div>
</div>
{/if}
<!-- BIMI Record -->
{#if dnsResults.bimi_record}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">BIMI</span> Brand Indicators for Message Identification
</h5>
<div class="card">
<div class="card-body">
<div class="mb-2">
<strong>Selector:</strong> <code>{dnsResults.bimi_record.selector}</code>
<strong class="ms-3">Domain:</strong> <code>{dnsResults.bimi_record.domain}</code>
</div>
<div class="mb-2">
<strong>Status:</strong>
{#if dnsResults.bimi_record.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dnsResults.bimi_record.logo_url}
<div class="mb-2">
<strong>Logo URL:</strong> <a href={dnsResults.bimi_record.logo_url} target="_blank" rel="noopener noreferrer">{dnsResults.bimi_record.logo_url}</a>
</div>
{/if}
{#if dnsResults.bimi_record.vmc_url}
<div class="mb-2">
<strong>VMC URL:</strong> <a href={dnsResults.bimi_record.vmc_url} target="_blank" rel="noopener noreferrer">{dnsResults.bimi_record.vmc_url}</a>
</div>
{/if}
{#if dnsResults.bimi_record.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break">{dnsResults.bimi_record.record}</code>
</div>
{/if}
{#if dnsResults.bimi_record.error}
<div class="text-danger">
<strong>Error:</strong> {dnsResults.bimi_record.error}
</div>
{/if}
</div>
</div>
</div>
{/if}
{/if}
</div>
</div>

View file

@ -145,10 +145,10 @@
</div>
<!-- DNS Records -->
{#if report.dns_records && report.dns_records.length > 0}
{#if report.dns_results}
<div class="row mb-4" id="dns">
<div class="col-12">
<DnsRecordsCard dnsRecords={report.dns_records} />
<DnsRecordsCard dnsResults={report.dns_results} />
</div>
</div>
{/if}