sshkeeper/cmd/vault.go

169 lines
3.7 KiB
Go

package cmd
import (
"fmt"
"os"
"syscall"
"github.com/spf13/cobra"
"github.com/mirivlad/sshkeeper/internal/config"
"github.com/mirivlad/sshkeeper/internal/vault"
"golang.org/x/term"
)
var vaultInstance *vault.Vault
func getOrCreateVault() *vault.Vault {
if vaultInstance == nil {
vaultInstance = vault.New(config.VaultPath(cfg.DataDir))
}
return vaultInstance
}
var vaultCmd = &cobra.Command{
Use: "vault",
Short: "Vault management commands",
}
var vaultUnlockCmd = &cobra.Command{
Use: "unlock",
Short: "Unlock the vault with master password",
RunE: func(cmd *cobra.Command, args []string) error {
v := getOrCreateVault()
if v.IsUnlocked() {
fmt.Println("Vault is already unlocked.")
return nil
}
vaultPath := config.VaultPath(cfg.DataDir)
// Check if vault exists and has content
info, err := os.Stat(vaultPath)
if os.IsNotExist(err) || info.Size() == 0 {
// New vault - create with master password
fmt.Print("Create master password: ")
pw1, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
if len(pw1) == 0 {
return fmt.Errorf("password cannot be empty")
}
fmt.Print("Repeat master password: ")
pw2, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
if string(pw1) != string(pw2) {
return fmt.Errorf("passwords do not match")
}
if err := vault.Create(vaultPath, string(pw1)); err != nil {
return fmt.Errorf("create vault: %w", err)
}
// Unlock immediately
if err := v.Unlock(string(pw1)); err != nil {
return fmt.Errorf("unlock vault: %w", err)
}
fmt.Println("Vault created and unlocked.")
return nil
}
// Unlock existing vault
fmt.Print("Master password: ")
pw, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
if err := v.Unlock(string(pw)); err != nil {
return fmt.Errorf("unlock vault: %w", err)
}
fmt.Println("Vault unlocked.")
return nil
},
}
var vaultLockCmd = &cobra.Command{
Use: "lock",
Short: "Lock the vault",
RunE: func(cmd *cobra.Command, args []string) error {
v := getOrCreateVault()
v.Lock()
fmt.Println("Vault locked.")
return nil
},
}
var vaultStatusCmd = &cobra.Command{
Use: "status",
Short: "Show vault status",
RunE: func(cmd *cobra.Command, args []string) error {
v := getOrCreateVault()
if v.IsUnlocked() {
fmt.Println("Vault: unlocked")
} else {
fmt.Println("Vault: locked")
}
return nil
},
}
var vaultChangePasswordCmd = &cobra.Command{
Use: "change-password",
Short: "Change master password",
RunE: func(cmd *cobra.Command, args []string) error {
v := getOrCreateVault()
if !v.IsUnlocked() {
return fmt.Errorf("vault is locked. Unlock first with 'sshkeeper vault unlock'")
}
fmt.Print("New master password: ")
pw1, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
if len(pw1) == 0 {
return fmt.Errorf("password cannot be empty")
}
fmt.Print("Repeat new master password: ")
pw2, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
if string(pw1) != string(pw2) {
return fmt.Errorf("passwords do not match")
}
if err := v.ChangePassword(string(pw1)); err != nil {
return fmt.Errorf("change password: %w", err)
}
fmt.Println("Master password changed.")
return nil
},
}
func init() {
vaultCmd.AddCommand(vaultUnlockCmd)
vaultCmd.AddCommand(vaultLockCmd)
vaultCmd.AddCommand(vaultStatusCmd)
vaultCmd.AddCommand(vaultChangePasswordCmd)
}