fix(t86 🔴): 換知識庫實例時清空看守資料夾——堵個資外洩

leo 07-28 實測坐實:youlin 時代的資料夾被原封同步進 geek6688 新實例
(「假如我把個人的同步到官方去就很危險,個資外泄」)。
修:instanceChanged(old,new) 比 host;連線精靈成功後若換了實例→清空
WatchFolders/WatchFolder,成功對話框改為說明「因為換了知識庫,為避免把舊資料夾
誤傳到新知識庫,請重新用『+新增知識資料夾…』選擇」;同實例改密碼行為照舊。
測試(main_test.go):host 異同判定含大小寫/尾斜線/scheme 差異。tray go test 綠。
(實作=子 CC;審查+commit=總管。⚠️ manifest 沿用=t86b 另案未含)
This commit is contained in:
2026-07-28 12:03:20 +08:00
parent fb63d9f0c4
commit 0deef3dbd9
2 changed files with 128 additions and 4 deletions
+37 -4
View File
@@ -292,8 +292,35 @@ func fetchConfigByLogin(portalURL, email, password string) (*daemonConfigResp, e
return &out, nil
}
// applyRemoteConfig 把換到的設定寫進本地 config(保留既有看守資料夾)
func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) {
// instanceChanged 比對兩個 cypher_url 的 hosthost 不同代表換了知識庫實例
// 只比 host 不比 scheme/path——同實例改密碼時 URL 完全不變,不觸發資料夾清空。
// 兩端有任一為空(首次設定或異常值)時回 false,不誤觸清空。
//
// t86leo 2026-07-28 實測:youlin 時代的資料夾被同步進 geek6688 新實例—個資外洩)。
func instanceChanged(oldURL, newURL string) bool {
parseHost := func(s string) string {
u, err := neturl.Parse(strings.TrimSpace(s))
if err != nil || u.Host == "" {
return strings.TrimSpace(s)
}
return u.Host
}
oldHost := parseHost(oldURL)
newHost := parseHost(newURL)
return oldHost != "" && newHost != "" && oldHost != newHost
}
// applyRemoteConfig 把換到的設定寫進本地 config。
// 回傳 true 代表換了知識庫實例(host 不同):此時已清空 WatchFolders/WatchFolder
// 呼叫端應提示用戶重新選擇資料夾,避免舊資料夾誤傳到新知識庫(t86)。
func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) bool {
// t86:換了實例(host 改變)就清空資料夾清單——在寫入新 URL 之前比對,
// 確保比的是「舊 host vs 新 host」而非「新 vs 新」。
switched := instanceChanged(cfg.CypherURL, r.Config.CypherURL)
if switched {
cfg.WatchFolders = nil
cfg.WatchFolder = ""
}
cfg.CypherURL = r.Config.CypherURL
cfg.Namespace = r.Config.Namespace
if r.Config.Library != "" {
@@ -309,6 +336,7 @@ func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) {
if cfg.Manifest == "" {
cfg.Manifest = filepath.Join(appDir(), "manifest.json")
}
return switched
}
// registerLibraries 把「這台機器看守的資料夾各自對應的庫」報上雲端自動登記(t52)。
@@ -472,7 +500,7 @@ func main() {
showConnectWizardWith(urlEntry.Text, emailEntry.Text)
return
}
applyRemoteConfig(cfg, r)
switched := applyRemoteConfig(cfg, r)
if err := saveConfig(cfg); err != nil {
dialog.ShowError(err, win)
return
@@ -483,7 +511,12 @@ func main() {
}
restartWatch()
rebuildTray()
dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win)
// t86:換了知識庫實例時,說明已清空資料夾清單的原因,請用戶重新選資料夾。
if switched {
dialog.ShowInformation("連上了", "因為換了知識庫,為了避免把舊資料夾誤傳到新知識庫,請重新用「+ 新增知識資料夾…」選擇要同步的資料夾。", win)
} else {
dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win)
}
}()
}, win)
d.Resize(fyne.NewSize(460, 320))
+91
View File
@@ -92,3 +92,94 @@ func TestDirectConfigJSONOmitsEmptyInstanceName(t *testing.T) {
t.Errorf("暱稱空時不該序列化出 instance_name 欄:%s", data)
}
}
// ── t86instanceChanged ─────────────────────────────────────────────────────
func TestInstanceChanged(t *testing.T) {
cases := []struct {
name string
oldURL string
newURL string
want bool
}{
{
"同 host 同實例改密碼不觸發清空",
"https://arcrun-cypher-executor.youlin.workers.dev",
"https://arcrun-cypher-executor.youlin.workers.dev",
false,
},
{
"不同 host 換實例觸發清空",
"https://arcrun-cypher-executor.youlin.workers.dev",
"https://arcrun-cypher-executor.geek6688.workers.dev",
true,
},
{
"舊 URL 空(首次設定)不觸發清空",
"",
"https://arcrun-cypher-executor.geek6688.workers.dev",
false,
},
{
"新 URL 空(異常值)不觸發清空",
"https://arcrun-cypher-executor.youlin.workers.dev",
"",
false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := instanceChanged(c.oldURL, c.newURL)
if got != c.want {
t.Errorf("instanceChanged(%q, %q) = %v, want %v", c.oldURL, c.newURL, got, c.want)
}
})
}
}
// t86:換實例後 applyRemoteConfig 清空資料夾清單
func TestApplyRemoteConfigClearsFoldersOnInstanceSwitch(t *testing.T) {
cfg := &directConfig{
CypherURL: "https://arcrun-cypher-executor.youlin.workers.dev",
Namespace: "youlin",
WatchFolder: "/Users/youlin/KnowledgeBase",
WatchFolders: []string{"/Users/youlin/KnowledgeBase", "/Users/youlin/Finance"},
}
r := &daemonConfigResp{}
r.Config.CypherURL = "https://arcrun-cypher-executor.geek6688.workers.dev"
r.Config.Namespace = "geek6688"
switched := applyRemoteConfig(cfg, r)
if !switched {
t.Error("換了不同 host 應回傳 switched=true")
}
if cfg.WatchFolder != "" {
t.Errorf("換實例後 WatchFolder 應清空,got %q", cfg.WatchFolder)
}
if len(cfg.WatchFolders) != 0 {
t.Errorf("換實例後 WatchFolders 應清空,got %v", cfg.WatchFolders)
}
}
// t86:同實例改密碼不清空資料夾清單
func TestApplyRemoteConfigKeepsFoldersOnSameInstance(t *testing.T) {
cfg := &directConfig{
CypherURL: "https://arcrun-cypher-executor.youlin.workers.dev",
Namespace: "youlin",
WatchFolder: "/Users/youlin/KnowledgeBase",
WatchFolders: []string{"/Users/youlin/KnowledgeBase"},
}
r := &daemonConfigResp{}
r.Config.CypherURL = "https://arcrun-cypher-executor.youlin.workers.dev"
r.Config.Namespace = "youlin"
switched := applyRemoteConfig(cfg, r)
if switched {
t.Error("同 host 不應回傳 switched=true")
}
if cfg.WatchFolder == "" || len(cfg.WatchFolders) == 0 {
t.Error("同實例不應清空資料夾清單")
}
}