- add success notification on successful login - show error notification with detailed message on login failure - normalize API paths to prevent double slashes and trailing slashes - redirect to login page only if not on login request or page
88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
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
|
|
}
|