internal/cli drops its copies of vault.ErrSecretNotFound, ErrVaultNotFound, ErrVersionNotFound and ErrSecretExists and of the secret package's keychain and Secure Enclave errors, and its second error for an unknown unlocker type, an invalid mnemonic, a length below 1, an unsupported secret type and an oversized secret. vault.ErrNilValueBuffer becomes secret.ErrNilValueBuffer. Every error of secret.ReadPassphrase wraps ErrPassphraseNotRead. ResolveGPGKeyFingerprint returns ErrGPGKeyNotFound for a key the keyring lacks. storeInKeychain returns errNilDataBuffer. bip85's ErrPasswordTooShort and ErrEncodedTooShort go with their unreachable checks, as does the macOS check in macOS-only code. Tests that matched these errors' text use errors.Is. Model: opus-5-5
This commit is contained in:
@@ -188,7 +188,7 @@ func TestStopAtPassphrasePromptLeavesNothing(t *testing.T) {
|
||||
|
||||
err := tt.run(c)
|
||||
|
||||
require.ErrorContains(t, err, "failed to read passphrase")
|
||||
require.ErrorIs(t, err, secret.ErrPassphraseNotRead)
|
||||
require.Equal(t, before, snapshotStateDir(t, tt.fs))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
var (
|
||||
errNotAgeSecretKey = errors.New(
|
||||
"does not contain a valid age secret key")
|
||||
errSecretDoesNotExist = errors.New("does not exist")
|
||||
)
|
||||
|
||||
// newCryptoCmd builds an encrypt/decrypt command with input/output flags
|
||||
@@ -245,7 +244,7 @@ func (cli *Instance) Decrypt(secretName, inputFile, outputFile string) error {
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("secret '%s' %w", secretName, errSecretDoesNotExist)
|
||||
return fmt.Errorf("secret '%s' %w", secretName, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// Get the age secret key from the secret
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/cli"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TestMissingSecretOrVaultErrors checks that a command that finds no such
|
||||
// secret or vault returns the vault package's error for it, as `secret get`
|
||||
// does, and leaves the vaults unchanged. "default" is the current vault, and
|
||||
// both vaults hold the secret "x".
|
||||
func TestMissingSecretOrVaultErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
before := snapshotStateDir(t, newTwoVaultFs(t))
|
||||
|
||||
tests := []struct {
|
||||
command string
|
||||
want error
|
||||
run func(c *cli.Instance) error
|
||||
}{
|
||||
{
|
||||
"rm --force nosuch", vault.ErrSecretNotFound,
|
||||
func(c *cli.Instance) error {
|
||||
return c.RemoveSecret(&cobra.Command{}, "nosuch", true)
|
||||
},
|
||||
},
|
||||
{
|
||||
"version rm --force nosuch", vault.ErrSecretNotFound,
|
||||
func(c *cli.Instance) error {
|
||||
return c.RemoveVersion(&cobra.Command{}, "nosuch", "20260101.001", true)
|
||||
},
|
||||
},
|
||||
{
|
||||
"mv --force work:nosuch default", vault.ErrSecretNotFound,
|
||||
func(c *cli.Instance) error {
|
||||
return c.MoveSecret(&cobra.Command{}, "work:nosuch", "default", true)
|
||||
},
|
||||
},
|
||||
{
|
||||
"decrypt nosuch", vault.ErrSecretNotFound,
|
||||
func(c *cli.Instance) error { return c.Decrypt("nosuch", "", "") },
|
||||
},
|
||||
{
|
||||
"vault rm --force nosuch", vault.ErrVaultNotFound,
|
||||
func(c *cli.Instance) error {
|
||||
return c.RemoveVault(&cobra.Command{}, "nosuch", true)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.command, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
requireRejectedAndUnchanged(t, before, tt.want, tt.run)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,7 @@ const (
|
||||
|
||||
// Sentinel errors for secret generation
|
||||
var (
|
||||
errLengthTooSmall = errors.New("length must be at least 1")
|
||||
errLengthNotPositive = errors.New("length must be positive")
|
||||
errMnemonicTypeNotSupported = errors.New(
|
||||
"mnemonic type not supported for secret generation, " +
|
||||
"use 'secret generate mnemonic' instead")
|
||||
errLengthTooSmall = errors.New("length must be at least 1")
|
||||
errUnsupportedSecretType = errors.New("unsupported type")
|
||||
)
|
||||
|
||||
@@ -148,7 +144,8 @@ func (cli *Instance) GenerateSecret(
|
||||
case "alnum":
|
||||
secretValue, err = generateRandomAlnum(length)
|
||||
case "mnemonic":
|
||||
return errMnemonicTypeNotSupported
|
||||
return fmt.Errorf("%w: mnemonic (use 'secret generate mnemonic' instead)",
|
||||
errUnsupportedSecretType)
|
||||
default:
|
||||
return fmt.Errorf("%w: %s (supported: base58, alnum)",
|
||||
errUnsupportedSecretType, secretType)
|
||||
@@ -204,8 +201,8 @@ func generateRandomAlnum(length int) (string, error) {
|
||||
// generateRandomString generates a random string of the specified length
|
||||
// using the given character set
|
||||
func generateRandomString(length int, charset string) (string, error) {
|
||||
if length <= 0 {
|
||||
return "", errLengthNotPositive
|
||||
if length < 1 {
|
||||
return "", errLengthTooSmall
|
||||
}
|
||||
|
||||
result := make([]byte, length)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//nolint:testpackage // white-box test of unexported internals
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestInvalidMnemonicError checks that every command that takes a mnemonic
|
||||
// returns errInvalidMnemonicPhrase for one that is not valid BIP39. The vault
|
||||
// "other" has no long-term key, as vault import needs.
|
||||
func TestInvalidMnemonicError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
command string
|
||||
run func(c *Instance) error
|
||||
}{
|
||||
{"secret init", func(c *Instance) error { return c.Init(c.cmd) }},
|
||||
{"secret vault create work", func(c *Instance) error {
|
||||
return c.CreateVault(c.cmd, "work")
|
||||
}},
|
||||
{"secret vault import other", func(c *Instance) error {
|
||||
return c.VaultImport(c.cmd, "other")
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.command, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
_, err := vault.CreateVault(fs, listTestStateDir, "other", nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
instance, _ := newTestInstance(fs)
|
||||
instance.Mnemonic = memguard.NewBufferFromBytes([]byte("not a mnemonic"))
|
||||
t.Cleanup(instance.Mnemonic.Destroy)
|
||||
|
||||
require.ErrorIs(t, tt.run(instance), errInvalidMnemonicPhrase)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateSecretErrors checks that `secret generate secret` gives one
|
||||
// error for a length below 1 and one for a type it cannot generate.
|
||||
func TestGenerateSecretErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
instance, cmd := newTestInstance(afero.NewMemMapFs())
|
||||
|
||||
err := instance.GenerateSecret(cmd, "x", 0, "base58", false)
|
||||
require.ErrorIs(t, err, errLengthTooSmall)
|
||||
|
||||
_, err = generateRandomString(0, "ab")
|
||||
require.ErrorIs(t, err, errLengthTooSmall)
|
||||
|
||||
err = instance.GenerateSecret(cmd, "x", defaultSecretLength, "mnemonic", false)
|
||||
require.ErrorIs(t, err, errUnsupportedSecretType)
|
||||
|
||||
err = instance.GenerateSecret(cmd, "x", defaultSecretLength, "hex", false)
|
||||
require.ErrorIs(t, err, errUnsupportedSecretType)
|
||||
}
|
||||
@@ -1216,9 +1216,8 @@ func test12bMoveSecret(t *testing.T, testMnemonic string, runSecret func(...stri
|
||||
|
||||
// Test error cases
|
||||
// Try to move non-existent secret
|
||||
output, err = runSecret("move", "test/nonexistent", "test/destination")
|
||||
require.Error(t, err, "move non-existent should fail")
|
||||
assert.Contains(t, output, "not found", "should indicate source not found")
|
||||
_, err = runSecret("move", "test/nonexistent", "test/destination")
|
||||
require.ErrorIs(t, err, vault.ErrSecretNotFound, "move non-existent should fail")
|
||||
|
||||
// Try to move to existing destination
|
||||
_, err = runSecretWithStdin("dest-value", map[string]string{
|
||||
@@ -1226,9 +1225,8 @@ func test12bMoveSecret(t *testing.T, testMnemonic string, runSecret func(...stri
|
||||
}, "add", "test/existing-dest")
|
||||
require.NoError(t, err, "add test/existing-dest should succeed")
|
||||
|
||||
output, err = runSecret("move", "test/renamed", "test/existing-dest")
|
||||
require.Error(t, err, "move to existing destination should fail")
|
||||
assert.Contains(t, output, "already exists", "should indicate destination exists")
|
||||
_, err = runSecret("move", "test/renamed", "test/existing-dest")
|
||||
require.ErrorIs(t, err, vault.ErrSecretExists, "move to existing destination should fail")
|
||||
|
||||
// Verify the source wasn't removed since move failed
|
||||
getOutput, err = runSecretWithEnv(map[string]string{
|
||||
@@ -1933,12 +1931,11 @@ func test23ErrorHandling(t *testing.T, tempDir, secretPath, testMnemonic string,
|
||||
|
||||
// Import to non-existent vault with test passphrase
|
||||
testPassphrase := "test-passphrase-123" // Define testPassphrase locally
|
||||
output, err := runSecretWithEnv(map[string]string{
|
||||
_, err = runSecretWithEnv(map[string]string{
|
||||
secret.EnvMnemonic: testMnemonic,
|
||||
secret.EnvUnlockPassphrase: testPassphrase,
|
||||
}, "vault", "import", "nonexistent")
|
||||
require.Error(t, err, "import to non-existent vault should fail")
|
||||
assert.Contains(t, output, "does not exist", "should indicate vault doesn't exist")
|
||||
require.ErrorIs(t, err, vault.ErrVaultNotFound, "import to non-existent vault should fail")
|
||||
|
||||
// Get specific version that doesn't exist
|
||||
_, err = runSecretWithEnv(map[string]string{
|
||||
@@ -1947,11 +1944,10 @@ func test23ErrorHandling(t *testing.T, tempDir, secretPath, testMnemonic string,
|
||||
require.ErrorIs(t, err, vault.ErrVersionNotFound, "get non-existent version should fail")
|
||||
|
||||
// Promote non-existent version
|
||||
output, err = runSecretWithEnv(map[string]string{
|
||||
_, err = runSecretWithEnv(map[string]string{
|
||||
secret.EnvMnemonic: testMnemonic,
|
||||
}, "version", "promote", "database/password", "99999999.999")
|
||||
require.Error(t, err, "promote non-existent version should fail")
|
||||
assert.Contains(t, output, "not found", "should indicate version not found")
|
||||
require.ErrorIs(t, err, vault.ErrVersionNotFound, "promote non-existent version should fail")
|
||||
}
|
||||
|
||||
func test24EnvironmentVariables(t *testing.T, tempDir, secretPath, testMnemonic, testPassphrase string) {
|
||||
|
||||
@@ -45,15 +45,6 @@ func TestRejectedMoveWithinVaultLeavesStateUnchanged(t *testing.T) {
|
||||
{`mv --force work:x ""`, workX, "", true, ontoItself},
|
||||
// "work" is a vault name, so the destination is work:x.
|
||||
{"mv --force work:x work", workX, "work", true, ontoItself},
|
||||
{
|
||||
"mv work:nosuch work:y", "work:nosuch", "work:y", false,
|
||||
"secret 'nosuch' not found",
|
||||
},
|
||||
// Only an existing vault is used.
|
||||
{
|
||||
"mv --force nosuch:x nosuch:y", "nosuch:x", "nosuch:y", true,
|
||||
"vault 'nosuch' does not exist",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -70,6 +61,33 @@ func TestRejectedMoveWithinVaultLeavesStateUnchanged(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
missing := []struct {
|
||||
command string
|
||||
source, dest string
|
||||
force bool
|
||||
want error
|
||||
}{
|
||||
{
|
||||
"mv work:nosuch work:y", "work:nosuch", "work:y", false,
|
||||
vault.ErrSecretNotFound,
|
||||
},
|
||||
// Only an existing vault is used.
|
||||
{
|
||||
"mv --force nosuch:x nosuch:y", "nosuch:x", "nosuch:y", true,
|
||||
vault.ErrVaultNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range missing {
|
||||
t.Run(tt.command, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
requireRejectedAndUnchanged(t, before, tt.want, func(c *cli.Instance) error {
|
||||
return c.MoveSecret(&cobra.Command{}, tt.source, tt.dest, tt.force)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Each of these spells "work" a second way. The spelling is not a valid
|
||||
// vault name, so the move is not taken for a move between two vaults,
|
||||
// which would delete the destination, here the source.
|
||||
|
||||
@@ -297,18 +297,8 @@ func TestInvalidVersionLeavesVaultsUnchanged(t *testing.T) {
|
||||
for _, tt := range commands {
|
||||
for _, version := range []string{"", ".", "..", "../../..", "a/b"} {
|
||||
t.Run(fmt.Sprintf("%s %q", tt.command, version), func(t *testing.T) {
|
||||
fs := newFsFromSnapshot(t, before)
|
||||
|
||||
err := tt.run(cli.NewCLIInstanceWithStateDir(fs, testStateDir), version)
|
||||
|
||||
require.Equal(t, before, snapshotStateDir(t, fs))
|
||||
|
||||
// Compared as text: `version rm` and `version promote` return
|
||||
// internal/cli's own error of this text, which errors.Is does
|
||||
// not match to vault.ErrVersionNotFound.
|
||||
want := fmt.Errorf("version '%s' %w '%s'",
|
||||
version, vault.ErrVersionNotFound, "x")
|
||||
require.EqualError(t, err, want.Error())
|
||||
requireRejectedAndUnchanged(t, before, vault.ErrVersionNotFound,
|
||||
func(c *cli.Instance) error { return tt.run(c, version) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+7
-16
@@ -32,13 +32,7 @@ const (
|
||||
|
||||
// Sentinel errors for secret operations
|
||||
var (
|
||||
errSecretTooLarge = errors.New("secret too large: exceeds 100MB limit")
|
||||
errSecretFileTooLarge = errors.New(
|
||||
"secret file too large: exceeds 100MB limit")
|
||||
errSecretNotFound = errors.New("not found")
|
||||
errSecretExistsNoForce = errors.New(
|
||||
"already exists (use --force to overwrite)")
|
||||
errVaultDoesNotExist = errors.New("does not exist")
|
||||
errSecretTooLarge = errors.New("secret too large: exceeds 100MB limit")
|
||||
errCrossVaultSourceUnqualified = errors.New(
|
||||
"source must specify vault (e.g., vault:secret) for cross-vault move")
|
||||
errMoveOntoItself = errors.New("cannot be moved onto itself")
|
||||
@@ -673,10 +667,6 @@ func (cli *Instance) ImportSecret(
|
||||
|
||||
buffers, totalSize, err := readSecretFromReader(file)
|
||||
if err != nil {
|
||||
if errors.Is(err, errSecretTooLarge) {
|
||||
return errSecretFileTooLarge
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to read secret from file %s: %w", sourceFile, err)
|
||||
}
|
||||
defer destroyBuffers(buffers)
|
||||
@@ -776,7 +766,7 @@ func (cli *Instance) findSecretToRemove(
|
||||
|
||||
if !exists {
|
||||
return secretToRemove{},
|
||||
fmt.Errorf("secret '%s' %w", secretName, errSecretNotFound)
|
||||
fmt.Errorf("secret '%s' %w", secretName, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// A secret without a versions directory has no versions, and can
|
||||
@@ -907,7 +897,7 @@ func (cli *Instance) existingVault(name string) (*vault.Vault, error) {
|
||||
}
|
||||
|
||||
if !slices.Contains(vaults, name) {
|
||||
return nil, fmt.Errorf("vault '%s' %w", name, errVaultDoesNotExist)
|
||||
return nil, fmt.Errorf("vault '%s' %w", name, vault.ErrVaultNotFound)
|
||||
}
|
||||
|
||||
return vault.NewVault(cli.fs, cli.stateDir, name), nil
|
||||
@@ -938,7 +928,7 @@ func (cli *Instance) moveSecretWithinVault(
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("secret '%s' %w", source, errSecretNotFound)
|
||||
return fmt.Errorf("secret '%s' %w", source, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
destEncoded := strings.ReplaceAll(dest, "/", "%")
|
||||
@@ -963,7 +953,8 @@ func (cli *Instance) moveSecretWithinVault(
|
||||
|
||||
if exists {
|
||||
if !force {
|
||||
return fmt.Errorf("secret '%s' %w", dest, errSecretExistsNoForce)
|
||||
return fmt.Errorf("secret '%s' %w (use --force to overwrite)",
|
||||
dest, vault.ErrSecretExists)
|
||||
}
|
||||
|
||||
err = secret.RemoveDirAtomic(cli.fs, destDir)
|
||||
@@ -1028,7 +1019,7 @@ func (cli *Instance) moveSecretCrossVault(
|
||||
exists, err := afero.DirExists(cli.fs, srcSecretDir)
|
||||
if err != nil || !exists {
|
||||
return fmt.Errorf("secret '%s' %w in vault '%s'",
|
||||
srcSecretName, errSecretNotFound, srcVault.Name)
|
||||
srcSecretName, vault.ErrSecretNotFound, srcVault.Name)
|
||||
}
|
||||
|
||||
// The source is removed after the copy, so a destination that is the
|
||||
|
||||
@@ -289,7 +289,7 @@ func TestImportSecretVariousSizes(t *testing.T) {
|
||||
{
|
||||
name: "101MB file - should fail",
|
||||
size: 101 * 1024 * 1024,
|
||||
wantErr: errSecretFileTooLarge,
|
||||
wantErr: errSecretTooLarge,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -39,15 +39,10 @@ var (
|
||||
errInvalidUnlockerType = errors.New("invalid unlocker type")
|
||||
errKeyIDOnlyForPGP = errors.New(
|
||||
"--keyid flag is only valid for PGP unlockers")
|
||||
errKeychainMacOSOnly = errors.New(
|
||||
"keychain unlockers are only supported on macOS")
|
||||
errSecureEnclaveMacOSOnly = errors.New(
|
||||
"secure enclave unlockers are only supported on macOS")
|
||||
// errGPGKeyAlreadyUnlocker carries only the message tail; the caller
|
||||
// composes "GPG key <id> is already added as an unlocker".
|
||||
errGPGKeyAlreadyUnlocker = errors.New(
|
||||
"is already added as an unlocker")
|
||||
errUnsupportedUnlockerType = errors.New("unsupported unlocker type")
|
||||
)
|
||||
|
||||
// UnlockerInfo represents unlocker information for display
|
||||
@@ -439,7 +434,7 @@ func (cli *Instance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %s (supported: %s)",
|
||||
errUnsupportedUnlockerType, unlockerType, supportedTypes)
|
||||
errInvalidUnlockerType, unlockerType, supportedTypes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +469,7 @@ func (cli *Instance) addPassphraseUnlocker(cmd *cobra.Command) error {
|
||||
// Use secure passphrase input with confirmation
|
||||
passphraseBuffer, err = readSecurePassphrase("Enter passphrase for unlocker: ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read passphrase: %w", err)
|
||||
return err
|
||||
}
|
||||
defer passphraseBuffer.Destroy()
|
||||
}
|
||||
@@ -494,10 +489,6 @@ func (cli *Instance) addPassphraseUnlocker(cmd *cobra.Command) error {
|
||||
|
||||
// addKeychainUnlocker creates a macOS Keychain unlocker in the current vault
|
||||
func (cli *Instance) addKeychainUnlocker(cmd *cobra.Command) error {
|
||||
if runtime.GOOS != platformDarwin {
|
||||
return errKeychainMacOSOnly
|
||||
}
|
||||
|
||||
keychainUnlocker, err := secret.CreateKeychainUnlocker(
|
||||
cli.fs, cli.stateDir, cli.Mnemonic, cli.UnlockPassphrase)
|
||||
if err != nil {
|
||||
@@ -525,10 +516,6 @@ func (cli *Instance) addKeychainUnlocker(cmd *cobra.Command) error {
|
||||
// addSecureEnclaveUnlocker creates a Secure Enclave unlocker in the
|
||||
// current vault
|
||||
func (cli *Instance) addSecureEnclaveUnlocker(cmd *cobra.Command) error {
|
||||
if runtime.GOOS != platformDarwin {
|
||||
return errSecureEnclaveMacOSOnly
|
||||
}
|
||||
|
||||
seUnlocker, err := secret.CreateSecureEnclaveUnlocker(
|
||||
cli.fs, cli.stateDir, cli.Mnemonic, cli.UnlockPassphrase)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
@@ -98,7 +99,7 @@ func TestAddPGPUnlockerUnknownKey(t *testing.T) {
|
||||
|
||||
err := instance.addPGPUnlocker(cmd)
|
||||
|
||||
require.ErrorContains(t, err, "failed to resolve GPG key fingerprint")
|
||||
require.ErrorIs(t, err, secret.ErrGPGKeyNotFound)
|
||||
assertDirEntries(t, base,
|
||||
filepath.Join(testVaultDir(listTestVaultName), listTestUnlockersDirName),
|
||||
listTestUnlockerDirOne)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
var (
|
||||
errMnemonicEmpty = errors.New("mnemonic cannot be empty")
|
||||
errInvalidMnemonicPhrase = errors.New("invalid BIP39 mnemonic phrase")
|
||||
errInvalidMnemonic = errors.New("invalid BIP39 mnemonic")
|
||||
errVaultHasLongTermKey = errors.New(
|
||||
"already has a long-term key configured")
|
||||
errMnemonicEnvNotSet = errors.New(
|
||||
@@ -250,7 +249,7 @@ func (cli *Instance) resolvePassphrase() (*memguard.LockedBuffer, func(), error)
|
||||
// Use secure passphrase input with confirmation
|
||||
passphraseBuffer, err := readSecurePassphrase("Enter passphrase for unlocker: ")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read passphrase: %w", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return passphraseBuffer, passphraseBuffer.Destroy, nil
|
||||
@@ -353,7 +352,7 @@ func (cli *Instance) vaultImportPreflight(
|
||||
|
||||
if !exists {
|
||||
return "", "", "", fmt.Errorf("vault '%s' %w",
|
||||
vaultName, errVaultDoesNotExist)
|
||||
vaultName, vault.ErrVaultNotFound)
|
||||
}
|
||||
|
||||
// Check if vault already has a public key
|
||||
@@ -381,7 +380,7 @@ func (cli *Instance) vaultImportPreflight(
|
||||
secret.Debug("Validating BIP39 mnemonic", "word_count", len(mnemonicWords))
|
||||
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return "", "", "", errInvalidMnemonic
|
||||
return "", "", "", errInvalidMnemonicPhrase
|
||||
}
|
||||
|
||||
return vaultDir, pubKeyPath, mnemonic, nil
|
||||
@@ -644,7 +643,7 @@ func (cli *Instance) findVaultToRemove(name string) (vaultToRemove, error) {
|
||||
|
||||
if !slices.Contains(vaults, name) {
|
||||
return vaultToRemove{},
|
||||
fmt.Errorf("vault '%s' %w", name, errVaultDoesNotExist)
|
||||
fmt.Errorf("vault '%s' %w", name, vault.ErrVaultNotFound)
|
||||
}
|
||||
|
||||
if len(vaults) == 1 {
|
||||
|
||||
@@ -23,7 +23,6 @@ const (
|
||||
|
||||
// Sentinel errors for version operations
|
||||
var (
|
||||
errVersionNotFound = errors.New("not found for secret")
|
||||
errCannotRemoveCurrentVersion = errors.New("promote another version first")
|
||||
)
|
||||
|
||||
@@ -156,7 +155,7 @@ func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error {
|
||||
if !exists {
|
||||
secret.Debug("Secret not found", "secret_name", secretName)
|
||||
|
||||
return fmt.Errorf("secret '%s' %w", secretName, errSecretNotFound)
|
||||
return fmt.Errorf("secret '%s' %w", secretName, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// List all versions
|
||||
@@ -289,7 +288,7 @@ func (cli *Instance) PromoteVersion(
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("version '%s' %w '%s'",
|
||||
version, errVersionNotFound, secretName)
|
||||
version, vault.ErrVersionNotFound, secretName)
|
||||
}
|
||||
|
||||
// Update the current symlink using the proper function
|
||||
@@ -374,7 +373,7 @@ func (cli *Instance) findVersionToRemove(
|
||||
|
||||
if !exists {
|
||||
return versionToRemove{},
|
||||
fmt.Errorf("secret '%s' %w", secretName, errSecretNotFound)
|
||||
fmt.Errorf("secret '%s' %w", secretName, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
// Check if version exists
|
||||
@@ -386,7 +385,7 @@ func (cli *Instance) findVersionToRemove(
|
||||
|
||||
if !exists {
|
||||
return versionToRemove{}, fmt.Errorf("version '%s' %w '%s'",
|
||||
version, errVersionNotFound, secretName)
|
||||
version, vault.ErrVersionNotFound, secretName)
|
||||
}
|
||||
|
||||
// Get current version
|
||||
|
||||
@@ -171,7 +171,7 @@ func TestListVersionsNonExistentSecret(t *testing.T) {
|
||||
|
||||
// Try to list versions of non-existent secret
|
||||
err := cli.ListVersions(cmd, "nonexistent/secret")
|
||||
require.ErrorIs(t, err, errSecretNotFound)
|
||||
require.ErrorIs(t, err, vault.ErrSecretNotFound)
|
||||
}
|
||||
|
||||
func TestPromoteVersionCommand(t *testing.T) {
|
||||
@@ -265,7 +265,7 @@ func TestPromoteNonExistentVersion(t *testing.T) {
|
||||
|
||||
// Try to promote non-existent version
|
||||
err = cli.PromoteVersion(cmd, "test/secret", "20991231.999")
|
||||
require.ErrorIs(t, err, errVersionNotFound)
|
||||
require.ErrorIs(t, err, vault.ErrVersionNotFound)
|
||||
}
|
||||
|
||||
func TestGetSecretWithVersion(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user