package main import ( "fmt" "io" "os" "strings" "github.com/spf13/cobra" "golang.org/x/crypto/bcrypt" ) var ( hashPassword string hashCost int hashFromStdin bool ) var passwordHashCmd = &cobra.Command{ Use: "password-hash [password]", Short: "Generate a bcrypt password hash", Long: "Generate a bcrypt password hash for creating or updating users.", Args: cobra.MaximumNArgs(1), Annotations: map[string]string{ "skip-config-load": "true", }, Run: func(cmd *cobra.Command, args []string) { checkError(runPasswordHash(args)) }, } func init() { passwordHashCmd.Flags().StringVarP(&hashPassword, "password", "p", "", "Password to hash") passwordHashCmd.Flags().IntVar(&hashCost, "cost", bcrypt.DefaultCost, "Bcrypt cost") passwordHashCmd.Flags().BoolVar(&hashFromStdin, "stdin", false, "Read password from stdin") } func runPasswordHash(args []string) error { if hashCost < bcrypt.MinCost || hashCost > bcrypt.MaxCost { return fmt.Errorf("invalid cost %d (must be between %d and %d)", hashCost, bcrypt.MinCost, bcrypt.MaxCost) } password, err := resolveHashPassword(args) if err != nil { return err } hashed, err := bcrypt.GenerateFromPassword([]byte(password), hashCost) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } fmt.Println(string(hashed)) return nil } func resolveHashPassword(args []string) (string, error) { if hashFromStdin && (hashPassword != "" || len(args) > 0) { return "", fmt.Errorf("use --stdin by itself (do not combine with --password or positional password)") } if hashFromStdin { data, err := io.ReadAll(os.Stdin) if err != nil { return "", fmt.Errorf("failed to read password from stdin: %w", err) } password := strings.TrimRight(string(data), "\r\n") if password == "" { return "", fmt.Errorf("password cannot be empty") } return password, nil } if hashPassword != "" && len(args) > 0 { return "", fmt.Errorf("use either --password or positional password, not both") } if hashPassword != "" { return hashPassword, nil } if len(args) > 0 { return args[0], nil } return readPassword("Password: "), nil }