From 12f2916a242caae8ccfecb0db063a71ccf43fe1b Mon Sep 17 00:00:00 2001 From: mirivlad Date: Tue, 2 Jun 2026 11:40:27 +0800 Subject: [PATCH] followup: SafeVaultPath in note update, email i18n, strict check-i18n.sh - applyRemoteNoteUpdate: use SafeVaultPath for vault mode, skip non-vault with log - Email subjects/bodies moved to Go i18n (confirm + reset) in ru.json and en.json - check-i18n.sh: ru/en key mismatch now FAIL (not WARNING) - All builds, tests, frontend pass --- cmd/verstak-gui/sync_apply.go | 37 +++++++++++++------------ cmd/verstak-server/handlers_user.go | 10 ++++--- cmd/verstak-server/handlers_web_user.go | 8 +++--- internal/i18n/locales/en.json | 7 ++++- internal/i18n/locales/ru.json | 7 ++++- scripts/check-i18n.sh | 12 +++++--- 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/cmd/verstak-gui/sync_apply.go b/cmd/verstak-gui/sync_apply.go index 58afe76..3cdccf2 100644 --- a/cmd/verstak-gui/sync_apply.go +++ b/cmd/verstak-gui/sync_apply.go @@ -269,25 +269,28 @@ func (a *App) applyRemoteNoteUpdate(op syncsvc.Op) error { return fmt.Errorf("note record not found: %w", err) } - var abs string if storageMode == "vault" { - abs = filepath.Join(a.vault, filePath) - } else { - abs = filePath + abs, err := syncsvc.SafeVaultPath(a.vault, filePath) + if err != nil { + return fmt.Errorf("unsafe vault path in note update: %w", err) + } + if err := os.WriteFile(abs, []byte(payload.Content), 0o640); err != nil { + return err + } + info, _ := os.Stat(abs) + size := int64(0) + if info != nil { + size = info.Size() + } + now := time.Now().UTC().Format(time.RFC3339) + _, e := a.db.Exec( + `UPDATE files SET size=?, updated_at=? WHERE path=? AND storage_mode=?`, + size, now, filePath, storageMode) + return e } - if err := os.WriteFile(abs, []byte(payload.Content), 0o640); err != nil { - return err - } - info, _ := os.Stat(abs) - size := int64(0) - if info != nil { - size = info.Size() - } - now := time.Now().UTC().Format(time.RFC3339) - _, e := a.db.Exec( - `UPDATE files SET size=?, updated_at=? WHERE path=? AND storage_mode=?`, - size, now, filePath, storageMode) - return e + log.Printf("applyRemoteNoteUpdate: skipping non-vault note update for node %s (mode=%s, path=%s)", + payload.NodeID, storageMode, filePath) + return nil } func (a *App) applyRemoteFileOrFolderOp(op syncsvc.Op) error { diff --git a/cmd/verstak-server/handlers_user.go b/cmd/verstak-server/handlers_user.go index 055c0c1..d6e2497 100644 --- a/cmd/verstak-server/handlers_user.go +++ b/cmd/verstak-server/handlers_user.go @@ -15,6 +15,8 @@ import ( "strings" "time" + "verstak/internal/i18n" + "golang.org/x/crypto/bcrypt" ) @@ -82,8 +84,8 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) { } else { confirmURL = fmt.Sprintf("/api/v1/auth/confirm?token=%s", tokenStr) } - body := fmt.Sprintf("Welcome to Verstak Sync!\n\nPlease confirm your email by clicking:\n%s\n\nIf you did not register, ignore this message.", confirmURL) - if err := s.smtpSend(req.Email, "Confirm your Verstak Sync account", body); err != nil { + body := fmt.Sprintf(i18n.T(s.locale(), "server.emailConfirmBody"), confirmURL) + if err := s.smtpSend(req.Email, i18n.T(s.locale(), "server.emailConfirmSubject"), body); err != nil { log.Printf("register: failed to send confirm email: %v", err) } } else { @@ -199,8 +201,8 @@ func (s *Server) handleForgot(w http.ResponseWriter, r *http.Request) { if srvURL != "" { resetURL = fmt.Sprintf("%s/api/v1/auth/reset?token=%s", srvURL, tokenStr) } - body := fmt.Sprintf("Reset your Verstak Sync password:\n\n%s\n\nThis link expires in 1 hour.", resetURL) - s.smtpSend(req.Email, "Verstak Sync password reset", body) + body := fmt.Sprintf(i18n.T(s.locale(), "server.emailResetBody"), resetURL) + s.smtpSend(req.Email, i18n.T(s.locale(), "server.emailResetSubject"), body) } jsonOK(w, map[string]string{"status": "if email exists, reset link sent"}) } diff --git a/cmd/verstak-server/handlers_web_user.go b/cmd/verstak-server/handlers_web_user.go index 61c5d98..d0cc8ca 100644 --- a/cmd/verstak-server/handlers_web_user.go +++ b/cmd/verstak-server/handlers_web_user.go @@ -98,8 +98,8 @@ func (s *Server) handleUserWebRegister(w http.ResponseWriter, r *http.Request) { } else { confirmURL = fmt.Sprintf("http://%s/api/v1/auth/confirm?token=%s", r.Host, tokenStr) } - body := fmt.Sprintf("Welcome to Verstak Sync!\n\nPlease confirm your email by clicking:\n%s\n\nIf you did not register, ignore this message.", confirmURL) - if err := s.smtpSend(email, "Confirm your Verstak Sync account", body); err != nil { + body := fmt.Sprintf(i18n.T(s.locale(), "server.emailConfirmBody"), confirmURL) + if err := s.smtpSend(email, i18n.T(s.locale(), "server.emailConfirmSubject"), body); err != nil { log.Printf("register web: failed to send confirm email: %v", err) } } else { @@ -153,8 +153,8 @@ func (s *Server) handleUserWebForgot(w http.ResponseWriter, r *http.Request) { if srvURL != "" { resetURL = fmt.Sprintf("%s/reset?token=%s", srvURL, tokenStr) } - body := fmt.Sprintf("Reset your Verstak Sync password:\n\n%s\n\nThis link expires in 1 hour.", resetURL) - if err := s.smtpSend(email, "Verstak Sync password reset", body); err != nil { + body := fmt.Sprintf(i18n.T(s.locale(), "server.emailResetBody"), resetURL) + if err := s.smtpSend(email, i18n.T(s.locale(), "server.emailResetSubject"), body); err != nil { log.Printf("forgot web: failed to send reset email: %v", err) } } else { diff --git a/internal/i18n/locales/en.json b/internal/i18n/locales/en.json index 5f0cb59..4511f89 100644 --- a/internal/i18n/locales/en.json +++ b/internal/i18n/locales/en.json @@ -97,5 +97,10 @@ "error.accountBlocked": "Account blocked", "error.emailNotConfirmed": "Email not confirmed", "error.tokenInvalid": "Invalid or expired token", - "error.tokenExpired": "Token expired" + "error.tokenExpired": "Token expired", + + "server.emailConfirmSubject": "Confirm your Verstak Sync account", + "server.emailConfirmBody": "Welcome to Verstak Sync!\n\nPlease confirm your email by clicking:\n%s\n\nIf you did not register, ignore this message.", + "server.emailResetSubject": "Verstak Sync password reset", + "server.emailResetBody": "Reset your Verstak Sync password:\n\n%s\n\nThis link expires in 1 hour." } diff --git a/internal/i18n/locales/ru.json b/internal/i18n/locales/ru.json index 296dc96..ad77cdb 100644 --- a/internal/i18n/locales/ru.json +++ b/internal/i18n/locales/ru.json @@ -379,5 +379,10 @@ "error.accountBlocked": "Аккаунт заблокирован", "error.emailNotConfirmed": "Email не подтверждён", "error.tokenInvalid": "Неверный или просроченный токен", - "error.tokenExpired": "Срок действия токена истёк" + "error.tokenExpired": "Срок действия токена истёк", + + "server.emailConfirmSubject": "Подтверждение аккаунта Verstak Sync", + "server.emailConfirmBody": "Добро пожаловать в Verstak Sync!\n\nПодтвердите email, перейдя по ссылке:\n%s\n\nЕсли вы не регистрировались, проигнорируйте это письмо.", + "server.emailResetSubject": "Сброс пароля Verstak Sync", + "server.emailResetBody": "Сброс пароля Verstak Sync:\n\n%s\n\nСсылка действительна 1 час." } diff --git a/scripts/check-i18n.sh b/scripts/check-i18n.sh index e8acec8..1a144eb 100755 --- a/scripts/check-i18n.sh +++ b/scripts/check-i18n.sh @@ -97,12 +97,14 @@ MISSING_EN=$(comm -23 <(echo "$RU_KEYS") <(echo "$EN_KEYS")) MISSING_RU=$(comm -23 <(echo "$EN_KEYS") <(echo "$RU_KEYS")) if [ -n "$MISSING_EN" ]; then - echo "WARNING: Keys in frontend ru.js but missing in en.js:" + echo "FAIL: Keys in frontend ru.js but missing in en.js:" echo "$MISSING_EN" + EXIT=1 fi if [ -n "$MISSING_RU" ]; then - echo "WARNING: Keys in frontend en.js but missing in ru.js:" + echo "FAIL: Keys in frontend en.js but missing in ru.js:" echo "$MISSING_RU" + EXIT=1 fi if [ -z "$MISSING_EN" ] && [ -z "$MISSING_RU" ]; then echo "OK: All frontend locale keys match between ru.js and en.js" @@ -116,12 +118,14 @@ GO_MISSING_EN=$(comm -23 <(echo "$GO_RU_KEYS") <(echo "$GO_EN_KEYS")) GO_MISSING_RU=$(comm -23 <(echo "$GO_EN_KEYS") <(echo "$GO_RU_KEYS")) if [ -n "$GO_MISSING_EN" ]; then - echo "WARNING: Keys in Go ru.json but missing in en.json:" + echo "FAIL: Keys in Go ru.json but missing in en.json:" echo "$GO_MISSING_EN" + EXIT=1 fi if [ -n "$GO_MISSING_RU" ]; then - echo "WARNING: Keys in Go en.json but missing in ru.json:" + echo "FAIL: Keys in Go en.json but missing in ru.json:" echo "$GO_MISSING_RU" + EXIT=1 fi if [ -z "$GO_MISSING_EN" ] && [ -z "$GO_MISSING_RU" ]; then echo "OK: All Go locale keys match between ru.json and en.json"