272 lines
7 KiB
Go
272 lines
7 KiB
Go
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
|
|
}
|