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, s := 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			for _, k := range cfg.AdminKeys() {
154				if backend.KeysEqual(k, s.PublicKey()) {
155					isAdmin = true
156					break
157				}
158			}
159
160			cmd.Printf("Username: %s\n", user.Username())
161			cmd.Printf("Admin: %t\n", isAdmin)
162			cmd.Printf("Public keys:\n")
163			for _, pk := range user.PublicKeys() {
164				cmd.Printf("  %s\n", backend.MarshalAuthorizedKey(pk))
165			}
166
167			return nil
168		},
169	}
170
171	userSetUsernameCommand := &cobra.Command{
172		Use:               "set-username USERNAME NEW_USERNAME",
173		Short:             "Change a user's username",
174		Args:              cobra.ExactArgs(2),
175		PersistentPreRunE: checkIfAdmin,
176		RunE: func(cmd *cobra.Command, args []string) error {
177			cfg, _ := fromContext(cmd)
178			username := args[0]
179			newUsername := args[1]
180
181			return cfg.Backend.SetUsername(username, newUsername)
182		},
183	}
184
185	cmd.AddCommand(
186		userCreateCommand,
187		userAddPubkeyCommand,
188		userInfoCommand,
189		userListCommand,
190		userDeleteCommand,
191		userRemovePubkeyCommand,
192		userSetAdminCommand,
193		userSetUsernameCommand,
194	)
195
196	return cmd
197}