Add password modal and file path autocompletion
This commit is contained in:
parent
c47252979c
commit
95b8a958fd
@ -21,6 +21,7 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@ -232,21 +233,57 @@ func cmdResetSession(cmd *Command) {
|
||||
}
|
||||
|
||||
func autocompleteFile(cmd *CommandAutocomplete) (completions []string, newText string) {
|
||||
// TODO implement
|
||||
return []string{}, ""
|
||||
inputPath, err := filepath.Abs(cmd.RawArgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO add dialog for asking passphrase
|
||||
const extremelyTemporaryHardcodedPassphrase = "gomuks"
|
||||
var searchNamePrefix, searchDir string
|
||||
if strings.HasSuffix(cmd.RawArgs, "/") {
|
||||
searchDir = inputPath
|
||||
} else {
|
||||
searchNamePrefix = filepath.Base(inputPath)
|
||||
searchDir = filepath.Dir(inputPath)
|
||||
}
|
||||
files, err := ioutil.ReadDir(searchDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
if !strings.HasPrefix(name, searchNamePrefix) || (name[0] == '.' && searchNamePrefix == "") {
|
||||
continue
|
||||
}
|
||||
fullPath := filepath.Join(searchDir, name)
|
||||
if file.IsDir() {
|
||||
fullPath += "/"
|
||||
}
|
||||
completions = append(completions, fullPath)
|
||||
}
|
||||
if len(completions) == 1 {
|
||||
newText = fmt.Sprintf("/%s %s", cmd.OrigCommand, completions[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func cmdImportKeys(cmd *Command) {
|
||||
data, err := ioutil.ReadFile(cmd.RawArgs)
|
||||
path, err := filepath.Abs(cmd.RawArgs)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to read %s: %v", cmd.RawArgs, err)
|
||||
cmd.Reply("Failed to get absolute path: %v", err)
|
||||
return
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to read %s: %v", path, err)
|
||||
return
|
||||
}
|
||||
passphrase, ok := cmd.MainView.AskPassword("Key import", false)
|
||||
if !ok {
|
||||
cmd.Reply("Passphrase entry cancelled")
|
||||
return
|
||||
}
|
||||
mach := cmd.Matrix.Crypto().(*crypto.OlmMachine)
|
||||
imported, total, err := mach.ImportKeys(extremelyTemporaryHardcodedPassphrase, data)
|
||||
imported, total, err := mach.ImportKeys(passphrase, data)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to import sessions: %v", err)
|
||||
} else {
|
||||
@ -255,15 +292,25 @@ func cmdImportKeys(cmd *Command) {
|
||||
}
|
||||
|
||||
func exportKeys(cmd *Command, sessions []*crypto.InboundGroupSession) {
|
||||
export, err := crypto.ExportKeys(extremelyTemporaryHardcodedPassphrase, sessions)
|
||||
path, err := filepath.Abs(cmd.RawArgs)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to get absolute path: %v", err)
|
||||
return
|
||||
}
|
||||
passphrase, ok := cmd.MainView.AskPassword("Key export", true)
|
||||
if !ok {
|
||||
cmd.Reply("Passphrase entry cancelled")
|
||||
return
|
||||
}
|
||||
export, err := crypto.ExportKeys(passphrase, sessions)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to export sessions: %v", err)
|
||||
}
|
||||
err = ioutil.WriteFile(cmd.RawArgs, export, 0400)
|
||||
err = ioutil.WriteFile(path, export, 0400)
|
||||
if err != nil {
|
||||
cmd.Reply("Failed to write sessions to %s: %v", cmd.RawArgs, err)
|
||||
cmd.Reply("Failed to write sessions to %s: %v", path, err)
|
||||
} else {
|
||||
cmd.Reply("Successfully exported %d sessions to %s", len(sessions), cmd.RawArgs)
|
||||
cmd.Reply("Successfully exported %d sessions to %s", len(sessions), path)
|
||||
}
|
||||
}
|
||||
|
||||
|
133
ui/password-modal.go
Normal file
133
ui/password-modal.go
Normal file
@ -0,0 +1,133 @@
|
||||
// gomuks - A terminal Matrix client written in Go.
|
||||
// Copyright (C) 2020 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"maunium.net/go/mauview"
|
||||
"maunium.net/go/tcell"
|
||||
)
|
||||
|
||||
type PasswordModal struct {
|
||||
mauview.Component
|
||||
|
||||
outputChan chan string
|
||||
cancelChan chan struct{}
|
||||
|
||||
form *mauview.Form
|
||||
|
||||
text *mauview.TextField
|
||||
confirmText *mauview.TextField
|
||||
|
||||
input *mauview.InputField
|
||||
confirmInput *mauview.InputField
|
||||
|
||||
cancel *mauview.Button
|
||||
submit *mauview.Button
|
||||
|
||||
parent *MainView
|
||||
}
|
||||
|
||||
func (view *MainView) AskPassword(title string, isNew bool) (string, bool) {
|
||||
pwm := NewPasswordModal(view, title, isNew)
|
||||
view.ShowModal(pwm)
|
||||
return pwm.Wait()
|
||||
}
|
||||
|
||||
func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal {
|
||||
pwm := &PasswordModal{
|
||||
parent: parent,
|
||||
form: mauview.NewForm(),
|
||||
outputChan: make(chan string, 1),
|
||||
cancelChan: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
pwm.form.
|
||||
SetColumns([]int{1, 20, 1, 20, 1}).
|
||||
SetRows([]int{1, 1, 1, 0, 0, 0, 1, 1, 1})
|
||||
|
||||
width := 45
|
||||
height := 8
|
||||
|
||||
pwm.text = mauview.NewTextField()
|
||||
if isNew {
|
||||
pwm.text.SetText("Create a passphrase")
|
||||
} else {
|
||||
pwm.text.SetText("Enter the passphrase")
|
||||
}
|
||||
pwm.input = mauview.NewInputField().
|
||||
SetMaskCharacter('*').
|
||||
SetPlaceholder("correct horse battery staple")
|
||||
pwm.form.AddComponent(pwm.text, 1, 1, 3, 1)
|
||||
pwm.form.AddFormItem(pwm.input, 1, 2, 3, 1)
|
||||
|
||||
if isNew {
|
||||
height += 3
|
||||
pwm.confirmInput = mauview.NewInputField().
|
||||
SetMaskCharacter('*').
|
||||
SetPlaceholder("correct horse battery staple").
|
||||
SetChangedFunc(pwm.HandleChange)
|
||||
pwm.input.SetChangedFunc(pwm.HandleChange)
|
||||
pwm.confirmText = mauview.NewTextField().SetText("Confirm passphrase")
|
||||
|
||||
pwm.form.SetRow(3, 1).SetRow(4, 1).SetRow(5, 1)
|
||||
pwm.form.AddComponent(pwm.confirmText, 1, 4, 3, 1)
|
||||
pwm.form.AddFormItem(pwm.confirmInput, 1, 5, 3, 1)
|
||||
}
|
||||
|
||||
pwm.cancel = mauview.NewButton("Cancel").SetOnClick(pwm.ClickCancel)
|
||||
pwm.submit = mauview.NewButton("Submit").SetOnClick(pwm.ClickSubmit)
|
||||
|
||||
pwm.form.AddFormItem(pwm.submit, 3, 7, 1, 1)
|
||||
pwm.form.AddFormItem(pwm.cancel, 1, 7, 1, 1)
|
||||
|
||||
box := mauview.NewBox(pwm.form).SetTitle(title)
|
||||
center := mauview.Center(box, width, height).SetAlwaysFocusChild(true)
|
||||
center.Focus()
|
||||
pwm.form.FocusNextItem()
|
||||
pwm.Component = center
|
||||
|
||||
return pwm
|
||||
}
|
||||
|
||||
func (pwm *PasswordModal) HandleChange(_ string) {
|
||||
if pwm.input.GetText() == pwm.confirmInput.GetText() {
|
||||
pwm.submit.SetBackgroundColor(mauview.Styles.ContrastBackgroundColor)
|
||||
} else {
|
||||
pwm.submit.SetBackgroundColor(tcell.ColorDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func (pwm *PasswordModal) ClickCancel() {
|
||||
pwm.parent.HideModal()
|
||||
pwm.cancelChan <- struct{}{}
|
||||
}
|
||||
|
||||
func (pwm *PasswordModal) ClickSubmit() {
|
||||
if pwm.confirmInput == nil || pwm.input.GetText() == pwm.confirmInput.GetText() {
|
||||
pwm.parent.HideModal()
|
||||
pwm.outputChan <- pwm.input.GetText()
|
||||
}
|
||||
}
|
||||
|
||||
func (pwm *PasswordModal) Wait() (string, bool) {
|
||||
select {
|
||||
case result := <- pwm.outputChan:
|
||||
return result, true
|
||||
case <- pwm.cancelChan:
|
||||
return "", false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user