Add UI master key setup and change flows
This commit is contained in:
+253
@@ -154,6 +154,258 @@ func TestUILifecycleActionsCreateSaveOpenLockAndUnlockLocalVault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIMasterKeyModesCreateOpenAndUnlockLocalVault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mode vault.MasterKeyMode
|
||||
password string
|
||||
keyFileData []byte
|
||||
}{
|
||||
{
|
||||
name: "password only",
|
||||
mode: vault.MasterKeyModePasswordOnly,
|
||||
password: "correct horse battery staple",
|
||||
},
|
||||
{
|
||||
name: "key file only",
|
||||
mode: vault.MasterKeyModeKeyFileOnly,
|
||||
keyFileData: []byte("key-file-only-material"),
|
||||
},
|
||||
{
|
||||
name: "composite",
|
||||
mode: vault.MasterKeyModePasswordAndKeyFile,
|
||||
password: "correct horse battery staple",
|
||||
keyFileData: []byte("composite-key-material"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keyFile := ""
|
||||
if len(tt.keyFileData) > 0 {
|
||||
keyFile = filepath.Join(t.TempDir(), "master.key")
|
||||
if err := os.WriteFile(keyFile, tt.keyFileData, 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(master.key) error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u.setMasterKeyMode(tt.mode)
|
||||
u.masterPassword.SetText(tt.password)
|
||||
u.keyFilePath.SetText(keyFile)
|
||||
|
||||
if err := u.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
}
|
||||
if err := u.state.UpsertEntry(vault.Entry{
|
||||
ID: "vault-console",
|
||||
Title: "Vault Console",
|
||||
Username: "dannyocean",
|
||||
Password: "token-1",
|
||||
URL: "https://vault.crew.example.invalid",
|
||||
Path: []string{"Root", "Internet"},
|
||||
}); err != nil {
|
||||
t.Fatalf("UpsertEntry() error = %v", err)
|
||||
}
|
||||
|
||||
path := filepath.Join(t.TempDir(), "keepassgo.kdbx")
|
||||
u.saveAsPath.SetText(path)
|
||||
if err := u.saveAsAction(); err != nil {
|
||||
t.Fatalf("saveAsAction() error = %v", err)
|
||||
}
|
||||
|
||||
if err := u.lockAction(); err != nil {
|
||||
t.Fatalf("lockAction() error = %v", err)
|
||||
}
|
||||
if err := u.unlockAction(); err != nil {
|
||||
t.Fatalf("unlockAction() error = %v", err)
|
||||
}
|
||||
|
||||
reopened := newUIWithSession("desktop", &session.Manager{})
|
||||
reopened.setMasterKeyMode(tt.mode)
|
||||
reopened.masterPassword.SetText(tt.password)
|
||||
reopened.keyFilePath.SetText(keyFile)
|
||||
reopened.vaultPath.SetText(path)
|
||||
if err := reopened.openVaultAction(); err != nil {
|
||||
t.Fatalf("openVaultAction() error = %v", err)
|
||||
}
|
||||
reopened.currentPath = []string{"Root", "Internet"}
|
||||
reopened.filter()
|
||||
if got := reopened.filteredTitles(); !slices.Equal(got, []string{"Vault Console"}) {
|
||||
t.Fatalf("reopened filteredTitles() = %v, want [Vault Console]", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIChangeMasterKeyModeForExistingVault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
updated := filepath.Join(t.TempDir(), "updated.key")
|
||||
if err := os.WriteFile(updated, []byte("updated-key"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(updated.key) error = %v", err)
|
||||
}
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordOnly)
|
||||
u.masterPassword.SetText("old-password")
|
||||
if err := u.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
}
|
||||
if err := u.state.UpsertEntry(vault.Entry{
|
||||
ID: "vault-console",
|
||||
Title: "Vault Console",
|
||||
Path: []string{"Root", "Internet"},
|
||||
}); err != nil {
|
||||
t.Fatalf("UpsertEntry() error = %v", err)
|
||||
}
|
||||
|
||||
path := filepath.Join(t.TempDir(), "keepassgo.kdbx")
|
||||
u.saveAsPath.SetText(path)
|
||||
if err := u.saveAsAction(); err != nil {
|
||||
t.Fatalf("saveAsAction() error = %v", err)
|
||||
}
|
||||
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordAndKeyFile)
|
||||
u.masterPassword.SetText("new-password")
|
||||
u.keyFilePath.SetText(updated)
|
||||
if err := u.changeMasterKeyAction(); err != nil {
|
||||
t.Fatalf("changeMasterKeyAction() error = %v", err)
|
||||
}
|
||||
if err := u.saveAction(); err != nil {
|
||||
t.Fatalf("saveAction() error = %v", err)
|
||||
}
|
||||
if err := u.lockAction(); err != nil {
|
||||
t.Fatalf("lockAction() error = %v", err)
|
||||
}
|
||||
|
||||
u.masterPassword.SetText("old-password")
|
||||
u.keyFilePath.SetText("")
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordOnly)
|
||||
u.runAction("unlock vault", u.unlockAction)
|
||||
if u.errorMessage == "" {
|
||||
t.Fatal("errorMessage = empty, want visible invalid master key error")
|
||||
}
|
||||
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordAndKeyFile)
|
||||
u.masterPassword.SetText("new-password")
|
||||
u.keyFilePath.SetText(updated)
|
||||
if err := u.unlockAction(); err != nil {
|
||||
t.Fatalf("unlockAction() with updated key error = %v", err)
|
||||
}
|
||||
|
||||
reopened := newUIWithSession("desktop", &session.Manager{})
|
||||
reopened.setMasterKeyMode(vault.MasterKeyModePasswordAndKeyFile)
|
||||
reopened.masterPassword.SetText("new-password")
|
||||
reopened.keyFilePath.SetText(updated)
|
||||
reopened.vaultPath.SetText(path)
|
||||
if err := reopened.openVaultAction(); err != nil {
|
||||
t.Fatalf("openVaultAction() with updated key error = %v", err)
|
||||
}
|
||||
reopened.currentPath = []string{"Root", "Internet"}
|
||||
reopened.filter()
|
||||
if got := reopened.filteredTitles(); !slices.Equal(got, []string{"Vault Console"}) {
|
||||
t.Fatalf("reopened filteredTitles() = %v, want [Vault Console]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIMasterKeyValidationErrorsAreVisible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mode vault.MasterKeyMode
|
||||
password string
|
||||
keyFile string
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
name: "password mode requires password",
|
||||
mode: vault.MasterKeyModePasswordOnly,
|
||||
wantError: "master password is required",
|
||||
},
|
||||
{
|
||||
name: "key file mode requires path",
|
||||
mode: vault.MasterKeyModeKeyFileOnly,
|
||||
wantError: "key file is required",
|
||||
},
|
||||
{
|
||||
name: "composite mode requires password",
|
||||
mode: vault.MasterKeyModePasswordAndKeyFile,
|
||||
keyFile: filepath.Join("/tmp", "ignored.key"),
|
||||
wantError: "master password is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u.setMasterKeyMode(tt.mode)
|
||||
u.masterPassword.SetText(tt.password)
|
||||
u.keyFilePath.SetText(tt.keyFile)
|
||||
|
||||
u.runAction("create vault", u.createVaultAction)
|
||||
|
||||
if got := u.errorMessage; got != tt.wantError {
|
||||
t.Fatalf("errorMessage = %q, want %q", got, tt.wantError)
|
||||
}
|
||||
if got := u.statusMessage; got != "" {
|
||||
t.Fatalf("statusMessage = %q, want empty on validation error", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIUnreadableAndInvalidMasterKeyErrorsAreVisible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keyFile := filepath.Join(t.TempDir(), "master.key")
|
||||
if err := os.WriteFile(keyFile, []byte("key-material"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(master.key) error = %v", err)
|
||||
}
|
||||
|
||||
create := newUIWithSession("desktop", &session.Manager{})
|
||||
create.setMasterKeyMode(vault.MasterKeyModeKeyFileOnly)
|
||||
create.keyFilePath.SetText(keyFile)
|
||||
if err := create.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
}
|
||||
|
||||
path := filepath.Join(t.TempDir(), "keepassgo.kdbx")
|
||||
create.saveAsPath.SetText(path)
|
||||
if err := create.saveAsAction(); err != nil {
|
||||
t.Fatalf("saveAsAction() error = %v", err)
|
||||
}
|
||||
|
||||
unreadable := newUIWithSession("desktop", &session.Manager{})
|
||||
unreadable.setMasterKeyMode(vault.MasterKeyModeKeyFileOnly)
|
||||
unreadable.keyFilePath.SetText(filepath.Join(t.TempDir(), "missing.key"))
|
||||
unreadable.runAction("open vault", unreadable.openVaultAction)
|
||||
if got := unreadable.errorMessage; got == "" || got[:14] != "read key file:" {
|
||||
t.Fatalf("errorMessage = %q, want read key file error", got)
|
||||
}
|
||||
|
||||
wrong := newUIWithSession("desktop", &session.Manager{})
|
||||
wrong.setMasterKeyMode(vault.MasterKeyModeKeyFileOnly)
|
||||
wrong.keyFilePath.SetText(filepath.Join(t.TempDir(), "wrong.key"))
|
||||
if err := os.WriteFile(wrong.keyFilePath.Text(), []byte("wrong-key"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(wrong.key) error = %v", err)
|
||||
}
|
||||
wrong.vaultPath.SetText(path)
|
||||
wrong.runAction("open vault", wrong.openVaultAction)
|
||||
if got := wrong.errorMessage; got == "" || !bytes.Contains([]byte(got), []byte(vault.ErrInvalidMasterKey.Error())) {
|
||||
t.Fatalf("errorMessage = %q, want invalid master key error", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIOpenRemoteAndSaveThroughConfiguredWebDAVTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -230,6 +482,7 @@ func TestUIMasterKeyInputSupportsKeyFileAndCompositeKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordAndKeyFile)
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
u.keyFilePath.SetText(keyFile)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user