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