Repository: caddyserver/caddy
File: modules/caddyhttp/caddyauth/command.go (Go)

Code before:
	"github.com/caddyserver/caddy/v2"
)

func init() {
	caddycmd.RegisterCommand(caddycmd.Command{
		Name:  "hash-password",
		Usage: "[--plaintext <password>] [--algorithm <name>] [--cost <difficulty>]",
		Short: "Hashes a password and writes base64",
		Long: `
Convenient way to hash a plaintext password. The resulting
hash is written to stdout as a base64 string.

--plaintext, when omitted, will be read from stdin. If
Caddy is attached to a controlling tty, the plaintext will
not be echoed.

--algorithm currently only supports 'bcrypt', and is the default.

--cost defines the bcrypt hashing difficulty.
A higher cost increases security by making hashing slower and more CPU-intensive.
Default is 14; higher values may significantly impact performance on slower machines.
`,
		CobraFunc: func(cmd *cobra.Command) {
			cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
			cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
			cmd.Flags().IntP("cost", "c", defaultBcryptCost, "Hashing cost used to create the given hashed password")
			cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
		},
	})
}

func cmdHashPassword(fs caddycmd.Flags) (int, error) {
	var err error

	algorithm := fs.String("algorithm")
	plaintext := []byte(fs.String("plaintext"))
	cost := fs.Int("cost")

	if len(plaintext) == 0 {
		fd := int(os.Stdin.Fd())
		if term.IsTerminal(fd) {
			// ensure the terminal state is restored on SIGINT
			state, _ := term.GetState(fd)
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			go func() {
				<-c
				_ = term.Restore(fd, state)
				os.Exit(caddy.ExitCodeFailedStartup)
			}()
			defer signal.Stop(c)

Review comment (suggestion):
One issue I have with this is that the cost is only used for bcrypt but if another algorithm is used (which... we don't actually support anymore 😅 we used to have `scrypt` but I removed it cause it's kinda bad anyway) then passing a cost can be misleading. Maybe this should say the cost is only relevant if the algorithm is bcrypt, for now.

Code after:
	"github.com/caddyserver/caddy/v2"
)

func init() {
	caddycmd.RegisterCommand(caddycmd.Command{
		Name:  "hash-password",
		Usage: "[--plaintext <password>] [--algorithm <name>] [--cost <difficulty>]",
		Short: "Hashes a password and writes base64",
		Long: `
Convenient way to hash a plaintext password. The resulting
hash is written to stdout as a base64 string.

--plaintext, when omitted, will be read from stdin. If
Caddy is attached to a controlling tty, the plaintext will
not be echoed.

--algorithm currently only supports 'bcrypt', and is the default.

--cost sets the bcrypt hashing difficulty.
Higher values increase security by making the hash computation slower and more CPU-intensive.
If the provided cost is not within the valid range [bcrypt.MinCost, bcrypt.MaxCost],
the default value (defaultBcryptCost) will be used instead.
Note: Higher cost values can significantly degrade performance on slower systems.
`,
		CobraFunc: func(cmd *cobra.Command) {
			cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
			cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
			cmd.Flags().Int("bcrypt-cost", defaultBcryptCost, "Bcrypt hashing cost (only used with 'bcrypt' algorithm)")
			cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
		},
	})
}

func cmdHashPassword(fs caddycmd.Flags) (int, error) {
	var err error

	algorithm := fs.String("algorithm")
	plaintext := []byte(fs.String("plaintext"))
	bcryptCost := fs.Int("bcrypt-cost")

	if len(plaintext) == 0 {
		fd := int(os.Stdin.Fd())
		if term.IsTerminal(fd) {
			// ensure the terminal state is restored on SIGINT
			state, _ := term.GetState(fd)
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			go func() {
				<-c
				_ = term.Restore(fd, state)
				os.Exit(caddy.ExitCodeFailedStartup)