From 7ed9203e4babc74d53bec8d6e1c741b6ed2d01df Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 6 Nov 2025 17:53:54 +0000 Subject: [PATCH] Fix config backup/restore failures (related to #646) Addresses two issues preventing configuration backup/restore: 1. Export passphrase validation mismatch: UI only validated 12+ char requirement when using custom passphrase, but backend always enforced it. Users with shorter login passwords saw unexplained failures. - Frontend now validates all passphrases meet 12-char minimum - Clear error message suggests custom passphrase if login password too short 2. Import data parsing failed silently: Frontend sent `exportData.data` which was undefined for legacy/CLI backups (raw base64 strings). Backend rejected these with no logs. - Frontend now handles both formats: {status, data} and raw strings - Backend logs validation failures for easier troubleshooting Related to #646 where user reported "error after entering password" with no container logs. These changes ensure proper validation feedback and make the backup system resilient to different export formats. --- .../src/components/Settings/Settings.tsx | 29 +++++++++++++++---- internal/api/config_handlers.go | 4 +++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 48a9efe..b5e2438 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -1902,10 +1902,14 @@ const Settings: Component = (props) => { return; } - // Just require a passphrase to be entered - const hasAuth = securityStatus()?.hasAuthentication; - if ((!hasAuth || useCustomPassphrase()) && !exportPassphrase()) { - showError('Please enter a passphrase'); + // Backend requires at least 12 characters for encryption security + if (exportPassphrase().length < 12) { + const hasAuth = securityStatus()?.hasAuthentication; + showError( + hasAuth && !useCustomPassphrase() + ? 'Your password must be at least 12 characters. Please use a custom passphrase instead.' + : 'Passphrase must be at least 12 characters long', + ); return; } @@ -2029,6 +2033,21 @@ const Settings: Component = (props) => { return; } + // Support both formats: + // 1. New format: {status: "success", data: "base64string"} + // 2. Legacy/CLI format: raw base64 string or {data: "base64string"} + let encryptedData: string; + if (typeof exportData === 'string') { + // Raw base64 string from CLI export + encryptedData = exportData; + } else if (exportData.data) { + // Standard format with data field + encryptedData = exportData.data; + } else { + showError('Invalid backup file format. Expected encrypted data in "data" field.'); + return; + } + // Get CSRF token from cookie const csrfToken = document.cookie .split('; ') @@ -2056,7 +2075,7 @@ const Settings: Component = (props) => { credentials: 'include', // Include cookies for session auth body: JSON.stringify({ passphrase: importPassphrase(), - data: exportData.data, + data: encryptedData, }), }); diff --git a/internal/api/config_handlers.go b/internal/api/config_handlers.go index 2c94434..08d13ce 100644 --- a/internal/api/config_handlers.go +++ b/internal/api/config_handlers.go @@ -2910,12 +2910,14 @@ func (h *ConfigHandlers) HandleExportConfig(w http.ResponseWriter, r *http.Reque } if req.Passphrase == "" { + log.Warn().Msg("Export rejected: passphrase is required") http.Error(w, "Passphrase is required", http.StatusBadRequest) return } // Require strong passphrase (at least 12 characters) if len(req.Passphrase) < 12 { + log.Warn().Int("length", len(req.Passphrase)).Msg("Export rejected: passphrase too short (minimum 12 characters)") http.Error(w, "Passphrase must be at least 12 characters long", http.StatusBadRequest) return } @@ -2947,11 +2949,13 @@ func (h *ConfigHandlers) HandleImportConfig(w http.ResponseWriter, r *http.Reque } if req.Passphrase == "" { + log.Warn().Msg("Import rejected: passphrase is required") http.Error(w, "Passphrase is required", http.StatusBadRequest) return } if req.Data == "" { + log.Warn().Msg("Import rejected: encrypted data is required (ensure backup file has 'data' field)") http.Error(w, "Import data is required", http.StatusBadRequest) return }