Bootstrap remote opens from local cache vaults

This commit is contained in:
Joe Julian
2026-04-06 16:12:53 -07:00
parent b27e2f168c
commit db74a0a198
2 changed files with 258 additions and 35 deletions
+172 -7
View File
@@ -1725,6 +1725,83 @@ func TestUIOpenRemoteActionUsesSelectedVaultBinding(t *testing.T) {
}
}
func TestUIOpenRemoteActionBootstrapsFromLocalVaultBinding(t *testing.T) {
t.Parallel()
key := vault.MasterKey{Password: "correct horse battery staple"}
localPath := filepath.Join(t.TempDir(), "family.kdbx")
remoteModel := vault.Model{
Entries: []vault.Entry{{
ID: "entry-1",
Title: "Vault Console",
Username: "dannyocean",
Password: "remote-token",
Path: []string{"Root", "Internet"},
}},
}
var remoteBytes bytes.Buffer
if err := vault.SaveKDBXWithKey(&remoteBytes, remoteModel, key); err != nil {
t.Fatalf("SaveKDBXWithKey(remote) error = %v", err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "tjulian" || password != "token-1" {
t.Fatalf("BasicAuth() = (%q, %q, %t), want (tjulian, token-1, true)", username, password, ok)
}
if r.Method != http.MethodGet {
t.Fatalf("method = %s, want GET", r.Method)
}
w.Header().Set("ETag", "\"v1\"")
_, _ = w.Write(remoteBytes.Bytes())
}))
defer server.Close()
localModel := vault.Model{}
if _, err := appstate.ConfigureRemoteBinding(&localModel, appstate.RemoteBindingInput{
LocalVaultPath: localPath,
RemoteProfileID: "family-webdav",
RemoteProfileName: "family.kdbx · dav.example.invalid",
BaseURL: server.URL,
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
CredentialTitle: "WebDAV Sign-In · tjulian",
Username: "tjulian",
Password: "token-1",
CredentialPath: []string{"Crew", "Internet"},
SyncMode: appstate.SyncModeAutomaticOnOpenSave,
}); err != nil {
t.Fatalf("ConfigureRemoteBinding(localModel) error = %v", err)
}
writeKDBXMainTestFile(t, localPath, localModel, key)
u := newUIWithSession("desktop", &session.Manager{})
u.masterPassword.SetText(key.Password)
u.applyRecentRemoteRecord(recentRemoteRecord{
BaseURL: server.URL,
Path: "files/family/keepass.kdbx",
LocalVaultPath: localPath,
RemoteProfileID: "family-webdav",
CredentialEntryID: "remote-creds-1",
})
if err := u.openRemoteAction(); err != nil {
t.Fatalf("openRemoteAction() error = %v", err)
}
if got := u.vaultPath.Text(); got != localPath {
t.Fatalf("vaultPath = %q, want %q", got, localPath)
}
current, err := u.state.Session.Current()
if err != nil {
t.Fatalf("Session.Current() error = %v", err)
}
if got := current.EntriesInPath([]string{"Root", "Internet"}); len(got) != 1 || got[0].Title != "Vault Console" {
t.Fatalf("EntriesInPath(Root/Internet) = %#v, want Vault Console", got)
}
}
func TestUIStartOpenRemoteActionUsesSelectedVaultBinding(t *testing.T) {
t.Parallel()
@@ -1798,6 +1875,92 @@ func TestUIStartOpenRemoteActionUsesSelectedVaultBinding(t *testing.T) {
}
}
func TestUIStartOpenRemoteActionBootstrapsFromLocalVaultBinding(t *testing.T) {
t.Parallel()
key := vault.MasterKey{Password: "correct horse battery staple"}
localPath := filepath.Join(t.TempDir(), "family.kdbx")
remoteModel := vault.Model{
Entries: []vault.Entry{{
ID: "entry-1",
Title: "Vault Console",
Username: "dannyocean",
Password: "remote-token",
Path: []string{"Root", "Internet"},
}},
}
var remoteBytes bytes.Buffer
if err := vault.SaveKDBXWithKey(&remoteBytes, remoteModel, key); err != nil {
t.Fatalf("SaveKDBXWithKey(remote) error = %v", err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "tjulian" || password != "token-1" {
t.Fatalf("BasicAuth() = (%q, %q, %t), want (tjulian, token-1, true)", username, password, ok)
}
if r.Method != http.MethodGet {
t.Fatalf("method = %s, want GET", r.Method)
}
w.Header().Set("ETag", "\"v1\"")
_, _ = w.Write(remoteBytes.Bytes())
}))
defer server.Close()
localModel := vault.Model{}
if _, err := appstate.ConfigureRemoteBinding(&localModel, appstate.RemoteBindingInput{
LocalVaultPath: localPath,
RemoteProfileID: "family-webdav",
RemoteProfileName: "family.kdbx · dav.example.invalid",
BaseURL: server.URL,
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
CredentialTitle: "WebDAV Sign-In · tjulian",
Username: "tjulian",
Password: "token-1",
CredentialPath: []string{"Crew", "Internet"},
SyncMode: appstate.SyncModeAutomaticOnOpenSave,
}); err != nil {
t.Fatalf("ConfigureRemoteBinding(localModel) error = %v", err)
}
writeKDBXMainTestFile(t, localPath, localModel, key)
manager := &session.Manager{}
u := newUIWithSession("desktop", manager)
u.masterPassword.SetText(key.Password)
u.applyRecentRemoteRecord(recentRemoteRecord{
BaseURL: server.URL,
Path: "files/family/keepass.kdbx",
LocalVaultPath: localPath,
RemoteProfileID: "family-webdav",
CredentialEntryID: "remote-creds-1",
})
u.startOpenRemoteAction()
if got := u.loadingMessage; got != "Open remote vault..." {
t.Fatalf("loadingMessage after start = %q, want %q", got, "Open remote vault...")
}
result := waitForBackgroundResult(t, u)
u.applyBackgroundResult(result)
if got := u.state.ErrorMessage; got != "" {
t.Fatalf("ErrorMessage after apply = %q, want empty", got)
}
if got := u.vaultPath.Text(); got != localPath {
t.Fatalf("vaultPath = %q, want %q", got, localPath)
}
current, err := u.state.Session.Current()
if err != nil {
t.Fatalf("Session.Current() error = %v", err)
}
if got := current.EntriesInPath([]string{"Root", "Internet"}); len(got) != 1 || got[0].Title != "Vault Console" {
t.Fatalf("EntriesInPath(Root/Internet) = %#v, want Vault Console", got)
}
}
func TestUIRemoteSaveConflictShowsVisibleErrorAndKeepsDirtyState(t *testing.T) {
t.Parallel()
@@ -5134,9 +5297,14 @@ func TestUIOpenRemoteVaultRestoresLastOpenedGroupForThatConnection(t *testing.T)
}))
defer server.Close()
first := newUIWithSession("desktop", &session.Manager{})
first.recentRemotesPath = statePath
first.recentRemotes = nil
paths := statePaths{
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
RecentRemotesPath: statePath,
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
}
first := newUIWithState("desktop", &session.Manager{}, paths)
first.lifecycleMode = "remote"
first.masterPassword.SetText("correct horse battery staple")
first.remoteBaseURL.SetText(server.URL)
@@ -5149,10 +5317,7 @@ func TestUIOpenRemoteVaultRestoresLastOpenedGroupForThatConnection(t *testing.T)
first.syncedPath = []string{"Root", "Internet"}
first.noteCurrentRemotePath()
reopened := newUIWithSession("desktop", &session.Manager{})
reopened.recentRemotesPath = statePath
reopened.recentRemotes = nil
reopened.loadRecentRemotes()
reopened := newUIWithState("desktop", &session.Manager{}, paths)
reopened.lifecycleMode = "remote"
reopened.masterPassword.SetText("correct horse battery staple")
reopened.remoteBaseURL.SetText(server.URL)