From 341f8829d67b197ece20fe1cb0da929486665853 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Jul 2020 23:44:04 +0300 Subject: Add very crude interactive verification support --- ui/verification-modal.go | 192 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 ui/verification-modal.go (limited to 'ui/verification-modal.go') diff --git a/ui/verification-modal.go b/ui/verification-modal.go new file mode 100644 index 0000000..fd77777 --- /dev/null +++ b/ui/verification-modal.go @@ -0,0 +1,192 @@ +// 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 . + +package ui + +import ( + "fmt" + "strconv" + "strings" + "time" + + "maunium.net/go/mauview" + "maunium.net/go/tcell" + + "maunium.net/go/gomuks/debug" + "maunium.net/go/mautrix/crypto" +) + +type EmojiView struct { + mauview.SimpleEventHandler + Numbers *[3]uint + Emojis *[7]crypto.VerificationEmoji +} + +func (e *EmojiView) Draw(screen mauview.Screen) { + if e.Emojis != nil { + width := 10 + for i, emoji := range e.Emojis { + x := i*width + i + y := 0 + if i >= 4 { + x = (i-4)*width + i + y = 2 + } + mauview.Print(screen, string(emoji.Emoji), x, y, width, mauview.AlignCenter, tcell.ColorDefault) + mauview.Print(screen, emoji.Description, x, y+1, width, mauview.AlignCenter, tcell.ColorDefault) + } + } else if e.Numbers != nil { + maxWidth := 43 + for i, number := range e.Numbers { + mauview.Print(screen, strconv.FormatUint(uint64(number), 10), 0, i, maxWidth, mauview.AlignCenter, tcell.ColorDefault) + } + } +} + +type VerificationModal struct { + mauview.Component + + container *mauview.Box + + waitingBar *mauview.ProgressBar + infoText *mauview.TextView + emojiText *EmojiView + inputBar *mauview.InputField + + stopWaiting chan struct{} + confirmChan chan bool + + parent *MainView +} + +func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, timeout time.Duration) *VerificationModal { + vm := &VerificationModal{ + parent: mainView, + stopWaiting: make(chan struct{}), + confirmChan: make(chan bool), + } + + progress := int(timeout.Seconds()) + vm.waitingBar = mauview.NewProgressBar(). + SetMax(progress). + SetProgress(progress). + SetIndeterminate(false) + + vm.infoText = mauview.NewTextView() + vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", device.UserID)) + + vm.emojiText = &EmojiView{} + + vm.inputBar = mauview.NewInputField().SetBackgroundColor(tcell.ColorDefault) + + flex := mauview.NewFlex(). + SetDirection(mauview.FlexRow). + AddFixedComponent(vm.waitingBar, 1). + AddFixedComponent(vm.infoText, 4). + AddFixedComponent(vm.emojiText, 4). + AddFixedComponent(vm.inputBar, 1) + + vm.container = mauview.NewBox(flex). + SetBorder(true). + SetTitle("Interactive verification") + + vm.Component = mauview.Center(vm.container, 45, 12).SetAlwaysFocusChild(true) + + go vm.decrementWaitingBar(progress) + + return vm +} + +func (vm *VerificationModal) decrementWaitingBar(progress int) { + for { + select { + case <-time.Tick(time.Second): + if progress <= 0 { + vm.parent.HideModal() + vm.parent.parent.Render() + return + } + progress-- + vm.waitingBar.SetProgress(progress) + vm.parent.parent.Render() + case <-vm.stopWaiting: + vm.waitingBar.SetIndeterminate(true) + break + } + } +} + +func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoji, _ *crypto.DeviceIdentity) bool { + vm.infoText.SetText("Check if the other device is showing the same emojis as below, then type \"yes\" to accept, or \"no\" to reject") + vm.inputBar. + SetTextColor(tcell.ColorWhite). + SetBackgroundColor(tcell.ColorDarkCyan). + SetPlaceholder("Type \"yes\" or \"no\""). + Focus() + vm.emojiText.Emojis = &emojis + vm.parent.parent.Render() + vm.stopWaiting <- struct{}{} + confirm := <-vm.confirmChan + // TODO this should hook into cancel/success of the verification and display a success message instead of just closing + vm.parent.HideModal() + vm.parent.parent.Render() + return confirm +} + +func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint, _ *crypto.DeviceIdentity) bool { + vm.infoText.SetText("Check if the other device is showing the same numbers as below, then type \"yes\" to accept, or \"no\" to reject") + vm.inputBar. + SetTextColor(tcell.ColorWhite). + SetBackgroundColor(tcell.ColorDarkCyan). + SetPlaceholder("Type \"yes\" or \"no\""). + Focus() + vm.emojiText.Numbers = &numbers + vm.parent.parent.Render() + vm.stopWaiting <- struct{}{} + confirm := <-vm.confirmChan + // TODO this should hook into cancel/success of the verification and display a success message instead of just closing + vm.parent.HideModal() + vm.parent.parent.Render() + return confirm +} + +func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { + if vm.emojiText.Emojis == nil && vm.emojiText.Numbers == nil { + debug.Print("Ignoring pre-emoji key event") + return false + } + if event.Key() == tcell.KeyEnter { + text := strings.ToLower(strings.TrimSpace(vm.inputBar.GetText())) + if text == "yes" { + debug.Print("Confirming verification") + vm.confirmChan <- true + } else if text == "no" { + debug.Print("Rejecting verification") + vm.confirmChan <- false + } + return true + } else { + return vm.inputBar.OnKeyEvent(event) + } +} + +func (vm *VerificationModal) Focus() { + vm.container.Focus() +} + +func (vm *VerificationModal) Blur() { + vm.container.Blur() +} -- cgit v1.2.3 From ead7e0bf1d9c584224c1738b32ad26e314957220 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 25 Jul 2020 18:40:31 +0300 Subject: Make verification modal wait for confirmation --- ui/verification-modal.go | 58 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 12 deletions(-) (limited to 'ui/verification-modal.go') diff --git a/ui/verification-modal.go b/ui/verification-modal.go index fd77777..e47006e 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -27,6 +27,7 @@ import ( "maunium.net/go/gomuks/debug" "maunium.net/go/mautrix/crypto" + "maunium.net/go/mautrix/event" ) type EmojiView struct { @@ -59,6 +60,8 @@ func (e *EmojiView) Draw(screen mauview.Screen) { type VerificationModal struct { mauview.Component + device *crypto.DeviceIdentity + container *mauview.Box waitingBar *mauview.ProgressBar @@ -68,6 +71,7 @@ type VerificationModal struct { stopWaiting chan struct{} confirmChan chan bool + done bool parent *MainView } @@ -75,8 +79,10 @@ type VerificationModal struct { func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, timeout time.Duration) *VerificationModal { vm := &VerificationModal{ parent: mainView, + device: device, stopWaiting: make(chan struct{}), confirmChan: make(chan bool), + done: false, } progress := int(timeout.Seconds()) @@ -115,8 +121,6 @@ func (vm *VerificationModal) decrementWaitingBar(progress int) { select { case <-time.Tick(time.Second): if progress <= 0 { - vm.parent.HideModal() - vm.parent.parent.Render() return } progress-- @@ -124,13 +128,14 @@ func (vm *VerificationModal) decrementWaitingBar(progress int) { vm.parent.parent.Render() case <-vm.stopWaiting: vm.waitingBar.SetIndeterminate(true) - break + vm.parent.parent.app.SetRedrawTicker(100 * time.Millisecond) + return } } } -func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoji, _ *crypto.DeviceIdentity) bool { - vm.infoText.SetText("Check if the other device is showing the same emojis as below, then type \"yes\" to accept, or \"no\" to reject") +func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoji) bool { + vm.infoText.SetText("Check if the other device is showing the\nsame emojis as below, then type \"yes\" to\naccept, or \"no\" to reject") vm.inputBar. SetTextColor(tcell.ColorWhite). SetBackgroundColor(tcell.ColorDarkCyan). @@ -140,14 +145,14 @@ func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoj vm.parent.parent.Render() vm.stopWaiting <- struct{}{} confirm := <-vm.confirmChan - // TODO this should hook into cancel/success of the verification and display a success message instead of just closing - vm.parent.HideModal() + vm.emojiText.Emojis = nil + vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", vm.device.UserID)) vm.parent.parent.Render() return confirm } -func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint, _ *crypto.DeviceIdentity) bool { - vm.infoText.SetText("Check if the other device is showing the same numbers as below, then type \"yes\" to accept, or \"no\" to reject") +func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint) bool { + vm.infoText.SetText("Check if the other device is showing the\nsame numbers as below, then type \"yes\" to\naccept, or \"no\" to reject") vm.inputBar. SetTextColor(tcell.ColorWhite). SetBackgroundColor(tcell.ColorDarkCyan). @@ -157,14 +162,42 @@ func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint, _ *crypto.Devic vm.parent.parent.Render() vm.stopWaiting <- struct{}{} confirm := <-vm.confirmChan - // TODO this should hook into cancel/success of the verification and display a success message instead of just closing - vm.parent.HideModal() + vm.emojiText.Numbers = nil + vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", vm.device.UserID)) vm.parent.parent.Render() return confirm } +func (vm *VerificationModal) OnCancel(cancelledByUs bool, reason string, _ event.VerificationCancelCode) { + vm.waitingBar.SetIndeterminate(false).SetMax(100).SetProgress(100) + vm.parent.parent.app.SetRedrawTicker(1 * time.Minute) + if cancelledByUs { + vm.infoText.SetText(fmt.Sprintf("Verification failed: %s", reason)) + } else { + vm.infoText.SetText(fmt.Sprintf("Verification cancelled by %s: %s", vm.device.UserID, reason)) + } + vm.inputBar.SetPlaceholder("Press enter to close dialog") + vm.done = true + vm.parent.parent.Render() +} + +func (vm *VerificationModal) OnSuccess() { + vm.waitingBar.SetIndeterminate(false).SetMax(100).SetProgress(100) + vm.parent.parent.app.SetRedrawTicker(1 * time.Minute) + vm.infoText.SetText(fmt.Sprintf("Successfully verified %s (%s) of %s", vm.device.Name, vm.device.DeviceID, vm.device.UserID)) + vm.inputBar.SetPlaceholder("Press enter to close dialog") + vm.done = true + vm.parent.parent.Render() +} + func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { - if vm.emojiText.Emojis == nil && vm.emojiText.Numbers == nil { + if vm.done { + if event.Key() == tcell.KeyEnter || event.Key() == tcell.KeyEsc { + vm.parent.HideModal() + return true + } + return false + } else if vm.emojiText.Emojis == nil && vm.emojiText.Numbers == nil { debug.Print("Ignoring pre-emoji key event") return false } @@ -177,6 +210,7 @@ func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { debug.Print("Rejecting verification") vm.confirmChan <- false } + vm.inputBar.SetTextAndMoveCursor("") return true } else { return vm.inputBar.OnKeyEvent(event) -- cgit v1.2.3 From ee3594db46fe261962f0a8a11c48cb9d6f84938f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 25 Jul 2020 20:54:32 +0300 Subject: Add toggle to only send to verified devices --- ui/verification-modal.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'ui/verification-modal.go') diff --git a/ui/verification-modal.go b/ui/verification-modal.go index e47006e..54c2ecf 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -96,7 +96,9 @@ func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, tim vm.emojiText = &EmojiView{} - vm.inputBar = mauview.NewInputField().SetBackgroundColor(tcell.ColorDefault) + vm.inputBar = mauview.NewInputField(). + SetBackgroundColor(tcell.ColorDefault). + SetPlaceholderTextColor(tcell.ColorWhite) flex := mauview.NewFlex(). SetDirection(mauview.FlexRow). @@ -176,7 +178,7 @@ func (vm *VerificationModal) OnCancel(cancelledByUs bool, reason string, _ event } else { vm.infoText.SetText(fmt.Sprintf("Verification cancelled by %s: %s", vm.device.UserID, reason)) } - vm.inputBar.SetPlaceholder("Press enter to close dialog") + vm.inputBar.SetPlaceholder("Press enter to close the dialog") vm.done = true vm.parent.parent.Render() } @@ -185,9 +187,13 @@ func (vm *VerificationModal) OnSuccess() { vm.waitingBar.SetIndeterminate(false).SetMax(100).SetProgress(100) vm.parent.parent.app.SetRedrawTicker(1 * time.Minute) vm.infoText.SetText(fmt.Sprintf("Successfully verified %s (%s) of %s", vm.device.Name, vm.device.DeviceID, vm.device.UserID)) - vm.inputBar.SetPlaceholder("Press enter to close dialog") + vm.inputBar.SetPlaceholder("Press enter to close the dialog") vm.done = true vm.parent.parent.Render() + if vm.parent.config.SendToVerifiedOnly { + // Hacky way to make new group sessions after verified + vm.parent.matrix.Crypto().(*crypto.OlmMachine).OnDevicesChanged(vm.device.UserID) + } } func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { -- cgit v1.2.3 From aac9db09d6e2e678dceacc0b43d3e4e4878d9933 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 25 Jul 2020 20:59:54 +0300 Subject: Clear verification modal input bar placeholder after accepting --- ui/verification-modal.go | 1 + 1 file changed, 1 insertion(+) (limited to 'ui/verification-modal.go') diff --git a/ui/verification-modal.go b/ui/verification-modal.go index 54c2ecf..b3fbd35 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -216,6 +216,7 @@ func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { debug.Print("Rejecting verification") vm.confirmChan <- false } + vm.inputBar.SetPlaceholder("") vm.inputBar.SetTextAndMoveCursor("") return true } else { -- cgit v1.2.3 From 2f5f0674b600f129204958c810843d998f6a2f6a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 30 Jul 2020 14:32:59 +0300 Subject: Update mautrix-go and make it build without crypto --- ui/verification-modal.go | 94 +++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 41 deletions(-) (limited to 'ui/verification-modal.go') diff --git a/ui/verification-modal.go b/ui/verification-modal.go index b3fbd35..bc529c9 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// +build cgo + package ui import ( @@ -32,14 +34,17 @@ import ( type EmojiView struct { mauview.SimpleEventHandler - Numbers *[3]uint - Emojis *[7]crypto.VerificationEmoji + Data crypto.SASData } func (e *EmojiView) Draw(screen mauview.Screen) { - if e.Emojis != nil { + if e.Data == nil { + return + } + switch e.Data.Type() { + case event.SASEmoji: width := 10 - for i, emoji := range e.Emojis { + for i, emoji := range e.Data.(crypto.EmojiSASData) { x := i*width + i y := 0 if i >= 4 { @@ -49,9 +54,9 @@ func (e *EmojiView) Draw(screen mauview.Screen) { mauview.Print(screen, string(emoji.Emoji), x, y, width, mauview.AlignCenter, tcell.ColorDefault) mauview.Print(screen, emoji.Description, x, y+1, width, mauview.AlignCenter, tcell.ColorDefault) } - } else if e.Numbers != nil { + case event.SASDecimal: maxWidth := 43 - for i, number := range e.Numbers { + for i, number := range e.Data.(crypto.DecimalSASData) { mauview.Print(screen, strconv.FormatUint(uint64(number), 10), 0, i, maxWidth, mauview.AlignCenter, tcell.ColorDefault) } } @@ -69,6 +74,8 @@ type VerificationModal struct { emojiText *EmojiView inputBar *mauview.InputField + progress int + progressMax int stopWaiting chan struct{} confirmChan chan bool done bool @@ -85,14 +92,15 @@ func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, tim done: false, } - progress := int(timeout.Seconds()) + vm.progressMax = int(timeout.Seconds()) + vm.progress = vm.progressMax vm.waitingBar = mauview.NewProgressBar(). - SetMax(progress). - SetProgress(progress). + SetMax(vm.progressMax). + SetProgress(vm.progress). SetIndeterminate(false) vm.infoText = mauview.NewTextView() - vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", device.UserID)) + vm.infoText.SetText(fmt.Sprintf("Waiting for %s\nto accept", device.UserID)) vm.emojiText = &EmojiView{} @@ -113,59 +121,58 @@ func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, tim vm.Component = mauview.Center(vm.container, 45, 12).SetAlwaysFocusChild(true) - go vm.decrementWaitingBar(progress) + go vm.decrementWaitingBar() return vm } -func (vm *VerificationModal) decrementWaitingBar(progress int) { +func (vm *VerificationModal) decrementWaitingBar() { for { select { case <-time.Tick(time.Second): - if progress <= 0 { + if vm.progress <= 0 { + vm.waitingBar.SetIndeterminate(true) + vm.parent.parent.app.SetRedrawTicker(100 * time.Millisecond) return } - progress-- - vm.waitingBar.SetProgress(progress) + vm.progress-- + vm.waitingBar.SetProgress(vm.progress) vm.parent.parent.Render() case <-vm.stopWaiting: - vm.waitingBar.SetIndeterminate(true) - vm.parent.parent.app.SetRedrawTicker(100 * time.Millisecond) return } } } -func (vm *VerificationModal) VerifyEmojisMatch(emojis [7]crypto.VerificationEmoji) bool { - vm.infoText.SetText("Check if the other device is showing the\nsame emojis as below, then type \"yes\" to\naccept, or \"no\" to reject") - vm.inputBar. - SetTextColor(tcell.ColorWhite). - SetBackgroundColor(tcell.ColorDarkCyan). - SetPlaceholder("Type \"yes\" or \"no\""). - Focus() - vm.emojiText.Emojis = &emojis - vm.parent.parent.Render() - vm.stopWaiting <- struct{}{} - confirm := <-vm.confirmChan - vm.emojiText.Emojis = nil - vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", vm.device.UserID)) - vm.parent.parent.Render() - return confirm +func (vm *VerificationModal) VerificationMethods() []crypto.VerificationMethod { + return []crypto.VerificationMethod{crypto.VerificationMethodEmoji{}, crypto.VerificationMethodDecimal{}} } -func (vm *VerificationModal) VerifyNumbersMatch(numbers [3]uint) bool { - vm.infoText.SetText("Check if the other device is showing the\nsame numbers as below, then type \"yes\" to\naccept, or \"no\" to reject") +func (vm *VerificationModal) VerifySASMatch(_ *crypto.DeviceIdentity, data crypto.SASData) bool { + var typeName string + if data.Type() == event.SASDecimal { + typeName = "numbers" + } else if data.Type() == event.SASEmoji { + typeName = "emojis" + } else { + return false + } + vm.infoText.SetText(fmt.Sprintf( + "Check if the other device is showing the\n"+ + "same %s as below, then type \"yes\" to\n"+ + "accept, or \"no\" to reject", typeName)) vm.inputBar. SetTextColor(tcell.ColorWhite). SetBackgroundColor(tcell.ColorDarkCyan). SetPlaceholder("Type \"yes\" or \"no\""). Focus() - vm.emojiText.Numbers = &numbers + vm.emojiText.Data = data vm.parent.parent.Render() - vm.stopWaiting <- struct{}{} + vm.progress = vm.progressMax confirm := <-vm.confirmChan - vm.emojiText.Numbers = nil - vm.infoText.SetText(fmt.Sprintf("Waiting for %s to accept", vm.device.UserID)) + vm.progress = vm.progressMax + vm.emojiText.Data = nil + vm.infoText.SetText(fmt.Sprintf("Waiting for %s\nto confirm", vm.device.UserID)) vm.parent.parent.Render() return confirm } @@ -179,6 +186,7 @@ func (vm *VerificationModal) OnCancel(cancelledByUs bool, reason string, _ event vm.infoText.SetText(fmt.Sprintf("Verification cancelled by %s: %s", vm.device.UserID, reason)) } vm.inputBar.SetPlaceholder("Press enter to close the dialog") + vm.stopWaiting <- struct{}{} vm.done = true vm.parent.parent.Render() } @@ -188,6 +196,7 @@ func (vm *VerificationModal) OnSuccess() { vm.parent.parent.app.SetRedrawTicker(1 * time.Minute) vm.infoText.SetText(fmt.Sprintf("Successfully verified %s (%s) of %s", vm.device.Name, vm.device.DeviceID, vm.device.UserID)) vm.inputBar.SetPlaceholder("Press enter to close the dialog") + vm.stopWaiting <- struct{}{} vm.done = true vm.parent.parent.Render() if vm.parent.config.SendToVerifiedOnly { @@ -203,7 +212,7 @@ func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { return true } return false - } else if vm.emojiText.Emojis == nil && vm.emojiText.Numbers == nil { + } else if vm.emojiText.Data == nil { debug.Print("Ignoring pre-emoji key event") return false } @@ -216,8 +225,11 @@ func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { debug.Print("Rejecting verification") vm.confirmChan <- false } - vm.inputBar.SetPlaceholder("") - vm.inputBar.SetTextAndMoveCursor("") + vm.inputBar. + SetPlaceholder(""). + SetTextAndMoveCursor(""). + SetBackgroundColor(tcell.ColorDefault). + SetTextColor(tcell.ColorDefault) return true } else { return vm.inputBar.OnKeyEvent(event) -- cgit v1.2.3