feat: Enhance configuration forms and validation for storage providers

- Added hidden input fields to ensure critical values are submitted with the form for S3 and other storage providers.
- Implemented JavaScript functionality to auto-populate hidden fields based on user input for S3-compatible providers.
- Updated validation logic for WebDAV and SMB providers to ensure required fields are checked.
- Enhanced the configuration handler to explicitly set fields for various storage provider types, including S3, Wasabi, MinIO, and B2.
- Improved error handling and logging for form submissions and provider credential management.
This commit is contained in:
StarFleetCPTN committed 2025-04-18 02:43:22 -07:00
1 parent 776c4971a6
commit dc665b9fa4
8 files changed
+661 -30

No files matched your search

+1 -3
View File
@@ -169,9 +169,7 @@ func (sp *StorageProvider) SetAuthenticated(value bool) {
func (sp *StorageProvider) IsOAuthProvider() bool {
return sp.Type == ProviderTypeOneDrive ||
sp.Type == ProviderTypeGoogleDrive ||
sp.Type == ProviderTypeGooglePhoto ||
sp.Type == ProviderTypeWebDAV ||
sp.Type == ProviderTypeNextcloud
sp.Type == ProviderTypeGooglePhoto
}
// RequiresEncryption returns true if the provider has sensitive fields that need encryption
+20 -4
View File
@@ -30,6 +30,8 @@ func (sp *StorageProvider) Validate() error {
return sp.validateS3()
case ProviderTypeFTP:
return sp.validateFTP()
case ProviderTypeWebDAV, ProviderTypeNextcloud:
return sp.validateWebDAV()
case ProviderTypeSMB:
return sp.validateSMB()
case ProviderTypeOneDrive:
@@ -67,6 +69,24 @@ func (sp *StorageProvider) validateSFTP() error {
return nil
}
// validateWebDAV validates WebDAV-specific fields
func (sp *StorageProvider) validateWebDAV() error {
if strings.TrimSpace(sp.Host) == "" {
return errors.New("host is required for WebDAV provider")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for WebDAV provider")
}
// Either password or encrypted password must be provided
if strings.TrimSpace(sp.Password) == "" && strings.TrimSpace(sp.EncryptedPassword) == "" {
return errors.New("password is required for WebDAV provider")
}
return nil
}
// validateS3 validates S3-specific fields
func (sp *StorageProvider) validateS3() error {
// For S3, either AccessKey or Username is used
@@ -115,10 +135,6 @@ func (sp *StorageProvider) validateSMB() error {
return errors.New("host is required for SMB provider")
}
if strings.TrimSpace(sp.Share) == "" {
return errors.New("share is required for SMB provider")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for SMB provider")
}
+232 -6
View File
@@ -191,6 +191,56 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create source config (sftp): %v\nOutput: %s", err, output)
}
case "smb":
args := []string{
"config", "create", sourceName, "smb",
"host", getStringValue(sourceCredentials, "host", config.SourceHost),
"user", getStringValue(sourceCredentials, "username", config.SourceUser),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Get domain if provided
domain := getStringValue(sourceCredentials, "domain", config.SourceDomain)
if domain != "" {
args = append(args, "domain", domain)
}
// Get port if specified (default is 445)
port := getIntValue(sourceCredentials, "port", config.SourcePort)
if port > 0 && port != 445 {
args = append(args, "port", fmt.Sprintf("%d", port))
}
// Get share if provided
share := getStringValue(sourceCredentials, "share", config.SourceShare)
if share != "" {
args = append(args, "share", share)
}
// Handle password
password := ""
if config.SourcePassword != "" {
password = config.SourcePassword
} else if encryptedPwd, ok := sourceCredentials["encrypted_password"].(string); ok && encryptedPwd != "" {
decryptedPwd, err := db.DecryptCredential(encryptedPwd)
if err != nil {
return fmt.Errorf("failed to decrypt source password: %v", err)
}
password = decryptedPwd
} else if pwVal, ok := sourceCredentials["password"].(string); ok && pwVal != "" {
password = pwVal
}
if password != "" {
args = append(args, "pass", password)
}
cmd := exec.Command(rclonePath, args...)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create source config (smb): %v\nOutput: %s", err, output)
}
case "s3":
args := []string{
"config", "create", sourceName, "s3",
@@ -269,12 +319,36 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
case "b2":
args := []string{
"config", "create", sourceName, "b2",
"account", config.SourceAccessKey, // B2 Account ID
"key", config.SourceSecretKey, // B2 Application Key
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Handle account ID (access key)
accountID := getStringValue(sourceCredentials, "access_key", config.SourceAccessKey)
if accountID != "" {
args = append(args, "account", accountID)
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.SourceSecretKey != "" {
// Direct input from form (transient)
secretKey = config.SourceSecretKey
} else if encryptedSecret, ok := sourceCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt source secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := sourceCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "key", secretKey)
}
if config.SourceEndpoint != "" {
args = append(args, "endpoint", config.SourceEndpoint)
}
@@ -288,12 +362,32 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
"provider", "Minio",
"env_auth", "false",
"access_key_id", getStringValue(sourceCredentials, "access_key", config.SourceAccessKey),
"secret_access_key", getStringOrDefault(sourceCredentials, "secret_key", config.SourceSecretKey),
"endpoint", getStringValue(sourceCredentials, "endpoint", config.SourceEndpoint),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.SourceSecretKey != "" {
// Direct input from form (transient)
secretKey = config.SourceSecretKey
} else if encryptedSecret, ok := sourceCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt source secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := sourceCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "secret_access_key", secretKey)
}
// Add region if specified
if getStringValue(sourceCredentials, "region", config.SourceRegion) != "" {
args = append(args, "region", getStringValue(sourceCredentials, "region", config.SourceRegion))
@@ -413,19 +507,89 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create destination config (sftp): %v\nOutput: %s", err, output)
}
case "smb":
args := []string{
"config", "create", destName, "smb",
"host", getStringValue(destCredentials, "host", config.DestHost),
"user", getStringValue(destCredentials, "username", config.DestUser),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Get domain if provided
domain := getStringValue(destCredentials, "domain", config.DestDomain)
if domain != "" {
args = append(args, "domain", domain)
}
// Get port if specified (default is 445)
port := getIntValue(destCredentials, "port", config.DestPort)
if port > 0 && port != 445 {
args = append(args, "port", fmt.Sprintf("%d", port))
}
// Get share if provided
share := getStringValue(destCredentials, "share", config.DestShare)
if share != "" {
args = append(args, "share", share)
}
// Handle password
password := ""
if config.DestPassword != "" {
password = config.DestPassword
} else if encryptedPwd, ok := destCredentials["encrypted_password"].(string); ok && encryptedPwd != "" {
decryptedPwd, err := db.DecryptCredential(encryptedPwd)
if err != nil {
return fmt.Errorf("failed to decrypt destination password: %v", err)
}
password = decryptedPwd
} else if pwVal, ok := destCredentials["password"].(string); ok && pwVal != "" {
password = pwVal
}
if password != "" {
args = append(args, "pass", password)
}
cmd := exec.Command(rclonePath, args...)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create destination config (smb): %v\nOutput: %s", err, output)
}
case "s3":
args := []string{
"config", "create", destName, "s3",
"provider", "AWS", // Assuming AWS provider
"env_auth", "false",
"access_key_id", getStringValue(destCredentials, "access_key", config.DestAccessKey),
"secret_access_key", getStringOrDefault(destCredentials, "secret_key", config.DestSecretKey),
"region", getStringValue(destCredentials, "region", config.DestRegion),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.DestSecretKey != "" {
// Direct input from form (transient)
secretKey = config.DestSecretKey
} else if encryptedSecret, ok := destCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt destination secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := destCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "secret_access_key", secretKey)
}
endpoint := getStringValue(destCredentials, "endpoint", config.DestEndpoint)
if endpoint != "" {
args = append(args, "endpoint", endpoint)
@@ -441,13 +605,33 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
"provider", "Wasabi",
"env_auth", "false",
"access_key_id", getStringValue(destCredentials, "access_key", config.DestAccessKey),
"secret_access_key", getStringOrDefault(destCredentials, "secret_key", config.DestSecretKey),
"region", getStringValue(destCredentials, "region", config.DestRegion),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.DestSecretKey != "" {
// Direct input from form (transient)
secretKey = config.DestSecretKey
} else if encryptedSecret, ok := destCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt destination secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := destCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "secret_access_key", secretKey)
}
endpoint := getStringValue(destCredentials, "endpoint", config.DestEndpoint)
if endpoint == "" {
endpoint = "s3.wasabisys.com"
@@ -472,6 +656,27 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
args = append(args, "account", accountID)
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.DestSecretKey != "" {
// Direct input from form (transient)
secretKey = config.DestSecretKey
} else if encryptedSecret, ok := destCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt destination secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := destCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "key", secretKey)
}
// Handle application key (secret key) with proper decryption if from provider
appKey := ""
if config.DestSecretKey != "" {
@@ -508,12 +713,33 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
"provider", "Minio",
"env_auth", "false",
"access_key_id", getStringValue(destCredentials, "access_key", config.DestAccessKey),
"secret_access_key", getStringOrDefault(destCredentials, "secret_key", config.DestSecretKey),
"endpoint", getStringValue(destCredentials, "endpoint", config.DestEndpoint),
"--non-interactive",
"--config", configPath,
"--log-level", "ERROR",
}
// Handle secret key with proper decryption if from provider
secretKey := ""
if config.DestSecretKey != "" {
// Direct input from form (transient)
secretKey = config.DestSecretKey
} else if encryptedSecret, ok := destCredentials["encrypted_secret_key"].(string); ok && encryptedSecret != "" {
// Provider reference with encrypted secret
decryptedSecret, err := db.DecryptCredential(encryptedSecret)
if err != nil {
return fmt.Errorf("failed to decrypt destination secret key: %v", err)
}
secretKey = decryptedSecret
} else if secretVal, ok := destCredentials["secret_key"].(string); ok && secretVal != "" {
// Backward compatibility
secretKey = secretVal
}
if secretKey != "" {
args = append(args, "secret_access_key", secretKey)
}
// Add region if specified
if getStringValue(destCredentials, "region", config.DestRegion) != "" {
args = append(args, "region", getStringValue(destCredentials, "region", config.DestRegion))