monorepo/cloud/maplepress-backend/pkg/dns/verifier.go

113 lines
3.2 KiB
Go

package dns
import (
"context"
"fmt"
"net"
"strings"
"time"
"go.uber.org/zap"
)
// Verifier handles DNS TXT record verification
type Verifier struct {
resolver *net.Resolver
logger *zap.Logger
}
// ProvideVerifier creates a new DNS Verifier
func ProvideVerifier(logger *zap.Logger) *Verifier {
return &Verifier{
resolver: &net.Resolver{
PreferGo: true, // Use Go's DNS resolver
},
logger: logger.Named("dns-verifier"),
}
}
// VerifyDomainOwnership checks if a domain has the correct TXT record
// Expected format: "maplepress-verify=TOKEN"
func (v *Verifier) VerifyDomainOwnership(ctx context.Context, domain string, expectedToken string) (bool, error) {
v.logger.Info("verifying domain ownership via DNS",
zap.String("domain", domain))
// Create context with timeout (10 seconds for DNS lookup)
lookupCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// Look up TXT records for the domain
txtRecords, err := v.resolver.LookupTXT(lookupCtx, domain)
if err != nil {
// Check if it's a timeout
if lookupCtx.Err() == context.DeadlineExceeded {
v.logger.Warn("DNS lookup timed out",
zap.String("domain", domain))
return false, fmt.Errorf("DNS lookup timed out after 10 seconds")
}
// Check if domain doesn't exist
if dnsErr, ok := err.(*net.DNSError); ok {
if dnsErr.IsNotFound {
v.logger.Warn("domain not found",
zap.String("domain", domain))
return false, fmt.Errorf("domain not found: %s", domain)
}
}
v.logger.Error("failed to lookup TXT records",
zap.String("domain", domain),
zap.Error(err))
return false, fmt.Errorf("failed to lookup DNS TXT records: %w", err)
}
// Expected verification record format
expectedRecord := fmt.Sprintf("maplepress-verify=%s", expectedToken)
// Check each TXT record
for _, record := range txtRecords {
v.logger.Debug("checking TXT record",
zap.String("domain", domain),
zap.String("record", record))
// Normalize whitespace and compare
normalizedRecord := strings.TrimSpace(record)
if normalizedRecord == expectedRecord {
v.logger.Info("domain ownership verified",
zap.String("domain", domain))
return true, nil
}
}
v.logger.Warn("verification record not found",
zap.String("domain", domain),
zap.String("expected", expectedRecord),
zap.Int("records_checked", len(txtRecords)))
return false, nil
}
// GetVerificationRecord returns the TXT record format for a given token
func GetVerificationRecord(token string) string {
return fmt.Sprintf("maplepress-verify=%s", token)
}
// GetVerificationInstructions returns user-friendly instructions
func GetVerificationInstructions(domain string, token string) string {
record := GetVerificationRecord(token)
return fmt.Sprintf(`To verify ownership of %s, add this DNS TXT record:
Host/Name: %s
Type: TXT
Value: %s
Instructions:
1. Log in to your domain registrar (GoDaddy, Namecheap, Cloudflare, etc.)
2. Find DNS settings or DNS management
3. Add a new TXT record with the values above
4. Wait 5-10 minutes for DNS propagation
5. Click "Verify Domain" in MaplePress
Note: DNS changes can take up to 48 hours to propagate globally, but usually complete within 10 minutes.`,
domain, domain, record)
}