Add UI master key setup and change flows
This commit is contained in:
@@ -1,111 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"git.julianfamily.org/keepassgo/appstate"
|
||||
"git.julianfamily.org/keepassgo/clipboard"
|
||||
"git.julianfamily.org/keepassgo/session"
|
||||
"git.julianfamily.org/keepassgo/vault"
|
||||
"git.julianfamily.org/keepassgo/webdav"
|
||||
"gioui.org/app"
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||
)
|
||||
|
||||
type entry = vault.Entry
|
||||
|
||||
type ui struct {
|
||||
mode string
|
||||
theme *material.Theme
|
||||
search widget.Editor
|
||||
vaultPath widget.Editor
|
||||
saveAsPath widget.Editor
|
||||
remoteBaseURL widget.Editor
|
||||
remotePath widget.Editor
|
||||
remoteUsername widget.Editor
|
||||
remotePassword widget.Editor
|
||||
masterPassword widget.Editor
|
||||
keyFilePath widget.Editor
|
||||
entryID widget.Editor
|
||||
entryTitle widget.Editor
|
||||
entryUsername widget.Editor
|
||||
entryPassword widget.Editor
|
||||
entryURL widget.Editor
|
||||
entryNotes widget.Editor
|
||||
entryTags widget.Editor
|
||||
entryPath widget.Editor
|
||||
entryFields widget.Editor
|
||||
historyIndex widget.Editor
|
||||
groupName widget.Editor
|
||||
passwordProfile widget.Editor
|
||||
attachmentName widget.Editor
|
||||
attachmentPath widget.Editor
|
||||
exportAttachmentPath widget.Editor
|
||||
list widget.List
|
||||
detailList widget.List
|
||||
copyUser widget.Clickable
|
||||
copyPass widget.Clickable
|
||||
copyURL widget.Clickable
|
||||
openURL widget.Clickable
|
||||
lockVault widget.Clickable
|
||||
unlockVault widget.Clickable
|
||||
createVault widget.Clickable
|
||||
openVault widget.Clickable
|
||||
saveVault widget.Clickable
|
||||
saveAsVault widget.Clickable
|
||||
openRemote widget.Clickable
|
||||
addEntry widget.Clickable
|
||||
saveEntry widget.Clickable
|
||||
duplicateEntry widget.Clickable
|
||||
deleteEntry widget.Clickable
|
||||
restoreEntry widget.Clickable
|
||||
saveTemplate widget.Clickable
|
||||
deleteTemplate widget.Clickable
|
||||
instantiateTemplate widget.Clickable
|
||||
addAttachment widget.Clickable
|
||||
removeAttachment widget.Clickable
|
||||
exportAttachment widget.Clickable
|
||||
restoreHistory widget.Clickable
|
||||
generatePassword widget.Clickable
|
||||
createGroup widget.Clickable
|
||||
renameGroup widget.Clickable
|
||||
deleteGroup widget.Clickable
|
||||
togglePasswordInline widget.Clickable
|
||||
showEntries widget.Clickable
|
||||
showTemplates widget.Clickable
|
||||
showRecycle widget.Clickable
|
||||
entryClicks []widget.Clickable
|
||||
breadcrumbs []widget.Clickable
|
||||
groupClicks []widget.Clickable
|
||||
state appstate.State
|
||||
visible []entry
|
||||
currentPath []string
|
||||
showPassword bool
|
||||
togglePassword widget.Clickable
|
||||
phoneSplit widget.Float
|
||||
splitDrag gesture.Drag
|
||||
splitBase float32
|
||||
splitStartY float32
|
||||
phoneSpan int
|
||||
eyeIcon *widget.Icon
|
||||
eyeOffIcon *widget.Icon
|
||||
copyIcon *widget.Icon
|
||||
statusMessage string
|
||||
errorMessage string
|
||||
mode string
|
||||
theme *material.Theme
|
||||
search widget.Editor
|
||||
vaultPath widget.Editor
|
||||
saveAsPath widget.Editor
|
||||
remoteBaseURL widget.Editor
|
||||
remotePath widget.Editor
|
||||
remoteUsername widget.Editor
|
||||
remotePassword widget.Editor
|
||||
masterPassword widget.Editor
|
||||
keyFilePath widget.Editor
|
||||
entryID widget.Editor
|
||||
entryTitle widget.Editor
|
||||
entryUsername widget.Editor
|
||||
entryPassword widget.Editor
|
||||
entryURL widget.Editor
|
||||
entryNotes widget.Editor
|
||||
entryTags widget.Editor
|
||||
entryPath widget.Editor
|
||||
entryFields widget.Editor
|
||||
historyIndex widget.Editor
|
||||
groupName widget.Editor
|
||||
passwordProfile widget.Editor
|
||||
attachmentName widget.Editor
|
||||
attachmentPath widget.Editor
|
||||
exportAttachmentPath widget.Editor
|
||||
list widget.List
|
||||
detailList widget.List
|
||||
copyUser widget.Clickable
|
||||
copyPass widget.Clickable
|
||||
copyURL widget.Clickable
|
||||
openURL widget.Clickable
|
||||
lockVault widget.Clickable
|
||||
unlockVault widget.Clickable
|
||||
createVault widget.Clickable
|
||||
openVault widget.Clickable
|
||||
saveVault widget.Clickable
|
||||
saveAsVault widget.Clickable
|
||||
openRemote widget.Clickable
|
||||
changeMasterKey widget.Clickable
|
||||
addEntry widget.Clickable
|
||||
saveEntry widget.Clickable
|
||||
duplicateEntry widget.Clickable
|
||||
deleteEntry widget.Clickable
|
||||
restoreEntry widget.Clickable
|
||||
saveTemplate widget.Clickable
|
||||
deleteTemplate widget.Clickable
|
||||
instantiateTemplate widget.Clickable
|
||||
addAttachment widget.Clickable
|
||||
removeAttachment widget.Clickable
|
||||
exportAttachment widget.Clickable
|
||||
restoreHistory widget.Clickable
|
||||
generatePassword widget.Clickable
|
||||
createGroup widget.Clickable
|
||||
renameGroup widget.Clickable
|
||||
deleteGroup widget.Clickable
|
||||
togglePasswordInline widget.Clickable
|
||||
showEntries widget.Clickable
|
||||
showTemplates widget.Clickable
|
||||
showRecycle widget.Clickable
|
||||
masterKeyPasswordOnly widget.Clickable
|
||||
masterKeyKeyFileOnly widget.Clickable
|
||||
masterKeyComposite widget.Clickable
|
||||
entryClicks []widget.Clickable
|
||||
breadcrumbs []widget.Clickable
|
||||
groupClicks []widget.Clickable
|
||||
state appstate.State
|
||||
masterKeyMode vault.MasterKeyMode
|
||||
visible []entry
|
||||
currentPath []string
|
||||
showPassword bool
|
||||
togglePassword widget.Clickable
|
||||
phoneSplit widget.Float
|
||||
splitDrag gesture.Drag
|
||||
splitBase float32
|
||||
splitStartY float32
|
||||
phoneSpan int
|
||||
eyeIcon *widget.Icon
|
||||
eyeOffIcon *widget.Icon
|
||||
copyIcon *widget.Icon
|
||||
statusMessage string
|
||||
errorMessage string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -143,28 +148,28 @@ func newUIWithState(mode string, sess appstate.CurrentSession) *ui {
|
||||
SingleLine: true,
|
||||
Submit: false,
|
||||
},
|
||||
vaultPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
saveAsPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
remoteBaseURL: widget.Editor{SingleLine: true, Submit: false},
|
||||
remotePath: widget.Editor{SingleLine: true, Submit: false},
|
||||
remoteUsername: widget.Editor{SingleLine: true, Submit: false},
|
||||
remotePassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
masterPassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
keyFilePath: 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},
|
||||
entryPassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryURL: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryNotes: widget.Editor{SingleLine: false, Submit: false},
|
||||
entryTags: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryFields: widget.Editor{SingleLine: false, Submit: false},
|
||||
historyIndex: widget.Editor{SingleLine: true, Submit: false},
|
||||
groupName: widget.Editor{SingleLine: true, Submit: false},
|
||||
passwordProfile: widget.Editor{SingleLine: true, Submit: false},
|
||||
attachmentName: widget.Editor{SingleLine: true, Submit: false},
|
||||
attachmentPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
vaultPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
saveAsPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
remoteBaseURL: widget.Editor{SingleLine: true, Submit: false},
|
||||
remotePath: widget.Editor{SingleLine: true, Submit: false},
|
||||
remoteUsername: widget.Editor{SingleLine: true, Submit: false},
|
||||
remotePassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
masterPassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
keyFilePath: 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},
|
||||
entryPassword: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryURL: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryNotes: widget.Editor{SingleLine: false, Submit: false},
|
||||
entryTags: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
entryFields: widget.Editor{SingleLine: false, Submit: false},
|
||||
historyIndex: widget.Editor{SingleLine: true, Submit: false},
|
||||
groupName: widget.Editor{SingleLine: true, Submit: false},
|
||||
passwordProfile: widget.Editor{SingleLine: true, Submit: false},
|
||||
attachmentName: widget.Editor{SingleLine: true, Submit: false},
|
||||
attachmentPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
exportAttachmentPath: widget.Editor{SingleLine: true, Submit: false},
|
||||
list: widget.List{
|
||||
List: layout.List{Axis: layout.Vertical},
|
||||
@@ -172,7 +177,8 @@ func newUIWithState(mode string, sess appstate.CurrentSession) *ui {
|
||||
detailList: widget.List{
|
||||
List: layout.List{Axis: layout.Vertical},
|
||||
},
|
||||
state: appstate.State{},
|
||||
state: appstate.State{},
|
||||
masterKeyMode: vault.MasterKeyModePasswordOnly,
|
||||
}
|
||||
u.state.Session = sess
|
||||
u.phoneSplit.Value = 0.46
|
||||
@@ -263,19 +269,44 @@ func (u *ui) selectedEntry() (entry, bool) {
|
||||
}
|
||||
|
||||
func (u *ui) currentMasterKey() (vault.MasterKey, error) {
|
||||
key := vault.MasterKey{Password: u.masterPassword.Text()}
|
||||
|
||||
password := u.masterPassword.Text()
|
||||
path := strings.TrimSpace(u.keyFilePath.Text())
|
||||
if path == "" {
|
||||
return key, nil
|
||||
|
||||
switch u.masterKeyMode {
|
||||
case vault.MasterKeyModeKeyFileOnly:
|
||||
if path == "" {
|
||||
return vault.MasterKey{}, fmt.Errorf("key file is required")
|
||||
}
|
||||
case vault.MasterKeyModePasswordAndKeyFile:
|
||||
if password == "" {
|
||||
return vault.MasterKey{}, fmt.Errorf("master password is required")
|
||||
}
|
||||
if path == "" {
|
||||
return vault.MasterKey{}, fmt.Errorf("key file is required")
|
||||
}
|
||||
default:
|
||||
if password == "" {
|
||||
return vault.MasterKey{}, fmt.Errorf("master password is required")
|
||||
}
|
||||
return vault.MasterKey{Password: password}, nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return vault.MasterKey{}, fmt.Errorf("read key file: %w", err)
|
||||
}
|
||||
key.KeyFileData = content
|
||||
return key, nil
|
||||
if len(content) == 0 {
|
||||
return vault.MasterKey{}, fmt.Errorf("key file is empty")
|
||||
}
|
||||
|
||||
return vault.MasterKey{
|
||||
Password: password,
|
||||
KeyFileData: content,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ui) setMasterKeyMode(mode vault.MasterKeyMode) {
|
||||
u.masterKeyMode = mode
|
||||
}
|
||||
|
||||
func (u *ui) createVaultAction() error {
|
||||
@@ -359,6 +390,14 @@ func (u *ui) unlockAction() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ui) changeMasterKeyAction() error {
|
||||
key, err := u.currentMasterKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.state.ChangeMasterKey(key)
|
||||
}
|
||||
|
||||
func (u *ui) runAction(label string, action func() error) {
|
||||
if err := action(); err != nil {
|
||||
u.errorMessage = err.Error()
|
||||
@@ -392,9 +431,21 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
for u.openRemote.Clicked(gtx) {
|
||||
u.runAction("open remote vault", u.openRemoteAction)
|
||||
}
|
||||
for u.changeMasterKey.Clicked(gtx) {
|
||||
u.runAction("change master key", u.changeMasterKeyAction)
|
||||
}
|
||||
for u.unlockVault.Clicked(gtx) {
|
||||
u.runAction("unlock vault", u.unlockAction)
|
||||
}
|
||||
for u.masterKeyPasswordOnly.Clicked(gtx) {
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordOnly)
|
||||
}
|
||||
for u.masterKeyKeyFileOnly.Clicked(gtx) {
|
||||
u.setMasterKeyMode(vault.MasterKeyModeKeyFileOnly)
|
||||
}
|
||||
for u.masterKeyComposite.Clicked(gtx) {
|
||||
u.setMasterKeyMode(vault.MasterKeyModePasswordAndKeyFile)
|
||||
}
|
||||
for u.showEntries.Clicked(gtx) {
|
||||
u.showEntriesSection()
|
||||
}
|
||||
@@ -757,7 +808,7 @@ func (u *ui) phoneSlider(gtx layout.Context) layout.Dimensions {
|
||||
y := (gtx.Constraints.Min.Y - handleH) / 2
|
||||
paint.FillShape(gtx.Ops, color.NRGBA{R: 214, G: 208, B: 197, A: 255}, clip.Rect{Min: image.Pt(0, y+1), Max: image.Pt(gtx.Constraints.Min.X, y+2)}.Op())
|
||||
paint.FillShape(gtx.Ops, accentColor, clip.RRect{
|
||||
Rect: image.Rectangle{Min: image.Pt(x, y), Max: image.Pt(x + handleW, y + handleH)},
|
||||
Rect: image.Rectangle{Min: image.Pt(x, y), Max: image.Pt(x+handleW, y+handleH)},
|
||||
NE: 2, NW: 2, SE: 2, SW: 2,
|
||||
}.Op(gtx.Ops))
|
||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||
|
||||
Reference in New Issue
Block a user