diff --git a/go.mod b/go.mod index c7dd656..c63e99d 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/labstack/echo/v4 v4.13.4 github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15 golang.org/x/crypto v0.44.0 + golang.org/x/term v0.39.0 ) require ( @@ -18,7 +19,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 68c7da4..735b8be 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,10 @@ golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvm golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= diff --git a/internal/auth.go b/internal/auth.go index b7687bd..da6f47f 100644 --- a/internal/auth.go +++ b/internal/auth.go @@ -112,6 +112,19 @@ func VerifyPassword(user *User, password string) bool { return err == nil } +// GetUsernameByID retrieves username by user ID +func GetUsernameByID(userID string) (string, error) { + var username string + err := authDB.QueryRow("SELECT username FROM users WHERE id = ?", userID).Scan(&username) + if err != nil { + if err == sql.ErrNoRows { + return "", fmt.Errorf("user not found") + } + return "", err + } + return username, nil +} + // GenerateSessionID generates a random session ID func GenerateSessionID() (string, error) { bytes := make([]byte, 32) diff --git a/main.go b/main.go index 17c610b..9ef656c 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "flag" + "fmt" "log/slog" "net/http" _ "net/http/pprof" @@ -10,11 +12,16 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/lowcarbdev/sbv/internal" + "golang.org/x/term" ) var logger *slog.Logger func main() { + // Parse CLI flags + resetPassword := flag.String("reset-password", "", "Reset password for the specified username") + flag.Parse() + // Initialize slog logger logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, @@ -35,6 +42,15 @@ func main() { } logger.Info("Authentication database initialized", "path", authDBPath) + // Handle password reset if requested + if *resetPassword != "" { + if err := handleResetPassword(*resetPassword); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + os.Exit(0) + } + // Create Echo instance e := echo.New() @@ -145,3 +161,45 @@ func main() { os.Exit(1) } } + +// handleResetPassword prompts for a new password and resets it for the given username +func handleResetPassword(username string) error { + // Look up the user + user, err := internal.GetUserByUsername(username) + if err != nil { + return fmt.Errorf("user '%s' not found", username) + } + + // Prompt for new password + fmt.Print("Enter new password: ") + passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if err != nil { + return fmt.Errorf("failed to read password: %w", err) + } + + // Prompt for password confirmation + fmt.Print("Confirm new password: ") + confirmBytes, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if err != nil { + return fmt.Errorf("failed to read password confirmation: %w", err) + } + + password := string(passwordBytes) + if password != string(confirmBytes) { + return fmt.Errorf("passwords do not match") + } + + if len(password) < 6 { + return fmt.Errorf("password must be at least 6 characters") + } + + // Update the password + if err := internal.UpdatePassword(user.ID, password); err != nil { + return fmt.Errorf("failed to update password: %w", err) + } + + fmt.Printf("Password reset successfully for user '%s'\n", username) + return nil +}