From 444c8d2d808362cb8c7ce234c0f01a45db1acb06 Mon Sep 17 00:00:00 2001 From: tjblackheart Date: Tue, 24 Feb 2026 08:29:50 +0100 Subject: [PATCH] refactor: simplify vault type constants and improve config struct - Rename TYPE_* constants to match vault package naming (ANDOTP, AEGIS, etc.) - Use typed vaults.Type instead of string for Config.Type field - Rename receiver variable from 'c' to 'cfg' for consistency --- cmd/andcli/main.go | 12 ++--- internal/config/config.go | 56 ++++++++++++------------ internal/config/config_test.go | 16 ++++--- internal/config/flags.go | 9 +--- internal/vaults/aegis/aegis.go | 27 ++++++------ internal/vaults/aegis/aegis_test.go | 2 +- internal/vaults/andotp/andotp.go | 25 +++++------ internal/vaults/andotp/andotp_test.go | 2 +- internal/vaults/entry.go | 8 ++-- internal/vaults/keepass/keepass.go | 34 +++++++------- internal/vaults/keepass/keepass_test.go | 4 +- internal/vaults/protonpass/protonpass.go | 20 ++++----- internal/vaults/stratum/stratum.go | 25 +++++------ internal/vaults/stratum/stratum_test.go | 3 +- internal/vaults/twofas/twofas.go | 27 ++++++------ internal/vaults/twofas/twofas_test.go | 2 +- internal/vaults/vault.go | 48 +++++++++++++------- internal/vaults/vault_test.go | 22 +++++----- 18 files changed, 172 insertions(+), 170 deletions(-) diff --git a/cmd/andcli/main.go b/cmd/andcli/main.go index 86d728e..e4cc7c3 100644 --- a/cmd/andcli/main.go +++ b/cmd/andcli/main.go @@ -59,17 +59,17 @@ func open(cfg *config.Config) (vaults.Vault, error) { } switch cfg.Type { - case vaults.TYPE_ANDOTP: + case vaults.ANDOTP: return andotp.Open(cfg.File, b) - case vaults.TYPE_AEGIS: + case vaults.AEGIS: return aegis.Open(cfg.File, b) - case vaults.TYPE_TWOFAS: + case vaults.TWOFAS: return twofas.Open(cfg.File, b) - case vaults.TYPE_STRATUM: + case vaults.STRATUM: return stratum.Open(cfg.File, b) - case vaults.TYPE_KEEPASS: + case vaults.KEEPASS: return keepass.Open(cfg.File, b) - case vaults.TYPE_PROTON: + case vaults.PROTON: return protonpass.Open(cfg.File, b) } diff --git a/internal/config/config.go b/internal/config/config.go index c3f3c7d..46d8b5e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,14 +10,15 @@ import ( "github.com/goccy/go-yaml" "github.com/tjblackheart/andcli/v2/internal/buildinfo" + "github.com/tjblackheart/andcli/v2/internal/vaults" ) type ( Config struct { - File string `yaml:"file"` - Type string `yaml:"type"` - ClipboardCmd string `yaml:"clipboard_cmd"` - Options *Opts `yaml:"options"` + File string `yaml:"file"` + Type vaults.Type `yaml:"type"` + ClipboardCmd string `yaml:"clipboard_cmd"` + Options *Opts `yaml:"options"` // path string passwordFromStdin bool @@ -70,81 +71,80 @@ func create(dir string) (*Config, error) { } // Writes the current configuration to a yaml file. -func (c Config) Persist() error { - b, err := yaml.Marshal(c) +func (cfg Config) Persist() error { + b, err := yaml.Marshal(cfg) if err != nil { return err } - return os.WriteFile(c.path, b, 0600) + return os.WriteFile(cfg.path, b, 0o600) } // Returns true if the flag option "passwd-stdin" was set. -func (c Config) PasswdStdin() bool { - return c.passwordFromStdin +func (cfg Config) PasswdStdin() bool { + return cfg.passwordFromStdin } // Reads an possibly existing config file and merges the content // into the current config. -func (c *Config) mergeExisting() error { - if _, err := os.Stat(c.path); os.IsNotExist(err) { +func (cfg *Config) mergeExisting() error { + if _, err := os.Stat(cfg.path); os.IsNotExist(err) { return nil } - b, err := os.ReadFile(c.path) + b, err := os.ReadFile(cfg.path) if err != nil { return err } - var existing = new(Config) + existing := new(Config) if err := yaml.Unmarshal(b, existing); err != nil { return err } - c.File = existing.File - c.Type = existing.Type - c.ClipboardCmd = existing.ClipboardCmd + cfg.File = existing.File + cfg.Type = existing.Type + cfg.ClipboardCmd = existing.ClipboardCmd if existing.Options != nil { - c.Options = existing.Options + cfg.Options = existing.Options } return nil } // Validates the current configuration. -func (c *Config) validate() error { - - if c.File == "" { +func (cfg *Config) validate() error { + if cfg.File == "" { return errors.New("no vault file specified") } - if c.Type == "" { + if cfg.Type == "" { return errors.New("no vault type specified") } var err error - if c.File, err = filepath.Abs(c.File); err != nil { - return fmt.Errorf("%s: %s", c.File, err) + if cfg.File, err = filepath.Abs(cfg.File); err != nil { + return fmt.Errorf("%s: %s", cfg.File, err) } - fi, err := os.Stat(c.File) + fi, err := os.Stat(cfg.File) if err != nil { - return fmt.Errorf("%s: %s", c.File, err) + return fmt.Errorf("%s: %s", cfg.File, err) } if fi.IsDir() { - return fmt.Errorf("%s: is a directory, not a vault file", c.File) + return fmt.Errorf("%s: is a directory, not a vault file", cfg.File) } // if set, check if the basic clipboard cmd is available in system PATH. // the option parsing is done at a later time. - if parts := strings.SplitN(c.ClipboardCmd, " ", 2); parts[0] != "" { + if parts := strings.SplitN(cfg.ClipboardCmd, " ", 2); parts[0] != "" { path, err := exec.LookPath(parts[0]) if err != nil { return fmt.Errorf("%s: %s", parts[0], err) } - c.ClipboardCmd = strings.ReplaceAll(c.ClipboardCmd, parts[0], path) + cfg.ClipboardCmd = strings.ReplaceAll(cfg.ClipboardCmd, parts[0], path) } return nil diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 70c3bc5..6fa5477 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -9,6 +9,7 @@ import ( "github.com/goccy/go-yaml" "github.com/tjblackheart/andcli/v2/internal/buildinfo" + "github.com/tjblackheart/andcli/v2/internal/vaults" ) func TestConfig_mergeExisting(t *testing.T) { @@ -49,10 +50,11 @@ func TestConfig_mergeExisting(t *testing.T) { }, { "handles custom options", - &Config{Options: &Opts{ - ShowUsernames: true, - ShowTokens: true, - }, + &Config{ + Options: &Opts{ + ShowUsernames: true, + ShowTokens: true, + }, path: path, }, &Config{ @@ -77,7 +79,7 @@ func TestConfig_mergeExisting(t *testing.T) { t.Fatal(err) } - if err := os.WriteFile(tt.want.path, b, 0644); err != nil { + if err := os.WriteFile(tt.want.path, b, 0o644); err != nil { t.Fatal(err) } } else { @@ -102,7 +104,7 @@ func TestConfig_validate(t *testing.T) { path := filepath.Join(os.TempDir(), "andcli_test.json") defer os.Remove(path) - os.WriteFile(path, nil, 0644) + os.WriteFile(path, nil, 0o644) tests := []struct { name string @@ -210,7 +212,7 @@ func Test_create(t *testing.T) { // default config want := &Config{ File: abs, - Type: *vtype, + Type: vaults.Type(*vtype), ClipboardCmd: "", Options: &Opts{ ShowUsernames: true, diff --git a/internal/config/flags.go b/internal/config/flags.go index 37bcb01..9da91a3 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -5,7 +5,6 @@ import ( "log" "os" "path/filepath" - "strings" flag "github.com/spf13/pflag" "github.com/tjblackheart/andcli/v2/internal/buildinfo" @@ -13,11 +12,9 @@ import ( ) var ( - availableVaults = strings.Join(vaults.Types(), ", ") - set = flag.NewFlagSet("default", flag.ExitOnError) vfile = set.StringP("file", "f", "", "Path to the encrypted vault (deprecated: Pass the filename directly)") - vtype = set.StringP("type", "t", "", fmt.Sprintf("Vault type (%s)", availableVaults)) + vtype = set.StringP("type", "t", "", fmt.Sprintf("Vault type (%s)", vaults.StrTypes())) cmd = set.StringP("clipboard-cmd", "c", "", "A custom clipboard command, including args (xclip, wl-copy, pbcopy etc.)") pwstdin = set.Bool("passwd-stdin", false, "Read the vault password from stdin. If set, skips the password input.") version = set.BoolP("version", "v", false, "Prints version info and exits") @@ -26,7 +23,6 @@ var ( // Parses given flags into the existing config. func (cfg *Config) parseFlags() error { - set.Usage = func() { usage(true) } if err := set.Parse(os.Args[1:]); err != nil { @@ -54,7 +50,7 @@ func (cfg *Config) parseFlags() error { } if *vtype != "" { - cfg.Type = *vtype + cfg.Type = vaults.Type(*vtype) } if *cmd != "" { @@ -78,7 +74,6 @@ func (cfg *Config) parseFlags() error { // prints custom formatted usage information func usage(includeDescription bool) { - if includeDescription { fmt.Println("andcli - A 2FA TUI for your shell") } diff --git a/internal/vaults/aegis/aegis.go b/internal/vaults/aegis/aegis.go index 04674bc..27c39b9 100644 --- a/internal/vaults/aegis/aegis.go +++ b/internal/vaults/aegis/aegis.go @@ -15,10 +15,12 @@ import ( "golang.org/x/crypto/scrypt" ) -const t = vaults.TYPE_AEGIS +const vaultType = vaults.AEGIS + +var _ vaults.Vault = &aegis{} type ( - vault struct { + aegis struct { Version int Header struct { Slots []struct { @@ -57,37 +59,35 @@ type ( ) func Open(filename string, pass []byte) (vaults.Vault, error) { - - var v vault + var v aegis b, err := os.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(b, &v); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } key, err := v.masterKeyFromPass(pass) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } b, err = v.decryptDB(key) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(b, &v.db); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } return v, nil } -func (v vault) Entries() []vaults.Entry { - +func (v aegis) Entries() []vaults.Entry { entries := make([]vaults.Entry, 0) for _, e := range v.db.Entries { @@ -109,7 +109,7 @@ func (v vault) Entries() []vaults.Entry { return entries } -func (v vault) masterKeyFromPass(password []byte) ([]byte, error) { +func (v aegis) masterKeyFromPass(password []byte) ([]byte, error) { var salt, keyNonce, keyTag, key, derivedKey []byte var err error @@ -166,8 +166,7 @@ func (v vault) masterKeyFromPass(password []byte) ([]byte, error) { return masterKey, nil } -func (v vault) decryptDB(key []byte) ([]byte, error) { - +func (v aegis) decryptDB(key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err diff --git a/internal/vaults/aegis/aegis_test.go b/internal/vaults/aegis/aegis_test.go index ec2d4d2..c30a793 100644 --- a/internal/vaults/aegis/aegis_test.go +++ b/internal/vaults/aegis/aegis_test.go @@ -66,7 +66,7 @@ func TestEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - entries := (&vault{db: db{Entries: tt.input}}).Entries() + entries := (&aegis{db: db{Entries: tt.input}}).Entries() if !reflect.DeepEqual(entries, tt.want) { t.Fatalf("Entries(): want %#v\nhave %#v", tt.want, entries) } diff --git a/internal/vaults/andotp/andotp.go b/internal/vaults/andotp/andotp.go index 3292228..49161fc 100644 --- a/internal/vaults/andotp/andotp.go +++ b/internal/vaults/andotp/andotp.go @@ -6,16 +6,16 @@ import ( "os" "strings" - "github.com/grijul/go-andotp/andotp" + gao "github.com/grijul/go-andotp/andotp" "github.com/tjblackheart/andcli/v2/internal/vaults" ) -const t = vaults.TYPE_ANDOTP +const vaultType = vaults.ANDOTP + +var _ vaults.Vault = &andotp{} type ( - vault struct { - entries []entry - } + andotp struct{ entries []entry } entry struct { Secret string @@ -33,28 +33,25 @@ type ( ) func Open(filename string, pass []byte) (vaults.Vault, error) { - b, err := os.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } - b, err = andotp.Decrypt(b, string(pass)) + b, err = gao.Decrypt(b, string(pass)) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } entries := make([]entry, 0) if err := json.Unmarshal(b, &entries); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } - return &vault{entries}, nil - + return &andotp{entries}, nil } -func (v vault) Entries() []vaults.Entry { - +func (v andotp) Entries() []vaults.Entry { entries := make([]vaults.Entry, 0) for _, e := range v.entries { diff --git a/internal/vaults/andotp/andotp_test.go b/internal/vaults/andotp/andotp_test.go index ac1f4a0..bfb7c19 100644 --- a/internal/vaults/andotp/andotp_test.go +++ b/internal/vaults/andotp/andotp_test.go @@ -65,7 +65,7 @@ func TestEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - entries := (&vault{entries: tt.input}).Entries() + entries := (&andotp{entries: tt.input}).Entries() if !reflect.DeepEqual(entries, tt.want) { t.Fatalf("Entries(): want %#v\nhave %#v", tt.want, entries) } diff --git a/internal/vaults/entry.go b/internal/vaults/entry.go index 10f7cf7..a21a793 100644 --- a/internal/vaults/entry.go +++ b/internal/vaults/entry.go @@ -11,7 +11,7 @@ import ( "github.com/xlzd/gotp" ) -// an Entry represents a generic vault entry. +// Entry represents a generic vault entry. type Entry struct { Secret string Issuer string @@ -23,8 +23,10 @@ type Entry struct { Period int } -var ErrMissingSecret = errors.New("missing secret value") -var ErrInvalidType = errors.New("entry is not a TOTP") +var ( + ErrMissingSecret = errors.New("missing secret value") + ErrInvalidType = errors.New("entry is not a TOTP") +) // Returns a generated OTP and expiration time for the current entry. func (e Entry) GenerateTOTP() (string, int64) { diff --git a/internal/vaults/keepass/keepass.go b/internal/vaults/keepass/keepass.go index f6d8767..70ae2da 100644 --- a/internal/vaults/keepass/keepass.go +++ b/internal/vaults/keepass/keepass.go @@ -9,37 +9,36 @@ import ( "strings" "github.com/tjblackheart/andcli/v2/internal/vaults" - keepass "github.com/tobischo/gokeepasslib/v3" + "github.com/tobischo/gokeepasslib/v3" ) -const t = vaults.TYPE_KEEPASS +const vaultType = vaults.KEEPASS -type vault struct { - entries []keepass.Entry -} +var _ vaults.Vault = &keepass{} -func Open(filename string, pass []byte) (vaults.Vault, error) { +type keepass struct{ entries []gokeepasslib.Entry } - var v = vault{entries: make([]keepass.Entry, 0)} +func Open(filename string, pass []byte) (vaults.Vault, error) { + v := keepass{entries: make([]gokeepasslib.Entry, 0)} file, err := os.Open(filename) if err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } - db := keepass.NewDatabase() - db.Credentials = keepass.NewPasswordCredentials(string(pass)) + db := gokeepasslib.NewDatabase() + db.Credentials = gokeepasslib.NewPasswordCredentials(string(pass)) - if err := keepass.NewDecoder(file).Decode(db); err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + if err := gokeepasslib.NewDecoder(file).Decode(db); err != nil { + return nil, fmt.Errorf("%s: %s", vaultType, err) } if err := db.UnlockProtectedEntries(); err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } if len(db.Content.Root.Groups) == 0 { - return nil, fmt.Errorf("%s: no content", t) + return nil, fmt.Errorf("%s: no content", vaultType) } v.entries = append(v.entries, parseGroups(db.Content.Root.Groups)...) @@ -47,8 +46,7 @@ func Open(filename string, pass []byte) (vaults.Vault, error) { return v, nil } -func (v vault) Entries() []vaults.Entry { - +func (v keepass) Entries() []vaults.Entry { entries := make([]vaults.Entry, 0) for _, e := range v.entries { issuer := e.GetTitle() @@ -85,8 +83,8 @@ func (v vault) Entries() []vaults.Entry { return entries } -func parseGroups(groups []keepass.Group) []keepass.Entry { - entries := make([]keepass.Entry, 0) +func parseGroups(groups []gokeepasslib.Group) []gokeepasslib.Entry { + entries := make([]gokeepasslib.Entry, 0) for _, group := range groups { entries = append(entries, group.Entries...) entries = append(entries, parseGroups(group.Groups)...) diff --git a/internal/vaults/keepass/keepass_test.go b/internal/vaults/keepass/keepass_test.go index fafdb34..0e4467a 100644 --- a/internal/vaults/keepass/keepass_test.go +++ b/internal/vaults/keepass/keepass_test.go @@ -45,7 +45,6 @@ func TestOpen(t *testing.T) { t.Fatalf("Open() have %v, %s", entries[i].Label, want) } } - }) } } @@ -93,7 +92,7 @@ func TestEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - entries := (&vault{tt.input}).Entries() + entries := (&keepass{tt.input}).Entries() if !reflect.DeepEqual(entries, tt.want) { t.Fatalf("Entries(): want %#v\nhave %#v", tt.want, entries) } @@ -102,7 +101,6 @@ func TestEntries(t *testing.T) { } func Test_parseGroups(t *testing.T) { - tests := []struct { name string groups []gokeepasslib.Group diff --git a/internal/vaults/protonpass/protonpass.go b/internal/vaults/protonpass/protonpass.go index 6b25a82..80b4f3b 100644 --- a/internal/vaults/protonpass/protonpass.go +++ b/internal/vaults/protonpass/protonpass.go @@ -17,13 +17,15 @@ import ( "github.com/tjblackheart/andcli/v2/internal/vaults" ) -const t = vaults.TYPE_PROTON +const vaultType = vaults.PROTON + +var _ vaults.Vault = &envelope{} type ( - envelope struct{ Vaults map[string]vault } + envelope struct{ Vaults map[string]proton } // protonvault only implements the essentials for reading OTP data. - vault struct { + proton struct { Name, Description string Items []struct { Data struct { @@ -39,32 +41,31 @@ type ( ) func Open(filename string, pass []byte) (vaults.Vault, error) { - b, err := read(filename) if err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } hnd, err := crypto.PGP().Decryption().Password(pass).New() if err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } result, err := hnd.Decrypt(b, crypto.Armor) if err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } var e envelope if err := json.Unmarshal(result.Bytes(), &e); err != nil { - return nil, fmt.Errorf("%s: %s", t, err) + return nil, fmt.Errorf("%s: %s", vaultType, err) } return e, nil } func (e envelope) Entries() []vaults.Entry { - var entries = make([]vaults.Entry, 0) + entries := make([]vaults.Entry, 0) for _, v := range e.Vaults { for _, i := range v.Items { @@ -104,7 +105,6 @@ func (e envelope) Entries() []vaults.Entry { // opens, reads and returns file content, handles zip if necessary. func read(filename string) ([]byte, error) { - sig := []byte{0x50, 0x4b, 0x03, 0x04} f, err := os.Open(filename) diff --git a/internal/vaults/stratum/stratum.go b/internal/vaults/stratum/stratum.go index 58330b9..9c57031 100644 --- a/internal/vaults/stratum/stratum.go +++ b/internal/vaults/stratum/stratum.go @@ -13,10 +13,9 @@ import ( "golang.org/x/crypto/argon2" ) -const t = vaults.TYPE_STRATUM +const vaultType = vaults.STRATUM -// force interface impl -var _ vaults.Vault = &vault{} +var _ vaults.Vault = &stratum{} const ( KEY_LENGTH = 32 @@ -38,7 +37,7 @@ const ( ) type ( - vault struct { + stratum struct { AuthenticatorCategories []any // ignored as of now Authenticators []entry } @@ -65,35 +64,34 @@ func Open(filename string, pass []byte) (vaults.Vault, error) { return nil, err } - v := &vault{Authenticators: make([]entry, 0)} + v := &stratum{Authenticators: make([]entry, 0)} switch string(b[:len(HEADER)]) { case HEADER: b, err := v.decrypt(b, pass) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(b, &v); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } case LEGACY_HEADER: b, err := v.decryptLegacy(b, pass) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(b, &v); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } } return v, nil } -func (v vault) Entries() []vaults.Entry { - +func (v stratum) Entries() []vaults.Entry { // https://github.com/stratumauth/app/blob/master/doc/BACKUP_FORMAT.md // Algorithm (applies to HOTP and TOTP): 0 = SHA-1, 1 = SHA-256, 2 = SHA-512 // Type: 1 = HOTP, 2 = TOTP, 3 = Mobile-Otp, 4 = Steam, 5 = Yandex @@ -127,8 +125,7 @@ func (v vault) Entries() []vaults.Entry { return list } -func (v vault) decrypt(b, pass []byte) ([]byte, error) { - +func (v stratum) decrypt(b, pass []byte) ([]byte, error) { salt := b[len(HEADER) : len(HEADER)+SALT_LENGTH] nonce := b[len(HEADER)+SALT_LENGTH : len(HEADER)+SALT_LENGTH+IV_LENGTH] payload := b[len(HEADER)+SALT_LENGTH+IV_LENGTH:] @@ -147,7 +144,7 @@ func (v vault) decrypt(b, pass []byte) ([]byte, error) { return gcm.Open(nil, nonce, payload, nil) } -func (v vault) decryptLegacy(_, _ []byte) ([]byte, error) { +func (v stratum) decryptLegacy(_, _ []byte) ([]byte, error) { return nil, errors.New("decryption of stratum legacy vaults is not implemented") } diff --git a/internal/vaults/stratum/stratum_test.go b/internal/vaults/stratum/stratum_test.go index f9f213c..008bbe3 100644 --- a/internal/vaults/stratum/stratum_test.go +++ b/internal/vaults/stratum/stratum_test.go @@ -45,7 +45,6 @@ func TestOpen(t *testing.T) { t.Fatalf("Open() have %v, %s", entries[i].Label, want) } } - }) } } @@ -75,7 +74,7 @@ func TestEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - entries := (&vault{Authenticators: tt.input}).Entries() + entries := (&stratum{Authenticators: tt.input}).Entries() if !reflect.DeepEqual(entries, tt.want) { t.Fatalf("Entries(): want %#v\nhave %#v", tt.want, entries) } diff --git a/internal/vaults/twofas/twofas.go b/internal/vaults/twofas/twofas.go index b8488a1..0ca831f 100644 --- a/internal/vaults/twofas/twofas.go +++ b/internal/vaults/twofas/twofas.go @@ -15,13 +15,15 @@ import ( ) const ( - t = vaults.TYPE_TWOFAS + vaultType = vaults.TWOFAS numFields int = 3 authTagLength int = 16 ) +var _ vaults.Vault = &twofas{} + type ( - vault struct { + twofas struct { UpdatedAt int SchemaVersion int AppVersionCode int @@ -63,37 +65,35 @@ type ( ) func Open(filename string, pass []byte) (vaults.Vault, error) { - - var v vault + var v twofas b, err := os.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(b, &v); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } key, err := v.masterKeyFromPass(pass) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } plain, err := v.decryptDB(key) if err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } if err := json.Unmarshal(plain, &v.db); err != nil { - return nil, fmt.Errorf("%s: %w", t, err) + return nil, fmt.Errorf("%s: %w", vaultType, err) } return v, nil } -func (v vault) Entries() []vaults.Entry { - +func (v twofas) Entries() []vaults.Entry { entries := make([]vaults.Entry, 0) for _, e := range v.db { @@ -120,8 +120,7 @@ func (v vault) Entries() []vaults.Entry { return entries } -func (v vault) masterKeyFromPass(password []byte) ([]byte, error) { - +func (v twofas) masterKeyFromPass(password []byte) ([]byte, error) { servicesEncrypted := strings.SplitN(v.ServicesEncrypted, ":", numFields+1) if len(servicesEncrypted) != numFields { return nil, fmt.Errorf("invalid vault file: number of fields is not %d", numFields) @@ -148,7 +147,7 @@ func (v vault) masterKeyFromPass(password []byte) ([]byte, error) { return pbkdf2.Key(password, salt, 10000, 32, sha256.New), nil } -func (v vault) decryptDB(key []byte) ([]byte, error) { +func (v twofas) decryptDB(key []byte) ([]byte, error) { servicesEncrypted := strings.SplitN(v.ServicesEncrypted, ":", numFields+1) if len(servicesEncrypted) != numFields { return nil, fmt.Errorf("invalid vault file: number of fields is not %d", numFields) diff --git a/internal/vaults/twofas/twofas_test.go b/internal/vaults/twofas/twofas_test.go index 1e90c1d..e56caf6 100644 --- a/internal/vaults/twofas/twofas_test.go +++ b/internal/vaults/twofas/twofas_test.go @@ -66,7 +66,7 @@ func TestEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - entries := (&vault{db: tt.input}).Entries() + entries := (&twofas{db: tt.input}).Entries() if !reflect.DeepEqual(entries, tt.want) { t.Fatalf("Entries(): want %#v\nhave %#v", tt.want, entries) } diff --git a/internal/vaults/vault.go b/internal/vaults/vault.go index d8db1ef..65cb329 100644 --- a/internal/vaults/vault.go +++ b/internal/vaults/vault.go @@ -1,27 +1,41 @@ package vaults +import "strings" + // Vault is the basic skeleton of a vault implementation. -type Vault interface { - Entries() []Entry -} +type Vault interface{ Entries() []Entry } + +// Type is an implemented vault type name. +type Type string + +func (t Type) String() string { return string(t) } const ( - TYPE_ANDOTP string = "andotp" - TYPE_AEGIS string = "aegis" - TYPE_TWOFAS string = "twofas" - TYPE_STRATUM string = "stratum" - TYPE_KEEPASS string = "keepass" - TYPE_PROTON string = "proton" + ANDOTP Type = "andotp" + AEGIS Type = "aegis" + TWOFAS Type = "twofas" + STRATUM Type = "stratum" + KEEPASS Type = "keepass" + PROTON Type = "proton" ) // Returns a list containing the implemented types. -func Types() []string { - return []string{ - TYPE_ANDOTP, - TYPE_AEGIS, - TYPE_TWOFAS, - TYPE_STRATUM, - TYPE_KEEPASS, - TYPE_PROTON, +func Types() []Type { + return []Type{ + ANDOTP, + AEGIS, + TWOFAS, + STRATUM, + KEEPASS, + PROTON, + } +} + +// StrTypes returns a concatenated string of all defined types. +func StrTypes() string { + var s []string + for _, t := range Types() { + s = append(s, t.String()) } + return strings.Join(s, ", ") } diff --git a/internal/vaults/vault_test.go b/internal/vaults/vault_test.go index 9346e82..3f41399 100644 --- a/internal/vaults/vault_test.go +++ b/internal/vaults/vault_test.go @@ -1,31 +1,33 @@ -package vaults +package vaults_test import ( "reflect" "testing" + + "github.com/tjblackheart/andcli/v2/internal/vaults" ) func TestTypes(t *testing.T) { tests := []struct { name string - want []string + want []vaults.Type }{ { "returns defined types", - []string{ - TYPE_ANDOTP, - TYPE_AEGIS, - TYPE_TWOFAS, - TYPE_STRATUM, - TYPE_KEEPASS, - TYPE_PROTON, + []vaults.Type{ + vaults.ANDOTP, + vaults.AEGIS, + vaults.TWOFAS, + vaults.STRATUM, + vaults.KEEPASS, + vaults.PROTON, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := Types(); !reflect.DeepEqual(got, tt.want) { + if got := vaults.Types(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Types() = %v, want %v", got, tt.want) } })