package services import ( "context" "crypto/tls" "encoding/json" "fmt" "net" "net/http" "net/smtp" "time" firebase "firebase.google.com/go/v4" "github.com/appwrite/sdk-for-go/appwrite" amqp "github.com/rabbitmq/amqp091-go" "github.com/stripe/stripe-go/v76" "github.com/stripe/stripe-go/v76/balance" "google.golang.org/api/option" ) // TestConnection attempts to use the saved credentials for a specific service to verify they work. func (s *CredentialsService) TestConnection(ctx context.Context, serviceName string) error { // First, check if configured configured, err := s.isServiceConfigured(ctx, serviceName) if err != nil { return fmt.Errorf("failed to check if service is configured: %w", err) } if !configured { return fmt.Errorf("service %s is not configured", serviceName) } // 2. Fetch the credentials payload decrypted, err := s.GetDecryptedKey(ctx, serviceName) if err != nil { return fmt.Errorf("failed to retrieve credentials: %w", err) } var creds map[string]string if err := json.Unmarshal([]byte(decrypted), &creds); err != nil { return fmt.Errorf("failed to parse credentials payload: %w", err) } // 3. Test based on service type switch serviceName { case "stripe": return s.testStripe(creds) case "cloudflare_config": return s.testCloudflare(ctx, creds) case "cpanel": return s.testCPanel(ctx, creds) case "lavinmq": return s.testLavinMQ(creds) case "appwrite": return s.testAppwrite(creds) case "fcm_service_account": return s.testFCM(ctx, creds) case "smtp": return s.testSMTP(creds) default: return fmt.Errorf("testing for service %s is not implemented", serviceName) } } func (s *CredentialsService) testStripe(creds map[string]string) error { secretKey := creds["secretKey"] if secretKey == "" { return fmt.Errorf("missing secretKey in credentials") } stripe.Key = secretKey _, err := balance.Get(nil) if err != nil { return fmt.Errorf("stripe connection test failed: %w", err) } return nil } func (s *CredentialsService) testCloudflare(ctx context.Context, creds map[string]string) error { apiToken := creds["apiToken"] if apiToken == "" { return fmt.Errorf("missing apiToken in credentials") } req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/user/tokens/verify", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+apiToken) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("cloudflare connection failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("cloudflare API returned status: %d", resp.StatusCode) } var result struct { Success bool `json:"success"` Messages []struct { Message string `json:"message"` } `json:"messages"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return fmt.Errorf("failed to parse cloudflare response: %w", err) } if !result.Success { return fmt.Errorf("cloudflare verification failed") } return nil } func (s *CredentialsService) testLavinMQ(creds map[string]string) error { amqpUrl := creds["amqpUrl"] if amqpUrl == "" { return fmt.Errorf("missing amqpUrl in credentials") } conn, err := amqp.Dial(amqpUrl) if err != nil { return fmt.Errorf("lavinmq connection failed: %w", err) } defer conn.Close() return nil } func (s *CredentialsService) testAppwrite(creds map[string]string) error { endpoint := creds["endpoint"] projectId := creds["projectId"] apiKey := creds["apiKey"] if endpoint == "" || projectId == "" || apiKey == "" { return fmt.Errorf("missing required Appwrite credentials") } client := appwrite.NewClient( appwrite.WithEndpoint(endpoint), appwrite.WithProject(projectId), appwrite.WithKey(apiKey), ) health := appwrite.NewHealth(client) _, err := health.Get() if err != nil { return fmt.Errorf("appwrite connection failed: %w", err) } return nil } func (s *CredentialsService) testCPanel(ctx context.Context, creds map[string]string) error { host := creds["host"] username := creds["username"] apiToken := creds["apiToken"] if host == "" || username == "" || apiToken == "" { return fmt.Errorf("missing required cpanel credentials") } // Just checking the base server reachability since testing cPanel API token accurately can be complex. client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, "GET", host, nil) if err != nil { return err } req.Header.Set("Authorization", fmt.Sprintf("cpanel %s:%s", username, apiToken)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("cpanel connection failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { return fmt.Errorf("cpanel authentication failed (status %d)", resp.StatusCode) } return nil } func (s *CredentialsService) testFCM(ctx context.Context, creds map[string]string) error { serviceAccountJson := creds["serviceAccountJson"] if serviceAccountJson == "" { return fmt.Errorf("missing serviceAccountJson in credentials") } opt := option.WithCredentialsJSON([]byte(serviceAccountJson)) app, err := firebase.NewApp(ctx, nil, opt) if err != nil { return fmt.Errorf("failed to initialize FCM app: %w", err) } client, err := app.Messaging(ctx) if err != nil { return fmt.Errorf("failed to get FCM messaging client: %w", err) } // We don't actually send a notification, we just check if we can instance the client // without credential errors. if client == nil { return fmt.Errorf("FCM client is nil") } return nil } func (s *CredentialsService) testSMTP(creds map[string]string) error { host := creds["host"] port := creds["port"] username := creds["username"] password := creds["password"] secure := creds["secure"] if host == "" || port == "" { return fmt.Errorf("missing host or port in smtp credentials") } serverName := host + ":" + port // Set 10s timeout conn, err := tls.DialWithDialer( &net.Dialer{Timeout: 10 * time.Second}, "tcp", serverName, &tls.Config{InsecureSkipVerify: true}, ) if err != nil { if secure == "true" { return fmt.Errorf("secure SMTP connection failed: %w", err) } // Try insecure client, err := smtp.Dial(serverName) if err != nil { return fmt.Errorf("insecure SMTP connection failed: %w", err) } defer client.Close() if username != "" && password != "" { auth := smtp.PlainAuth("", username, password, host) if err = client.Auth(auth); err != nil { return fmt.Errorf("SMTP authentication failed: %w", err) } } return nil } defer conn.Close() client, err := smtp.NewClient(conn, host) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } defer client.Close() if username != "" && password != "" { auth := smtp.PlainAuth("", username, password, host) if err = client.Auth(auth); err != nil { return fmt.Errorf("SMTP authentication failed: %w", err) } } return nil }