Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/tui/secure.go

225 lines
4.6 KiB
Go

/*
©AngelaMos | 2026
secure.go
The encryption sub-form: a focusable list of toggle, choice, and secret controls
*/
package tui
import (
"strings"
"github.com/CarterPerez-dev/crypha/internal/payload"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type secField int
const (
secEncrypt secField = iota
secPass
secCipher
secStrength
secCompress
)
var (
cipherChoices = []string{string(payload.CipherChaCha20), string(payload.CipherAES256GCM)}
strengthChoices = []string{string(payload.StrengthDefault), string(payload.StrengthHigh)}
)
type secureForm struct {
encrypt bool
pass textinput.Model
cipher int
strength int
compress bool
focus int
}
func newSecureForm() secureForm {
ti := textinput.New()
ti.Prompt = ""
ti.Placeholder = "passphrase"
ti.EchoMode = textinput.EchoPassword
ti.CharLimit = passLimit
ti.Width = passFieldWidth
return secureForm{pass: ti}
}
func (f secureForm) fields() []secField {
if f.encrypt {
return []secField{secEncrypt, secPass, secCipher, secStrength, secCompress}
}
return []secField{secEncrypt, secCompress}
}
func (f secureForm) focusField() secField {
fs := f.fields()
i := f.focus
if i < 0 {
i = 0
}
if i >= len(fs) {
i = len(fs) - 1
}
return fs[i]
}
func (f *secureForm) move(delta int) {
n := len(f.fields())
f.focus += delta
if f.focus < 0 {
f.focus = 0
}
if f.focus >= n {
f.focus = n - 1
}
f.syncFocus()
}
func (f *secureForm) syncFocus() tea.Cmd {
if f.focusField() == secPass {
return f.pass.Focus()
}
f.pass.Blur()
return nil
}
func (f secureForm) cipherValue() string {
return cipherChoices[f.cipher]
}
func (f secureForm) strengthValue() string {
return strengthChoices[f.strength]
}
func (f secureForm) passphrase() []byte {
if !f.encrypt {
return nil
}
v := f.pass.Value()
if v == "" {
return nil
}
return []byte(v)
}
func (f secureForm) update(msg tea.Msg) (secureForm, tea.Cmd) {
km, ok := msg.(tea.KeyMsg)
if !ok {
var cmd tea.Cmd
f.pass, cmd = f.pass.Update(msg)
return f, cmd
}
switch km.String() {
case "up", "shift+tab":
cmd := f.moveWith(-1)
return f, cmd
case "down", "tab":
cmd := f.moveWith(1)
return f, cmd
}
switch f.focusField() {
case secEncrypt:
if isToggleKey(km) {
f.encrypt = !f.encrypt
f.syncFocus()
}
case secCompress:
if isToggleKey(km) {
f.compress = !f.compress
}
case secCipher:
f.cipher = cycleChoice(f.cipher, len(cipherChoices), km)
case secStrength:
f.strength = cycleChoice(f.strength, len(strengthChoices), km)
case secPass:
var cmd tea.Cmd
f.pass, cmd = f.pass.Update(msg)
return f, cmd
}
return f, nil
}
func (f *secureForm) moveWith(delta int) tea.Cmd {
f.move(delta)
return f.syncFocus()
}
func isToggleKey(km tea.KeyMsg) bool {
switch km.String() {
case " ", "left", "right":
return true
}
return false
}
func cycleChoice(idx, n int, km tea.KeyMsg) int {
switch km.String() {
case "left":
return (idx - 1 + n) % n
case "right", " ":
return (idx + 1) % n
}
return idx
}
func (f secureForm) view(width int) string {
rows := make([]string, 0, len(f.fields()))
for i, fld := range f.fields() {
rows = append(rows, f.fieldRow(fld, i == f.focus))
}
note := styleHint.Width(width).Render("up/down move · space or left/right change · enter continues")
return lipgloss.JoinVertical(lipgloss.Left, lipgloss.JoinVertical(lipgloss.Left, rows...), "", note)
}
func (f secureForm) fieldRow(fld secField, focused bool) string {
prefix := " "
labelStyle := styleLabel
if focused {
prefix = styleCursor.Render(accentBar) + " "
labelStyle = styleValueBold
}
label, control := f.labelControl(fld)
return prefix + labelStyle.Width(labelWidth).Render(label) + control
}
func (f secureForm) labelControl(fld secField) (string, string) {
switch fld {
case secEncrypt:
return "encrypt", toggleControl(f.encrypt)
case secPass:
return "passphrase", f.pass.View()
case secCipher:
return "cipher", choiceControl(cipherChoices, f.cipher)
case secStrength:
return "key strength", choiceControl(strengthChoices, f.strength)
case secCompress:
return "compress", toggleControl(f.compress)
}
return "", ""
}
func toggleControl(on bool) string {
return pill("off", !on) + " " + pill("on", on)
}
func choiceControl(choices []string, idx int) string {
parts := make([]string, len(choices))
for i, c := range choices {
parts[i] = pill(c, i == idx)
}
return strings.Join(parts, " ")
}
func pill(text string, active bool) string {
if active {
return badge(text, hexViolet)
}
return lipgloss.NewStyle().Foreground(lipgloss.Color(hexFaint)).Padding(0, 1).Render(text)
}