From c933393ac5a8f6582bbc423ee3adac03524cecdf Mon Sep 17 00:00:00 2001 From: Louis Dalibard Date: Sun, 28 Apr 2024 21:35:37 +0200 Subject: [PATCH] init: first commit --- .gitignore | 3 ++ config/config.go | 13 +++++++ go.mod | 27 +++++++++++++ go.sum | 48 +++++++++++++++++++++++ main.go | 63 ++++++++++++++++++++++++++++++ totp/totp.go | 22 +++++++++++ ui/components/components.go | 59 ++++++++++++++++++++++++++++ ui/styling/styling.go | 78 +++++++++++++++++++++++++++++++++++++ ui/ui.go | 1 + utils/utils.go | 9 +++++ 10 files changed, 323 insertions(+) create mode 100644 config/config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 totp/totp.go create mode 100644 ui/components/components.go create mode 100644 ui/styling/styling.go create mode 100644 ui/ui.go create mode 100644 utils/utils.go diff --git a/.gitignore b/.gitignore index adf8f72..51e9063 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # Go workspace file go.work +# Bin +himitsu + diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..8a29ae8 --- /dev/null +++ b/config/config.go @@ -0,0 +1,13 @@ +package config + +import "himitsu/totp" + +const Version = "v0.0.1" + +var Config = []totp.TOTP{ + { + Label: "Test", + Secret: "Test", + Account: "Test", + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b3b3e0d --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module himitsu + +go 1.22.2 + +require ( + github.com/charmbracelet/bubbletea v0.25.0 + github.com/lucasb-eyer/go-colorful v1.2.0 + github.com/muesli/termenv v0.15.2 + github.com/pquerna/otp v1.4.0 +) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/rivo/uniseg v0.4.6 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fffe4f6 --- /dev/null +++ b/go.sum @@ -0,0 +1,48 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= +github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= +github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a567e9f --- /dev/null +++ b/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + "himitsu/config" + "himitsu/ui/components" + "log" + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +func main() { + p := tea.NewProgram(initialModel()) + if _, err := p.Run(); err != nil { + log.Fatal(err) + } +} + +type ( + tickMsg struct{} +) + +func tick() tea.Cmd { + return tea.Tick(time.Second, func(time.Time) tea.Msg { + return tickMsg{} + }) +} + +type model struct { +} + +func initialModel() model { + return model{} +} + +func (m model) Init() tea.Cmd { + return tick() +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + case tickMsg: + return m, tick() + } + return m, cmd +} + +func (m model) View() string { + tpl := components.VersionNumber() + "\n\n" + for _, totp := range config.Config { + tpl += components.TOTP(totp) + } + tpl += "\n" + components.KeybindsHints([]string{"esc: quit"}) + "\n" + return fmt.Sprintf(tpl) +} diff --git a/totp/totp.go b/totp/totp.go new file mode 100644 index 0000000..fca571c --- /dev/null +++ b/totp/totp.go @@ -0,0 +1,22 @@ +package totp + +import ( + "himitsu/utils" + "strings" + "time" + + totpLib "github.com/pquerna/otp/totp" +) + +type TOTP struct { + Label string + Secret string + Account string +} + +func GetCode(secret string) string { + code, err := totpLib.GenerateCode(strings.ToUpper(strings.ReplaceAll(secret, " ", "")), time.Now()) + utils.CheckError(err) + return code + +} diff --git a/ui/components/components.go b/ui/components/components.go new file mode 100644 index 0000000..1035e14 --- /dev/null +++ b/ui/components/components.go @@ -0,0 +1,59 @@ +package components + +import ( + "fmt" + "himitsu/config" + "himitsu/totp" + "himitsu/ui/styling" + "math" + "strings" + "time" + + "github.com/muesli/termenv" +) + +func VersionNumber() string { + return "himitsu " + styling.ColorFg(config.Version, styling.HighlightedColor) +} + +func Checkbox(label string, checked bool, selected bool) string { + s := fmt.Sprintf("[ ] %s", label) + if checked { + s = "[x] " + label + } + if selected { + return styling.ColorFg(s, styling.HighlightedColor) + } + return s +} + +func Progressbar(width int, percent float64) string { + w := float64(width) + + fullSize := int(math.Round(w * percent)) + var fullCells string + for i := 0; i < fullSize; i++ { + fullCells += termenv.String(styling.ProgressFullChar).Foreground(styling.Term.Color(styling.Ramp[i])).String() + } + + emptySize := int(w) - fullSize + emptyCells := strings.Repeat(styling.ProgressEmpty, emptySize) + + return fmt.Sprintf("%s%s %02ds", fullCells, emptyCells, 30-int(math.Round(percent*30))) +} + +func TOTP(currTotp totp.TOTP) string { + s := fmt.Sprintf("╔═══════════════════════════════════════════════════════════════════╗\n║ %s %-25.25s %s %s ║\n╚═══════════════════════════════════════════════════════════════════╝\n", styling.ColorFg(fmt.Sprintf("%-15.15s", currTotp.Label), styling.HighlightedColor), currTotp.Account, totp.GetCode(currTotp.Secret), Progressbar(10, float64(time.Now().UnixMilli()%30000)/30000.)) + return s +} + +func KeybindsHints(keybinds []string) string { + s := "" + for index, keybind := range keybinds { + if index != 0 { + s += styling.Dot + } + s += styling.Subtle(keybind) + } + return s +} diff --git a/ui/styling/styling.go b/ui/styling/styling.go new file mode 100644 index 0000000..645ba5f --- /dev/null +++ b/ui/styling/styling.go @@ -0,0 +1,78 @@ +package styling + +import ( + "fmt" + "strconv" + + "github.com/lucasb-eyer/go-colorful" + "github.com/muesli/termenv" +) + +const ( + HighlightedColor = "212" + SecondaryColor = "225" +) + +const ( + ProgressBarWidth = 71 + ProgressFullChar = "█" + ProgressEmptyChar = "░" +) + +var ( + Term = termenv.EnvColorProfile() + Keyword = MakeFgStyle("211") + Subtle = MakeFgStyle("241") + ProgressEmpty = Subtle(ProgressEmptyChar) + Dot = ColorFg(" • ", "236") + + // Gradient colors we'll use for the progress bar + Ramp = MakeRamp("#B14FFF", "#00FFA3", ProgressBarWidth) +) + +// Utils + +// Color a string's foreground with the given value. +func ColorFg(val, color string) string { + return termenv.String(val).Foreground(Term.Color(color)).String() +} + +// Return a function that will colorize the foreground of a given string. +func MakeFgStyle(color string) func(string) string { + return termenv.Style{}.Foreground(Term.Color(color)).Styled +} + +// Color a string's foreground and background with the given value. +func MakeFgBgStyle(fg, bg string) func(string) string { + return termenv.Style{}. + Foreground(Term.Color(fg)). + Background(Term.Color(bg)). + Styled +} + +// Generate a blend of colors. +func MakeRamp(colorA, colorB string, steps float64) (s []string) { + cA, _ := colorful.Hex(colorA) + cB, _ := colorful.Hex(colorB) + + for i := 0.0; i < steps; i++ { + c := cA.BlendLuv(cB, i/steps) + s = append(s, ColorToHex(c)) + } + return +} + +// Convert a colorful.Color to a hexadecimal format compatible with termenv. +func ColorToHex(c colorful.Color) string { + return fmt.Sprintf("#%s%s%s", ColorFloatToHex(c.R), ColorFloatToHex(c.G), ColorFloatToHex(c.B)) +} + +// Helper function for converting colors to hex. Assumes a value between 0 and +// 1. +func ColorFloatToHex(f float64) (s string) { + s = strconv.FormatInt(int64(f*255), 16) + if len(s) == 1 { + s = "0" + s + } + return +} diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 0000000..5b1faa2 --- /dev/null +++ b/ui/ui.go @@ -0,0 +1 @@ +package ui diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..62ea048 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,9 @@ +package utils + +import "log" + +func CheckError(err error) { + if err != nil { + log.Fatal(err) + } +}