From fde0660f753fab4e677902cf874f8fe54bbc1997 Mon Sep 17 00:00:00 2001 From: lan-yonghui Date: Sun, 8 Nov 2026 14:24:16 +0800 Subject: [PATCH] fix: verify backup and HTTPS proxy certificates with custom CA support --- agent/utils/cloud_storage/client/ali.go | 43 +++----------- agent/utils/cloud_storage/client/minio.go | 14 ++--- agent/utils/cloud_storage/client/s3.go | 14 ++--- agent/utils/cloud_storage/client/tls.go | 57 +++++++++++++++++++ agent/utils/cloud_storage/client/webdav.go | 12 +--- core/utils/cloud_storage/refresh_token.go | 9 +-- core/utils/ssh/http.go | 11 +++- frontend/src/lang/modules/en.ts | 3 + frontend/src/lang/modules/es-es.ts | 3 + frontend/src/lang/modules/fa.ts | 3 + frontend/src/lang/modules/ja.ts | 3 + frontend/src/lang/modules/ko.ts | 3 + frontend/src/lang/modules/lo.ts | 3 + frontend/src/lang/modules/ms.ts | 3 + frontend/src/lang/modules/pt-br.ts | 3 + frontend/src/lang/modules/ru.ts | 3 + frontend/src/lang/modules/tr.ts | 3 + frontend/src/lang/modules/zh-Hant.ts | 2 + frontend/src/lang/modules/zh.ts | 2 + .../setting/backup-account/operate/index.vue | 28 ++++++++- 20 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 agent/utils/cloud_storage/client/tls.go diff --git a/agent/utils/cloud_storage/client/ali.go b/agent/utils/cloud_storage/client/ali.go index 1cd5d8c8d..e9ed24675 100644 --- a/agent/utils/cloud_storage/client/ali.go +++ b/agent/utils/cloud_storage/client/ali.go @@ -2,7 +2,6 @@ package client import ( "context" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -11,8 +10,6 @@ import ( "os" "path" "strings" - - "github.com/go-resty/resty/v2" ) type aliClient struct { @@ -58,10 +55,7 @@ func (a aliClient) Delete(pathItem string) (bool, error) { if err != nil { return false, err } - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "drive_id": a.driveID, "file_id": fileInfo.FileID, @@ -108,10 +102,7 @@ func (a aliClient) Upload(ctx context.Context, src, target string) (bool, error) "size": fileInfo.Size(), "check_name_mode": "auto_rename", } - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() url := "https://api.alipan.com/v2/file/create" resp, err := client.R(). @@ -146,10 +137,7 @@ func (a aliClient) Download(src, target string) (bool, error) { if err != nil { return false, err } - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() if fileInfo.Size > 100*1024*1024 { return false, fmt.Errorf("The translation file %s exceeds 100MB, please download it through the client.", src) } @@ -266,10 +254,7 @@ func (a aliClient) loadDirWithPath(path string) ([]fileInfo, error) { } func (a aliClient) loadFileWithParentID(parentID string) ([]fileInfo, error) { - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "drive_id": a.driveID, "fields": "*", @@ -338,10 +323,7 @@ func (a aliClient) mkdirWithPath(target string) (string, error) { } func (a aliClient) mkdir(parentID, name string) (string, error) { - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "drive_id": a.driveID, "name": name, @@ -472,10 +454,7 @@ func (a aliClient) handleDownload(uri string, target string) error { } func (a *aliClient) completeUpload(ctx context.Context, uploadID, fileID string) error { - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "drive_id": a.driveID, "upload_id": uploadID, @@ -504,10 +483,7 @@ type tokenResp struct { } func loadToken(refresh_token string) (string, error) { - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "grant_type": "refresh_token", "refresh_token": refresh_token, @@ -536,10 +512,7 @@ func RefreshALIToken(varMap map[string]interface{}) (string, error) { if len(refresh_token) == 0 { return "", errors.New("no such refresh token find in db") } - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALIHTTPClient() data := map[string]interface{}{ "grant_type": "refresh_token", "refresh_token": refresh_token, diff --git a/agent/utils/cloud_storage/client/minio.go b/agent/utils/cloud_storage/client/minio.go index d184b7ac9..845166aa9 100644 --- a/agent/utils/cloud_storage/client/minio.go +++ b/agent/utils/cloud_storage/client/minio.go @@ -2,10 +2,8 @@ package client import ( "context" - "crypto/tls" "fmt" "io" - "net/http" "os" "strings" @@ -29,14 +27,10 @@ func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) { return nil, buserr.WithDetail("ErrInvalidParams", "ssl", fmt.Errorf("no such proto in ssl: %s", ssl)) } - secure := false - tlsConfig := &tls.Config{} - if ssl == "https" { - secure = true - tlsConfig.InsecureSkipVerify = true - } - var transport http.RoundTripper = &http.Transport{ - TLSClientConfig: tlsConfig, + secure := ssl == "https" + transport, err := newStorageHTTPTransport(vars) + if err != nil { + return nil, err } client, err := minio.New(strings.ReplaceAll(endpoint, ssl+"://", ""), &minio.Options{ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), diff --git a/agent/utils/cloud_storage/client/s3.go b/agent/utils/cloud_storage/client/s3.go index 908d16993..9609ff403 100644 --- a/agent/utils/cloud_storage/client/s3.go +++ b/agent/utils/cloud_storage/client/s3.go @@ -2,8 +2,6 @@ package client import ( "context" - "crypto/tls" - "net/http" "os" "strings" "time" @@ -48,14 +46,10 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) { } ssl := strings.Split(endpoint, ":")[0] - secure := false - tlsConfig := &tls.Config{} - if ssl == "https" { - secure = true - tlsConfig.InsecureSkipVerify = true - } - var transport http.RoundTripper = &http.Transport{ - TLSClientConfig: tlsConfig, + secure := ssl == "https" + transport, err := newStorageHTTPTransport(vars) + if err != nil { + return nil, err } endpoint = strings.TrimPrefix(endpoint, ssl+"://") diff --git a/agent/utils/cloud_storage/client/tls.go b/agent/utils/cloud_storage/client/tls.go new file mode 100644 index 000000000..733fc0bed --- /dev/null +++ b/agent/utils/cloud_storage/client/tls.go @@ -0,0 +1,57 @@ +package client + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "net/http" + + "github.com/go-resty/resty/v2" +) + +func newStorageHTTPTransport(vars map[string]interface{}) (*http.Transport, error) { + config := &tls.Config{} + value, exists := vars["caCertPEM"] + if !exists || value == nil { + return &http.Transport{TLSClientConfig: config}, nil + } + certificatePEM, ok := value.(string) + if !ok { + return nil, errors.New("caCertPEM must be a PEM certificate string") + } + rest := bytes.TrimSpace([]byte(certificatePEM)) + if len(rest) == 0 { + return &http.Transport{TLSClientConfig: config}, nil + } + roots, err := x509.SystemCertPool() + if err != nil { + roots = x509.NewCertPool() + } + for len(rest) > 0 { + if !bytes.HasPrefix(rest, []byte("-----BEGIN CERTIFICATE-----")) { + return nil, errors.New("caCertPEM must contain only PEM certificates") + } + block, remaining := pem.Decode(rest) + if block == nil || block.Type != "CERTIFICATE" || len(block.Headers) != 0 { + return nil, errors.New("caCertPEM contains an invalid PEM certificate") + } + if bytes.Count(rest[:len(rest)-len(remaining)], []byte("-----BEGIN CERTIFICATE-----")) != 1 { + return nil, errors.New("caCertPEM contains an invalid PEM certificate") + } + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("caCertPEM contains an invalid certificate: %w", err) + } + roots.AddCert(certificate) + rest = bytes.TrimSpace(remaining) + } + config.RootCAs = roots + return &http.Transport{TLSClientConfig: config}, nil +} + +func newALIHTTPClient() *resty.Client { + return resty.New().SetTLSClientConfig(&tls.Config{}) +} diff --git a/agent/utils/cloud_storage/client/webdav.go b/agent/utils/cloud_storage/client/webdav.go index e4756db04..86d58a3ba 100644 --- a/agent/utils/cloud_storage/client/webdav.go +++ b/agent/utils/cloud_storage/client/webdav.go @@ -2,13 +2,10 @@ package client import ( "context" - "crypto/tls" "fmt" "io" - "net/http" "os" "path" - "strings" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client/helper/webdav" @@ -39,12 +36,9 @@ func newWebDAVClient(ctx context.Context, vars map[string]interface{}) (*webDAVC url = address } client := webdav.NewClient(url, username, password) - tlsConfig := &tls.Config{} - if strings.HasPrefix(address, "https") { - tlsConfig.InsecureSkipVerify = true - } - var transport http.RoundTripper = &http.Transport{ - TLSClientConfig: tlsConfig, + transport, err := newStorageHTTPTransport(vars) + if err != nil { + return nil, err } client.SetTransport(transport) if err := client.ConnectWithContext(ctx); err != nil { diff --git a/core/utils/cloud_storage/refresh_token.go b/core/utils/cloud_storage/refresh_token.go index 2d1293eb0..5b13da8c0 100644 --- a/core/utils/cloud_storage/refresh_token.go +++ b/core/utils/cloud_storage/refresh_token.go @@ -35,10 +35,7 @@ func RefreshALIToken(varMap map[string]interface{}) (string, error) { if len(refresh_token) == 0 { return "", errors.New("no such refresh token find in db") } - client := resty.New() - client.SetTLSClientConfig(&tls.Config{ - InsecureSkipVerify: true, - }) + client := newALITokenHTTPClient() data := map[string]interface{}{ "grant_type": "refresh_token", "refresh_token": refresh_token, @@ -62,6 +59,10 @@ func RefreshALIToken(varMap map[string]interface{}) (string, error) { return respItem.RefreshToken, nil } +func newALITokenHTTPClient() *resty.Client { + return resty.New().SetTLSClientConfig(&tls.Config{}) +} + func RefreshToken(grantType string, tokenType string, varMap map[string]interface{}) (string, error) { data := url.Values{} isCN := loadParamFromVars("isCN", varMap) diff --git a/core/utils/ssh/http.go b/core/utils/ssh/http.go index b64b66b67..d91083a31 100644 --- a/core/utils/ssh/http.go +++ b/core/utils/ssh/http.go @@ -3,6 +3,7 @@ package ssh import ( "bufio" "crypto/tls" + "crypto/x509" "encoding/base64" "fmt" "net" @@ -20,17 +21,25 @@ type HTTPProxyDialer struct { } func HTTPDial(dialer HTTPProxyDialer, network, addr string) (net.Conn, error) { + return dialHTTPProxy(dialer, network, addr, nil) +} + +func dialHTTPProxy(dialer HTTPProxyDialer, network, addr string, rootCAs *x509.CertPool) (net.Conn, error) { var conn net.Conn var err error global.LOG.Debugf("Dialing HTTP proxy %s for %s", dialer.URL, addr) dialer.URL = strings.TrimPrefix(dialer.URL, dialer.Type+"://") if dialer.Type == "https" { + host, _, splitErr := net.SplitHostPort(dialer.URL) + if splitErr != nil { + return nil, splitErr + } conn, err = tls.DialWithDialer( &net.Dialer{Timeout: 30 * time.Second}, network, dialer.URL, - &tls.Config{InsecureSkipVerify: true}, + &tls.Config{RootCAs: rootCAs, ServerName: host}, ) } else { conn, err = net.DialTimeout(network, dialer.URL, 30*time.Second) diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 933fa2715..9650a8d11 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -3870,6 +3870,9 @@ const message = { 'If a cloud provider is compatible with the S3 protocol, you can directly use Amazon S3 for backup. ', domain: 'Accelerate domain', backupAccount: 'Backup account | Backup accounts', + backupCACert: 'CA certificate (PEM)', + backupCACertHelp: + 'Leave empty to use system CAs. For self-hosted HTTPS storage, paste its CA certificate. Certificate verification remains enabled.', loadBucket: 'Get bucket', accountName: 'Account name', accountKey: 'Account key', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 2442caa0b..dd33bf6f3 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -3947,6 +3947,9 @@ const message = { 'Si un proveedor de nube es compatible con el protocolo S3, puedes usar Amazon S3 directamente para el respaldo. ', domain: 'Dominio de aceleración', backupAccount: 'Cuenta de respaldo | Cuentas de respaldo', + backupCACert: 'Certificado CA (PEM)', + backupCACertHelp: + 'Déjelo vacío para usar las CA del sistema. Para almacenamiento HTTPS propio, pegue su certificado CA. La verificación de certificados permanece activa.', loadBucket: 'Obtener bucket', accountName: 'Nombre de la cuenta', accountKey: 'Clave de la cuenta', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 9449db7b7..a7b8ad962 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -3830,6 +3830,9 @@ const message = { 'اگر یک ارائه‌دهنده ابری با پروتکل S3 سازگار است، می‌توانید مستقیماً از آمازون S3 برای پشتیبان‌گیری استفاده کنید.', domain: 'دامنه شتاب‌دهنده', backupAccount: 'حساب پشتیبان | حساب‌های پشتیبان', + backupCACert: 'گواهی CA (PEM)', + backupCACertHelp: + 'برای استفاده از CAهای سیستم خالی بگذارید. برای فضای ذخیره‌سازی HTTPS خودمیزبان، گواهی CA را وارد کنید. اعتبارسنجی گواهی همیشه فعال است.', loadBucket: 'دریافت سطل', accountName: 'نام حساب', accountKey: 'کلید حساب', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index b5c81204d..5fa1d3a49 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -3822,6 +3822,9 @@ const message = { '理論的には、クラウドプロバイダーがS3プロトコルに対応していれば、現行のAmazon S3クラウドストレージを使ってバックアップできます。具体的な設定については、', domain: 'ドメインを加速します', backupAccount: 'バックアップアカウント|バックアップアカウント', + backupCACert: 'CA 証明書(PEM)', + backupCACertHelp: + '空欄の場合はシステムの CA を使用します。自前の HTTPS ストレージには CA 証明書を貼り付けてください。証明書の検証は常に有効です。', loadBucket: 'バケツを入手してください', accountName: 'アカウント名', accountKey: 'アカウントキー', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index f910d6404..a55b0a5c5 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -3764,6 +3764,9 @@ const message = { backupAlert: 'S3 프로토콜을 지원하는 클라우드 공급자는 Amazon S3 를 사용하여 백업할 수 있습니다.', domain: '가속 도메인', backupAccount: '백업 계정 | 백업 계정', + backupCACert: 'CA 인증서(PEM)', + backupCACertHelp: + '비워 두면 시스템 CA를 사용합니다. 자체 호스팅 HTTPS 저장소의 CA 인증서를 붙여 넣으세요. 인증서 검증은 항상 활성화됩니다.', loadBucket: '버킷 가져오기', accountName: '계정 이름', accountKey: '계정 키', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 9b14d061e..d7dc700e3 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -3754,6 +3754,9 @@ const message = { backupAlert: 'ຫາກຜູ້ໃຫ້ບໍລິການຄລາວຮອງຮັບໂປຣໂຕຄໍ S3, ທ່ານສາມາດໃຊ້ Amazon S3 ໃນການສຳຮອງຂໍ້ມູນໄດ້ໂດຍກົງ.', domain: 'ໂດເມນເລັ່ງຄວາມໄວ', backupAccount: 'ບັນຊີສຳຮອງ', + backupCACert: 'ໃບຢັ້ງຢືນ CA (PEM)', + backupCACertHelp: + 'ປ່ອຍວ່າງເພື່ອໃຊ້ CA ຂອງລະບົບ. ສຳລັບບ່ອນເກັບຂໍ້ມູນ HTTPS ທີ່ໂຮສເອງ ໃຫ້ວາງໃບຢັ້ງຢືນ CA. ການກວດສອບໃບຢັ້ງຢືນຈະເປີດຢູ່ສະເໝີ.', loadBucket: 'ຮັບ Bucket', accountName: 'ຊື່ບັນຊີ', accountKey: 'Account key', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 1fed24eec..bcf8983ca 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -3913,6 +3913,9 @@ const message = { 'Jika penyedia awan serasi dengan protokol S3, anda boleh menggunakan Amazon S3 secara langsung untuk sandaran.', domain: 'Domain pemecutan', backupAccount: 'Akaun sandaran | Akaun sandaran', + backupCACert: 'Sijil CA (PEM)', + backupCACertHelp: + 'Biarkan kosong untuk menggunakan CA sistem. Untuk storan HTTPS yang dihos sendiri, tampal sijil CA. Pengesahan sijil sentiasa diaktifkan.', loadBucket: 'Dapatkan baldi', accountName: 'Nama akaun', accountKey: 'Kunci akaun', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 700a24267..9fcf3d4ed 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -3918,6 +3918,9 @@ const message = { 'Se um provedor de nuvem for compatível com o protocolo S3, você pode usar diretamente o Amazon S3 para backup.', domain: 'Domínio de aceleração', backupAccount: 'Conta de backup | Contas de backup', + backupCACert: 'Certificado de CA (PEM)', + backupCACertHelp: + 'Deixe em branco para usar as CAs do sistema. Para armazenamento HTTPS próprio, cole o certificado da CA. A verificação de certificados permanece ativa.', loadBucket: 'Obter bucket', accountName: 'Nome da conta', accountKey: 'Chave da conta', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index e4c94ff9c..b06c52ed2 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -3892,6 +3892,9 @@ const message = { 'Если облачный провайдер совместим с протоколом S3, вы можете напрямую использовать Amazon S3 для резервного копирования.', domain: 'Домен ускорения', backupAccount: 'Аккаунт резервного копирования | Аккаунты резервного копирования', + backupCACert: 'Сертификат CA (PEM)', + backupCACertHelp: + 'Оставьте поле пустым, чтобы использовать системные CA. Для собственного HTTPS-хранилища вставьте сертификат CA. Проверка сертификатов всегда включена.', loadBucket: 'Получить корзину', accountName: 'Имя аккаунта', accountKey: 'Ключ аккаунта', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index aee2e8f30..67808b95a 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -3918,6 +3918,9 @@ const message = { 'Bir bulut sağlayıcısı S3 protokolüyle uyumluysa, yedekleme için doğrudan Amazon S3 kullanılabilir.', domain: 'Hızlandırma alanı', backupAccount: 'Yedekleme hesabı | Yedekleme hesapları', + backupCACert: 'CA sertifikası (PEM)', + backupCACertHelp: + "Sistem CA'larını kullanmak için boş bırakın. Kendi HTTPS depolamanız için CA sertifikasını yapıştırın. Sertifika doğrulaması her zaman etkindir.", loadBucket: 'Kova al', accountName: 'Hesap adı', accountKey: 'Hesap anahtarı', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 6d8c015ef..4660f6490 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -3617,6 +3617,8 @@ const message = { backupAlert: '理論上只要雲廠商相容 S3 協議,就可以用現有的亞馬遜 S3 雲端儲存來備份,詳細設定請參考 ', domain: '加速域名', backupAccount: '備份帳號', + backupCACert: 'CA 憑證(PEM)', + backupCACertHelp: '留空時使用系統信任的 CA。自架 HTTPS 儲存服務可貼上 CA 憑證,憑證驗證始終開啟。', loadBucket: '取得桶', accountName: '帳戶名稱', accountKey: '帳戶金鑰', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index a3799aa0d..7f4f81645 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -3643,6 +3643,8 @@ const message = { backupAlert: '理论上只要云厂商兼容 S3 协议,就可以用现有的亚马逊 S3 云存储来备份,具体配置参考', domain: '加速域名', backupAccount: '备份账号', + backupCACert: 'CA 证书(PEM)', + backupCACertHelp: '留空时使用系统信任的 CA。自建 HTTPS 存储可粘贴其 CA 证书,证书校验始终开启。', loadBucket: '获取桶', accountName: '账户名称', accountKey: '账户密钥', diff --git a/frontend/src/views/setting/backup-account/operate/index.vue b/frontend/src/views/setting/backup-account/operate/index.vue index 032532803..a06d9f331 100644 --- a/frontend/src/views/setting/backup-account/operate/index.vue +++ b/frontend/src/views/setting/backup-account/operate/index.vue @@ -184,6 +184,15 @@ + + + {{ $t('setting.backupCACertHelp') }} + @@ -452,10 +461,27 @@ const dialogData = ref({ title: '', }); +const supportsCustomCA = computed(() => { + const row = dialogData.value.rowData; + if (!row) return false; + if (row.type === 'S3' || row.type === 'MINIO') return domainProto.value === 'https'; + return row.type === 'WebDAV' && /^https:\/\//i.test(String(row.varsJson?.['address'] || '').trim()); +}); + const formWatcher = computed(() => { const { type, isPublic, accessKey, bucket, credential, backupPath, bucketInput, varsJson } = dialogData.value.rowData || {}; - return { type, isPublic, accessKey, bucket, credential, backupPath, bucketInput, varsJson }; + return { + type, + isPublic, + accessKey, + bucket, + credential, + backupPath, + bucketInput, + varsJson, + protocol: domainProto.value, + }; }); const startWatcher = () => { if (stopWatch.value) {