A Go password manager for the terminal
Two Widgets
The terminal UI, like in the previous Snapshot columns, uses the termui package from GitHub. Listing 5 calls ui.Init()
in line 12 to initialize its functions and calls ui.Close()
to acknowledge a user abort in the defer
statement in line 15. This neatly folds up the UI to leave a usable terminal for the shell.
Listing 5
ui.go
01 package main 02 import ( 03 "fmt" 04 ui "github.com/gizak/termui/v3" 05 "github.com/gizak/termui/v3/widgets" 06 ) 07 func runUI(lines []string) { 08 rows := []string{} 09 for _, line := range lines { 10 rows = append(rows, mask(line)) 11 } 12 if err := ui.Init(); err != nil { 13 panic(err) 14 } 15 defer ui.Close() 16 lb := widgets.NewList() 17 lb.Rows = rows 18 lb.SelectedRow = 0 19 lb.SelectedRowStyle = ui.NewStyle(ui.ColorBlack) 20 lb.TextStyle.Fg = ui.ColorGreen 21 lb.Title = fmt.Sprintf("passview 1.0") 22 pa := widgets.NewParagraph() 23 pa.Text = "[Q]uit [Enter]reveal" 24 pa.TextStyle.Fg = ui.ColorBlack 25 w, h := ui.TerminalDimensions() 26 lb.SetRect(0, 0, w, h-3) 27 pa.SetRect(0, h-3, w, h) 28 ui.Render(lb, pa) 29 uiEvents := ui.PollEvents() 30 for { 31 select { 32 case e := <-uiEvents: 33 switch e.ID { 34 case "k": 35 hideCur(lb) 36 lb.ScrollUp() 37 ui.Render(lb) 38 case "j": 39 hideCur(lb) 40 lb.ScrollDown() 41 ui.Render(lb) 42 case "q", "<C-c>": 43 return 44 case "<Enter>": 45 showCur(lb, lines) 46 ui.Render(lb) 47 } 48 } 49 } 50 } 51 func hideCur(lb *widgets.List) { 52 idx := lb.SelectedRow 53 lb.Rows[idx] = mask(lb.Rows[idx]) 54 } 55 func showCur(lb *widgets.List, lines []string) { 56 idx := lb.SelectedRow 57 lb.Rows[idx] = lines[idx] 58 }
The terminal UI shown in Figure 1 consists of two stacked widgets: On top, there is a listbox with the password entries, which the user can scroll through. It also supports leafing through multiple pages if the list of entries is longer than the maximum number of lines displayed. Below the listbox, at the bottom of the terminal window, a paragraph widget indicates which keys the user can press next: Enter reveals the selected password, while Q exits the program.
To allow the UI to take advantage of the entire geometry of the terminal window, line 25 queries the window dimensions, using the TerminalsDimensions()
helper function from the termui package. From the width and height of the window, lines 26 and 27 then determine the position and dimensions of the two stacked widgets. In this case, the paragraph widget is assigned the bottom three lines, while the listbox on top gets everything else. Horizontally, both widgets extend to the edges of the terminal window.
The listbox entries reside in the Rows
attribute of the listbox as an array slice of strings. Line 17 populates this array with the rows
array slice. Before this happened, the for
loop starting in line 9 stuffed all masked entries into rows
but kept the original lines in lines
. The two array slices for masked and unmasked entries make it easy to later reveal masked entries: The code only needs to look at the same index number in the original slice to reveal the unmasked content.
After line 28 has drawn the widgets on screen, line 29 fires off the PollEvents()
Go routine, which will intercept all of the user's keystrokes concurrently from now on and send them to the uiEvents
channel. From there, the program fetches events via the select
statement in the infinite for
loop starting in line 30 and immediately responds to all incoming keystrokes. If the user presses K to scroll up, line 35 uses hideCur()
(starting in line 51) and the mask()
function to hide a password that may have been previously revealed in the current listbox entry. Then, ScrollUp()
(line 36) tells the listbox to scroll up one item, and the subsequent Render
command smoothly displays the change in the UI. The same applies to pressing J, which lets the user scroll down through the list of entries.
Line 44 intercepts presses of the Enter key and calls the showCur()
function defined in line 55. The function fetches the original unmasked password entry from the lines
list and replaces the currently selected line of the listbox with it. And, hey presto, account and password are displayed on the screen in clear text. hideCur()
starting in line 51 does the opposite and hides the current entry using the mask()
function when the user moves away.
Installation
As always, the binary can be generated from the Go code using the typical three-step process (Listing 6). This process fetches all the dependent libraries from GitHub, compiles them, and binds everything together to create the finished pv
binary. You can then copy this to any target computer with a similar architecture. It'll run there without complaints, and it also conveniently even conjures up the UI into the terminal on remote machines. You will want to copy the test.age
password file to a file in your home directory for production operation; the password reminder is then ready for use.
Listing 6
Compiling the Program
$ go mod init pv $ go mod tidy $ go build pv.go crypto.go util.go ui.go
Infos
- Age: https://github.com/FiloSottile/age
- "What did Ken Thompson mean when he said, 'I'd spell creat with an "e".'?": https://unix.stackexchange.com/questions/10893/what-did-ken-thompson-mean-when-he-vsaid-id-spell-creat-with-an-e
« Previous 1 2 3
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Gnome 47.1 Released with a Few Fixes
The latest release of the Gnome desktop is all about fixing a few nagging issues and not about bringing new features into the mix.
-
System76 Unveils an Ampere-Powered Thelio Desktop
If you're looking for a new desktop system for developing autonomous driving and software-defined vehicle solutions. System76 has you covered.
-
VirtualBox 7.1.4 Includes Initial Support for Linux kernel 6.12
The latest version of VirtualBox has arrived and it not only adds initial support for kernel 6.12 but another feature that will make using the virtual machine tool much easier.
-
New Slimbook EVO with Raw AMD Ryzen Power
If you're looking for serious power in a 14" ultrabook that is powered by Linux, Slimbook has just the thing for you.
-
The Gnome Foundation Struggling to Stay Afloat
The foundation behind the Gnome desktop environment is having to go through some serious belt-tightening due to continued financial problems.
-
Thousands of Linux Servers Infected with Stealth Malware Since 2021
Perfctl is capable of remaining undetected, which makes it dangerous and hard to mitigate.
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.