all repos — weimar @ b26a020c4b21a1c0d9a62bc6afdf405ea2097d82

Unnamed repository; edit this file 'description' to name the repository.

cmd/weimar/users.go (view raw)

  1package main
  2
  3import (
  4	"fmt"
  5	"log/slog"
  6
  7	"github.com/mjensen/weimar/internal/auth"
  8	"github.com/mjensen/weimar/internal/config"
  9	"github.com/mjensen/weimar/internal/db"
 10	"github.com/spf13/cobra"
 11	"golang.org/x/crypto/bcrypt"
 12)
 13
 14var usersCmd = &cobra.Command{
 15	Use:   "users",
 16	Short: "Manage users",
 17}
 18
 19var usersListCmd = &cobra.Command{
 20	Use:   "list",
 21	Short: "List all users",
 22	RunE: func(cmd *cobra.Command, args []string) error {
 23		cfg := config.FromViper()
 24		database, err := db.Open(cfg.Database.Path)
 25		if err != nil {
 26			return fmt.Errorf("opening database: %w", err)
 27		}
 28		defer database.Close()
 29
 30		users, err := database.ListUsers(cmd.Context())
 31		if err != nil {
 32			return fmt.Errorf("listing users: %w", err)
 33		}
 34
 35		if len(users) == 0 {
 36			fmt.Fprintln(cmd.OutOrStdout(), "no users found")
 37			return nil
 38		}
 39
 40		for _, u := range users {
 41			admin := ""
 42			if u.IsAdmin {
 43				admin = " [admin]"
 44			}
 45			fmt.Fprintf(cmd.OutOrStdout(), "%d\t%s%s\n", u.ID, u.Username, admin)
 46		}
 47		return nil
 48	},
 49}
 50
 51var usersCreateCmd = &cobra.Command{
 52	Use:   "create <username> <password>",
 53	Short: "Create a new user",
 54	Args:  cobra.ExactArgs(2),
 55	RunE: func(cmd *cobra.Command, args []string) error {
 56		cfg := config.FromViper()
 57		database, err := db.Open(cfg.Database.Path)
 58		if err != nil {
 59			return fmt.Errorf("opening database: %w", err)
 60		}
 61		defer database.Close()
 62
 63		isAdmin, _ := cmd.Flags().GetBool("admin")
 64		user, err := auth.Register(cmd.Context(), database, args[0], args[1], isAdmin)
 65		if err != nil {
 66			return fmt.Errorf("creating user: %w", err)
 67		}
 68		fmt.Fprintf(cmd.OutOrStdout(), "created user %d: %s\n", user.ID, user.Username)
 69		return nil
 70	},
 71}
 72
 73var usersDeleteCmd = &cobra.Command{
 74	Use:   "delete <username>",
 75	Short: "Delete a user",
 76	Args:  cobra.ExactArgs(1),
 77	RunE: func(cmd *cobra.Command, args []string) error {
 78		cfg := config.FromViper()
 79		database, err := db.Open(cfg.Database.Path)
 80		if err != nil {
 81			return fmt.Errorf("opening database: %w", err)
 82		}
 83		defer database.Close()
 84
 85		user, err := database.GetUserByUsername(cmd.Context(), args[0])
 86		if err != nil {
 87			return fmt.Errorf("looking up user: %w", err)
 88		}
 89
 90		if err := database.DeleteUser(cmd.Context(), user.ID); err != nil {
 91			return fmt.Errorf("deleting user: %w", err)
 92		}
 93		fmt.Fprintf(cmd.OutOrStdout(), "deleted user %d: %s\n", user.ID, user.Username)
 94		return nil
 95	},
 96}
 97
 98var usersPasswordResetCmd = &cobra.Command{
 99	Use:   "password-reset <username>",
100	Short: "Reset a user's password",
101	Long:  "Reset a user's password. You will be prompted for the new password.",
102	Args:  cobra.ExactArgs(1),
103	RunE: func(cmd *cobra.Command, args []string) error {
104		cfg := config.FromViper()
105		database, err := db.Open(cfg.Database.Path)
106		if err != nil {
107			return fmt.Errorf("opening database: %w", err)
108		}
109		defer database.Close()
110
111		user, err := database.GetUserByUsername(cmd.Context(), args[0])
112		if err != nil {
113			return fmt.Errorf("looking up user: %w", err)
114		}
115
116		fmt.Fprint(cmd.ErrOrStderr(), "New password: ")
117		var password string
118		if _, err := fmt.Scanln(&password); err != nil {
119			return fmt.Errorf("reading password: %w", err)
120		}
121
122		if len(password) < auth.MinPasswordLength {
123			return fmt.Errorf("password must be at least %d characters", auth.MinPasswordLength)
124		}
125
126		hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
127		if err != nil {
128			return fmt.Errorf("hashing password: %w", err)
129		}
130
131		if err := database.UpdateUserPassword(cmd.Context(), user.ID, string(hash)); err != nil {
132			return fmt.Errorf("updating password: %w", err)
133		}
134		slog.Info("password reset", "user", user.Username)
135		fmt.Fprintf(cmd.OutOrStdout(), "password reset for user %d: %s\n", user.ID, user.Username)
136		return nil
137	},
138}
139
140func init() {
141	rootCmd.AddCommand(usersCmd)
142	usersCmd.AddCommand(usersListCmd)
143	usersCmd.AddCommand(usersCreateCmd)
144	usersCreateCmd.Flags().Bool("admin", false, "make the new user an admin")
145	usersCmd.AddCommand(usersDeleteCmd)
146	usersCmd.AddCommand(usersPasswordResetCmd)
147}