feat(auth): enhance login flow with notifications and path normalization
Some checks failed
CI / Test (1.22) (push) Failing after -30m33s
CI / Test (1.23) (push) Failing after -30m32s
CI / Lint (push) Failing after -30m39s
CI / Build (push) Failing after -30m35s

- 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
This commit is contained in:
2026-03-05 01:03:50 +02:00
parent 1490e0b596
commit 271a0603b8
10 changed files with 230 additions and 29 deletions

View File

@@ -0,0 +1,87 @@
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
}