package main import ( "fmt" "log/slog" "github.com/mjensen/weimar/internal/auth" "github.com/mjensen/weimar/internal/config" "github.com/mjensen/weimar/internal/db" "github.com/spf13/cobra" "golang.org/x/crypto/bcrypt" ) var usersCmd = &cobra.Command{ Use: "users", Short: "Manage users", } var usersListCmd = &cobra.Command{ Use: "list", Short: "List all users", RunE: func(cmd *cobra.Command, args []string) error { cfg := config.FromViper() database, err := db.Open(cfg.Database.Path) if err != nil { return fmt.Errorf("opening database: %w", err) } defer database.Close() users, err := database.ListUsers(cmd.Context()) if err != nil { return fmt.Errorf("listing users: %w", err) } if len(users) == 0 { fmt.Fprintln(cmd.OutOrStdout(), "no users found") return nil } for _, u := range users { admin := "" if u.IsAdmin { admin = " [admin]" } fmt.Fprintf(cmd.OutOrStdout(), "%d\t%s%s\n", u.ID, u.Username, admin) } return nil }, } var usersCreateCmd = &cobra.Command{ Use: "create ", Short: "Create a new user", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cfg := config.FromViper() database, err := db.Open(cfg.Database.Path) if err != nil { return fmt.Errorf("opening database: %w", err) } defer database.Close() isAdmin, _ := cmd.Flags().GetBool("admin") user, err := auth.Register(cmd.Context(), database, args[0], args[1], isAdmin) if err != nil { return fmt.Errorf("creating user: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "created user %d: %s\n", user.ID, user.Username) return nil }, } var usersDeleteCmd = &cobra.Command{ Use: "delete ", Short: "Delete a user", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cfg := config.FromViper() database, err := db.Open(cfg.Database.Path) if err != nil { return fmt.Errorf("opening database: %w", err) } defer database.Close() user, err := database.GetUserByUsername(cmd.Context(), args[0]) if err != nil { return fmt.Errorf("looking up user: %w", err) } if err := database.DeleteUser(cmd.Context(), user.ID); err != nil { return fmt.Errorf("deleting user: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "deleted user %d: %s\n", user.ID, user.Username) return nil }, } var usersPasswordResetCmd = &cobra.Command{ Use: "password-reset ", Short: "Reset a user's password", Long: "Reset a user's password. You will be prompted for the new password.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cfg := config.FromViper() database, err := db.Open(cfg.Database.Path) if err != nil { return fmt.Errorf("opening database: %w", err) } defer database.Close() user, err := database.GetUserByUsername(cmd.Context(), args[0]) if err != nil { return fmt.Errorf("looking up user: %w", err) } fmt.Fprint(cmd.ErrOrStderr(), "New password: ") var password string if _, err := fmt.Scanln(&password); err != nil { return fmt.Errorf("reading password: %w", err) } if len(password) < auth.MinPasswordLength { return fmt.Errorf("password must be at least %d characters", auth.MinPasswordLength) } hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) if err != nil { return fmt.Errorf("hashing password: %w", err) } if err := database.UpdateUserPassword(cmd.Context(), user.ID, string(hash)); err != nil { return fmt.Errorf("updating password: %w", err) } slog.Info("password reset", "user", user.Username) fmt.Fprintf(cmd.OutOrStdout(), "password reset for user %d: %s\n", user.ID, user.Username) return nil }, } func init() { rootCmd.AddCommand(usersCmd) usersCmd.AddCommand(usersListCmd) usersCmd.AddCommand(usersCreateCmd) usersCreateCmd.Flags().Bool("admin", false, "make the new user an admin") usersCmd.AddCommand(usersDeleteCmd) usersCmd.AddCommand(usersPasswordResetCmd) }