Add configurable vault security settings

This commit is contained in:
Joe Julian
2026-03-30 07:58:27 -07:00
parent 9afddd7a93
commit b7a4742ee6
11 changed files with 458 additions and 47 deletions
+120
View File
@@ -136,6 +136,8 @@ type ui struct {
apiPolicyOperation widget.Editor
apiPolicyPath widget.Editor
apiPolicyEntryID widget.Editor
securityCipher widget.Editor
securityKDF widget.Editor
entryID widget.Editor
entryTitle widget.Editor
entryUsername widget.Editor
@@ -170,8 +172,11 @@ type ui struct {
synchronizeVault widget.Clickable
toggleSyncMenu widget.Clickable
openAdvancedSync widget.Clickable
openSecuritySettings widget.Clickable
closeAdvancedSync widget.Clickable
closeSecuritySettings widget.Clickable
runAdvancedSync widget.Clickable
saveSecuritySettings widget.Clickable
editEntry widget.Clickable
cancelEdit widget.Clickable
pickVaultPath widget.Clickable
@@ -268,6 +273,7 @@ type ui struct {
syncRemotePassword widget.Editor
syncDialogOpen bool
syncMenuOpen bool
securityDialogOpen bool
showSyncPassword bool
keyboardFocus focusID
defaultSaveAsPath string
@@ -349,6 +355,8 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
apiPolicyOperation: widget.Editor{SingleLine: true, Submit: false},
apiPolicyPath: widget.Editor{SingleLine: true, Submit: false},
apiPolicyEntryID: widget.Editor{SingleLine: true, Submit: false},
securityCipher: widget.Editor{SingleLine: true, Submit: false},
securityKDF: widget.Editor{SingleLine: true, Submit: false},
entryID: widget.Editor{SingleLine: true, Submit: false},
entryTitle: widget.Editor{SingleLine: true, Submit: false},
entryUsername: widget.Editor{SingleLine: true, Submit: false},
@@ -398,6 +406,8 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
u.expandLessIcon, _ = widget.NewIcon(icons.NavigationExpandLess)
u.chevronDownIcon, _ = widget.NewIcon(icons.NavigationArrowDropDown)
u.passwordProfile.SetText("strong")
u.securityCipher.SetText(vault.CipherChaCha20)
u.securityKDF.SetText(vault.KDFArgon2)
u.keyboardFocus = focusSearch
u.setCustomFieldRows(nil)
u.loadRecentVaults()
@@ -628,6 +638,12 @@ func (u *ui) createVaultAction() error {
if err != nil {
return err
}
if err := u.state.ConfigureSecurity(vault.SecuritySettings{
Cipher: strings.TrimSpace(u.securityCipher.Text()),
KDF: strings.TrimSpace(u.securityKDF.Text()),
}); err != nil {
return err
}
if err := u.state.CreateVault(key); err != nil {
return err
}
@@ -640,6 +656,7 @@ func (u *ui) createVaultAction() error {
}
u.resetPasswordPeek()
u.currentPath = append([]string(nil), u.state.CurrentPath...)
u.loadSecuritySettingsFromSession()
u.editingEntry = false
u.filter()
return nil
@@ -662,6 +679,7 @@ func (u *ui) openVaultAction() error {
u.resetPasswordPeek()
u.currentPath = append([]string(nil), u.state.CurrentPath...)
u.restoreRecentVaultGroup(path)
u.loadSecuritySettingsFromSession()
u.editingEntry = false
u.filter()
return nil
@@ -709,6 +727,7 @@ func (u *ui) openRemoteAction() error {
)
u.resetPasswordPeek()
u.restoreRecentRemoteGroup(strings.TrimSpace(u.remoteBaseURL.Text()), strings.TrimSpace(u.remotePath.Text()))
u.loadSecuritySettingsFromSession()
u.editingEntry = false
u.filter()
return nil
@@ -737,6 +756,7 @@ func (u *ui) unlockAction() error {
}
u.resetPasswordPeek()
u.currentPath = append([]string(nil), u.state.CurrentPath...)
u.loadSecuritySettingsFromSession()
u.editingEntry = false
u.filter()
return nil
@@ -751,6 +771,27 @@ func (u *ui) changeMasterKeyAction() error {
return u.state.ChangeMasterKey(key)
}
func (u *ui) loadSecuritySettingsFromSession() {
settings, err := u.state.SecuritySettings()
if err != nil {
return
}
u.securityCipher.SetText(settings.Cipher)
u.securityKDF.SetText(settings.KDF)
}
func (u *ui) saveSecuritySettingsAction() error {
settings := vault.SecuritySettings{
Cipher: strings.TrimSpace(u.securityCipher.Text()),
KDF: strings.TrimSpace(u.securityKDF.Text()),
}
if err := u.state.ConfigureSecurity(settings); err != nil {
return err
}
u.securityDialogOpen = false
return nil
}
func (u *ui) clearMasterPassword() {
u.masterPassword.SetText("")
}
@@ -1518,13 +1559,23 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
for u.openAdvancedSync.Clicked(gtx) {
u.openAdvancedSyncDialog()
}
for u.openSecuritySettings.Clicked(gtx) {
u.loadSecuritySettingsFromSession()
u.securityDialogOpen = true
}
for u.closeAdvancedSync.Clicked(gtx) {
u.syncDialogOpen = false
u.showSyncPassword = false
}
for u.closeSecuritySettings.Clicked(gtx) {
u.securityDialogOpen = false
}
for u.runAdvancedSync.Clicked(gtx) {
u.runAction("advanced synchronize vault", u.advancedSyncAction)
}
for u.saveSecuritySettings.Clicked(gtx) {
u.runAction("save security settings", u.saveSecuritySettingsAction)
}
for u.unlockVault.Clicked(gtx) {
u.runAction("unlock vault", u.unlockAction)
}
@@ -1835,6 +1886,12 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
}
return u.syncDialog(gtx)
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
if !u.securityDialogOpen {
return layout.Dimensions{}
}
return u.securityDialog(gtx)
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
if _, ok := u.pendingApproval(); !ok {
return layout.Dimensions{}
@@ -1891,6 +1948,61 @@ func (u *ui) syncDialog(gtx layout.Context) layout.Dimensions {
)
}
func (u *ui) securityDialog(gtx layout.Context) layout.Dimensions {
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
paint.FillShape(gtx.Ops, color.NRGBA{A: 90}, clip.Rect{Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
width := gtx.Dp(unit.Dp(620))
if width > gtx.Constraints.Max.X {
width = gtx.Constraints.Max.X - gtx.Dp(unit.Dp(24))
}
if width < 1 {
width = gtx.Constraints.Max.X
}
gtx.Constraints.Min.X = width
gtx.Constraints.Max.X = width
return card(gtx, u.securityDialogContent)
})
}),
)
}
func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(20), "Security Settings")
lbl.Color = accentColor
return lbl.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(14), "Choose the KDBX cipher and KDF family KeePassGO should use for new or future saves.")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Supported values: "+strings.Join([]string{vault.CipherAES256, vault.CipherChaCha20}, ", "), &u.securityCipher, false)),
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Supported values: "+strings.Join([]string{vault.KDFAES, vault.KDFArgon2}, ", "), &u.securityKDF, false)),
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.closeSecuritySettings, "Cancel")
}),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.saveSecuritySettings, "Save Security Settings")
}),
)
}),
)
}
func (u *ui) approvalDialog(gtx layout.Context) layout.Dimensions {
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
@@ -2168,6 +2280,10 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(u.syncButtonGroup),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Security")
}),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.lockVault, "Lock")
return btn.Layout(gtx)
@@ -2505,6 +2621,10 @@ func (u *ui) detailPanel(gtx layout.Context) layout.Dimensions {
}),
layout.Rigid(u.syncButtonGroup),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Security")
}),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.lockVault, "Lock")
return btn.Layout(gtx)