1package cmd
  2
  3import (
  4	"sort"
  5	"strings"
  6
  7	"github.com/charmbracelet/log"
  8	"github.com/charmbracelet/soft-serve/server/backend"
  9	"github.com/spf13/cobra"
 10	"golang.org/x/crypto/ssh"
 11)
 12
 13func userCommand() *cobra.Command {
 14	cmd := &cobra.Command{
 15		Use:     "user",
 16		Aliases: []string{"users"},
 17		Short:   "Manage users",
 18	}
 19
 20	var admin bool
 21	var key string
 22	userCreateCommand := &cobra.Command{
 23		Use:               "create USERNAME",
 24		Short:             "Create a new user",
 25		Args:              cobra.ExactArgs(1),
 26		PersistentPreRunE: checkIfAdmin,
 27		RunE: func(cmd *cobra.Command, args []string) error {
 28			var pubkeys []ssh.PublicKey
 29			cfg, _ := fromContext(cmd)
 30			username := args[0]
 31			if key != "" {
 32				pk, _, err := backend.ParseAuthorizedKey(key)
 33				if err != nil {
 34					return err
 35				}
 36
 37				pubkeys = []ssh.PublicKey{pk}
 38			}
 39
 40			opts := backend.UserOptions{
 41				Admin:      admin,
 42				PublicKeys: pubkeys,
 43			}
 44
 45			_, err := cfg.Backend.CreateUser(username, opts)
 46			return err
 47		},
 48	}
 49
 50	userCreateCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
 51	userCreateCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
 52
 53	userDeleteCommand := &cobra.Command{
 54		Use:               "delete USERNAME",
 55		Short:             "Delete a user",
 56		Args:              cobra.ExactArgs(1),
 57		PersistentPreRunE: checkIfAdmin,
 58		RunE: func(cmd *cobra.Command, args []string) error {
 59			cfg, _ := fromContext(cmd)
 60			username := args[0]
 61
 62			return cfg.Backend.DeleteUser(username)
 63		},
 64	}
 65
 66	userListCommand := &cobra.Command{
 67		Use:               "list",
 68		Aliases:           []string{"ls"},
 69		Short:             "List users",
 70		Args:              cobra.NoArgs,
 71		PersistentPreRunE: checkIfAdmin,
 72		RunE: func(cmd *cobra.Command, _ []string) error {
 73			cfg, _ := fromContext(cmd)
 74			users, err := cfg.Backend.Users()
 75			if err != nil {
 76				return err
 77			}
 78
 79			sort.Strings(users)
 80			for _, u := range users {
 81				cmd.Println(u)
 82			}
 83
 84			return nil
 85		},
 86	}
 87
 88	userAddPubkeyCommand := &cobra.Command{
 89		Use:               "add-pubkey USERNAME AUTHORIZED_KEY",
 90		Short:             "Add a public key to a user",
 91		Args:              cobra.MinimumNArgs(2),
 92		PersistentPreRunE: checkIfAdmin,
 93		RunE: func(cmd *cobra.Command, args []string) error {
 94			cfg, _ := fromContext(cmd)
 95			username := args[0]
 96			pubkey := strings.Join(args[1:], " ")
 97			pk, _, err := backend.ParseAuthorizedKey(pubkey)
 98			if err != nil {
 99				return err
100			}
101
102			return cfg.Backend.AddPublicKey(username, pk)
103		},
104	}
105
106	userRemovePubkeyCommand := &cobra.Command{
107		Use:               "remove-pubkey USERNAME AUTHORIZED_KEY",
108		Short:             "Remove a public key from a user",
109		Args:              cobra.MinimumNArgs(2),
110		PersistentPreRunE: checkIfAdmin,
111		RunE: func(cmd *cobra.Command, args []string) error {
112			cfg, _ := fromContext(cmd)
113			username := args[0]
114			pubkey := strings.Join(args[1:], " ")
115			log.Debugf("key is %q", pubkey)
116			pk, _, err := backend.ParseAuthorizedKey(pubkey)
117			if err != nil {
118				return err
119			}
120
121			return cfg.Backend.RemovePublicKey(username, pk)
122		},
123	}
124
125	userSetAdminCommand := &cobra.Command{
126		Use:               "set-admin USERNAME [true|false]",
127		Short:             "Make a user an admin",
128		Args:              cobra.ExactArgs(2),
129		PersistentPreRunE: checkIfAdmin,
130		RunE: func(cmd *cobra.Command, args []string) error {
131			cfg, _ := fromContext(cmd)
132			username := args[0]
133
134			return cfg.Backend.SetAdmin(username, args[1] == "true")
135		},
136	}
137
138	userInfoCommand := &cobra.Command{
139		Use:               "info USERNAME",
140		Short:             "Show information about a user",
141		Args:              cobra.ExactArgs(1),
142		PersistentPreRunE: checkIfAdmin,
143		RunE: func(cmd *cobra.Command, args []string) error {
144			cfg, _ := fromContext(cmd)
145			username := args[0]
146
147			user, err := cfg.Backend.User(username)
148			if err != nil {
149				return err
150			}
151
152			isAdmin := user.IsAdmin()
153
154			cmd.Printf("Username: %s\n", user.Username())
155			cmd.Printf("Admin: %t\n", isAdmin)
156			cmd.Printf("Public keys:\n")
157			for _, pk := range user.PublicKeys() {
158				cmd.Printf("  %s\n", backend.MarshalAuthorizedKey(pk))
159			}
160
161			return nil
162		},
163	}
164
165	userSetUsernameCommand := &cobra.Command{
166		Use:               "set-username USERNAME NEW_USERNAME",
167		Short:             "Change a user's username",
168		Args:              cobra.ExactArgs(2),
169		PersistentPreRunE: checkIfAdmin,
170		RunE: func(cmd *cobra.Command, args []string) error {
171			cfg, _ := fromContext(cmd)
172			username := args[0]
173			newUsername := args[1]
174
175			return cfg.Backend.SetUsername(username, newUsername)
176		},
177	}
178
179	cmd.AddCommand(
180		userCreateCommand,
181		userAddPubkeyCommand,
182		userInfoCommand,
183		userListCommand,
184		userDeleteCommand,
185		userRemovePubkeyCommand,
186		userSetAdminCommand,
187		userSetUsernameCommand,
188	)
189
190	return cmd
191}