cmd/weimar/users.go (view raw)
1package main
2
3import (
4 "fmt"
5 "log/slog"
6
7 "codeberg.org/maxwelljensen/weimar/internal/auth"
8 "codeberg.org/maxwelljensen/weimar/internal/config"
9 "codeberg.org/maxwelljensen/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}