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) }