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	userAddCommand := &cobra.Command{
 23		Use:               "add USERNAME",
 24		Short:             "Add a 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	userAddCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
 51	userAddCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
 52
 53	userRemoveCommand := &cobra.Command{
 54		Use:               "remove USERNAME",
 55		Short:             "Remove 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			ak := backend.MarshalAuthorizedKey(s.PublicKey())
146			username := args[0]
147
148			user, err := cfg.Backend.User(username)
149			if err != nil {
150				return err
151			}
152
153			isAdmin := user.IsAdmin()
154			for _, k := range cfg.InitialAdminKeys {
155				if ak == k {
156					isAdmin = true
157					break
158				}
159			}
160
161			cmd.Printf("Username: %s\n", user.Username())
162			cmd.Printf("Admin: %t\n", isAdmin)
163			cmd.Printf("Public keys:\n")
164			for _, pk := range user.PublicKeys() {
165				cmd.Printf("  %s\n", backend.MarshalAuthorizedKey(pk))
166			}
167
168			return nil
169		},
170	}
171
172	userSetUsernameCommand := &cobra.Command{
173		Use:               "set-username USERNAME NEW_USERNAME",
174		Short:             "Change a user's username",
175		Args:              cobra.ExactArgs(2),
176		PersistentPreRunE: checkIfAdmin,
177		RunE: func(cmd *cobra.Command, args []string) error {
178			cfg, _ := fromContext(cmd)
179			username := args[0]
180			newUsername := args[1]
181
182			return cfg.Backend.SetUsername(username, newUsername)
183		},
184	}
185
186	cmd.AddCommand(
187		userAddCommand,
188		userAddPubkeyCommand,
189		userInfoCommand,
190		userListCommand,
191		userRemoveCommand,
192		userRemovePubkeyCommand,
193		userSetAdminCommand,
194		userSetUsernameCommand,
195	)
196
197	return cmd
198}