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