user.go

  1package cmd
  2
  3import (
  4	"sort"
  5	"strings"
  6
  7	"github.com/charmbracelet/soft-serve/pkg/backend"
  8	"github.com/charmbracelet/soft-serve/pkg/proto"
  9	"github.com/charmbracelet/soft-serve/pkg/sshutils"
 10	"github.com/spf13/cobra"
 11	"golang.org/x/crypto/ssh"
 12)
 13
 14// UserCommand returns the user subcommand.
 15func UserCommand() *cobra.Command {
 16	cmd := &cobra.Command{
 17		Use:     "user",
 18		Aliases: []string{"users"},
 19		Short:   "Manage users",
 20	}
 21
 22	var admin bool
 23	var key string
 24	userCreateCommand := &cobra.Command{
 25		Use:               "create USERNAME [EMAIL]",
 26		Short:             "Create a new user",
 27		Args:              cobra.MinimumNArgs(1),
 28		PersistentPreRunE: checkIfAdmin,
 29		RunE: func(cmd *cobra.Command, args []string) error {
 30			var pubkeys []ssh.PublicKey
 31			ctx := cmd.Context()
 32			be := backend.FromContext(ctx)
 33			username := args[0]
 34			if key != "" {
 35				pk, _, err := sshutils.ParseAuthorizedKey(key)
 36				if err != nil {
 37					return err
 38				}
 39
 40				pubkeys = []ssh.PublicKey{pk}
 41			}
 42
 43			opts := proto.UserOptions{
 44				Admin:      admin,
 45				PublicKeys: pubkeys,
 46			}
 47
 48			if len(args) > 1 {
 49				opts.Emails = append(opts.Emails, args[1])
 50			}
 51
 52			_, err := be.CreateUser(ctx, username, opts)
 53			return err
 54		},
 55	}
 56
 57	userCreateCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
 58	userCreateCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
 59
 60	userDeleteCommand := &cobra.Command{
 61		Use:               "delete USERNAME",
 62		Short:             "Delete a user",
 63		Args:              cobra.ExactArgs(1),
 64		PersistentPreRunE: checkIfAdmin,
 65		RunE: func(cmd *cobra.Command, args []string) error {
 66			ctx := cmd.Context()
 67			be := backend.FromContext(ctx)
 68			username := args[0]
 69
 70			return be.DeleteUser(ctx, username)
 71		},
 72	}
 73
 74	userListCommand := &cobra.Command{
 75		Use:               "list",
 76		Aliases:           []string{"ls"},
 77		Short:             "List users",
 78		Args:              cobra.NoArgs,
 79		PersistentPreRunE: checkIfAdmin,
 80		RunE: func(cmd *cobra.Command, _ []string) error {
 81			ctx := cmd.Context()
 82			be := backend.FromContext(ctx)
 83			users, err := be.Users(ctx)
 84			if err != nil {
 85				return err
 86			}
 87
 88			sort.Strings(users)
 89			for _, u := range users {
 90				cmd.Println(u)
 91			}
 92
 93			return nil
 94		},
 95	}
 96
 97	userAddPubkeyCommand := &cobra.Command{
 98		Use:               "add-pubkey USERNAME AUTHORIZED_KEY",
 99		Short:             "Add a public key to a user",
100		Args:              cobra.MinimumNArgs(2),
101		PersistentPreRunE: checkIfAdmin,
102		RunE: func(cmd *cobra.Command, args []string) error {
103			ctx := cmd.Context()
104			be := backend.FromContext(ctx)
105			username := args[0]
106			pubkey := strings.Join(args[1:], " ")
107			pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
108			if err != nil {
109				return err
110			}
111
112			return be.AddPublicKey(ctx, username, pk)
113		},
114	}
115
116	userRemovePubkeyCommand := &cobra.Command{
117		Use:               "remove-pubkey USERNAME AUTHORIZED_KEY",
118		Short:             "Remove a public key from a user",
119		Args:              cobra.MinimumNArgs(2),
120		PersistentPreRunE: checkIfAdmin,
121		RunE: func(cmd *cobra.Command, args []string) error {
122			ctx := cmd.Context()
123			be := backend.FromContext(ctx)
124			username := args[0]
125			pubkey := strings.Join(args[1:], " ")
126			pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
127			if err != nil {
128				return err
129			}
130
131			return be.RemovePublicKey(ctx, username, pk)
132		},
133	}
134
135	userSetAdminCommand := &cobra.Command{
136		Use:               "set-admin USERNAME [true|false]",
137		Short:             "Make a user an admin",
138		Args:              cobra.ExactArgs(2),
139		PersistentPreRunE: checkIfAdmin,
140		RunE: func(cmd *cobra.Command, args []string) error {
141			ctx := cmd.Context()
142			be := backend.FromContext(ctx)
143			username := args[0]
144
145			return be.SetAdmin(ctx, username, args[1] == "true")
146		},
147	}
148
149	userInfoCommand := &cobra.Command{
150		Use:               "info USERNAME",
151		Short:             "Show information about a user",
152		Args:              cobra.ExactArgs(1),
153		PersistentPreRunE: checkIfAdmin,
154		RunE: func(cmd *cobra.Command, args []string) error {
155			ctx := cmd.Context()
156			be := backend.FromContext(ctx)
157			username := args[0]
158
159			user, err := be.User(ctx, username)
160			if err != nil {
161				return err
162			}
163
164			isAdmin := user.IsAdmin()
165
166			cmd.Printf("Username: %s\n", user.Username())
167			cmd.Printf("Admin: %t\n", isAdmin)
168			cmd.Printf("Public keys:\n")
169			for _, pk := range user.PublicKeys() {
170				cmd.Printf("  %s\n", sshutils.MarshalAuthorizedKey(pk))
171			}
172
173			emails := user.Emails()
174			if len(emails) > 0 {
175				cmd.Printf("Emails:\n")
176				for _, e := range emails {
177					cmd.Printf("  %s (primary: %v)\n", e.Email(), e.IsPrimary())
178				}
179			}
180
181			return nil
182		},
183	}
184
185	userSetUsernameCommand := &cobra.Command{
186		Use:               "set-username USERNAME NEW_USERNAME",
187		Short:             "Change a user's username",
188		Args:              cobra.ExactArgs(2),
189		PersistentPreRunE: checkIfAdmin,
190		RunE: func(cmd *cobra.Command, args []string) error {
191			ctx := cmd.Context()
192			be := backend.FromContext(ctx)
193			username := args[0]
194			newUsername := args[1]
195
196			return be.SetUsername(ctx, username, newUsername)
197		},
198	}
199
200	userAddEmailCommand := &cobra.Command{
201		Use:               "add-email USERNAME EMAIL",
202		Short:             "Add an email to a user",
203		Args:              cobra.ExactArgs(2),
204		PersistentPreRunE: checkIfAdmin,
205		RunE: func(cmd *cobra.Command, args []string) error {
206			ctx := cmd.Context()
207			be := backend.FromContext(ctx)
208			username := args[0]
209			email := args[1]
210			u, err := be.User(ctx, username)
211			if err != nil {
212				return err
213			}
214
215			return be.AddUserEmail(ctx, u, email)
216		},
217	}
218
219	userRemoveEmailCommand := &cobra.Command{
220		Use:               "remove-email USERNAME EMAIL",
221		Short:             "Remove an email from a user",
222		Args:              cobra.ExactArgs(2),
223		PersistentPreRunE: checkIfAdmin,
224		RunE: func(cmd *cobra.Command, args []string) error {
225			ctx := cmd.Context()
226			be := backend.FromContext(ctx)
227			username := args[0]
228			email := args[1]
229			u, err := be.User(ctx, username)
230			if err != nil {
231				return err
232			}
233
234			return be.RemoveUserEmail(ctx, u, email)
235		},
236	}
237
238	userSetPrimaryEmailCommand := &cobra.Command{
239		Use:               "set-primary-email USERNAME EMAIL",
240		Short:             "Set a user's primary email",
241		Args:              cobra.ExactArgs(2),
242		PersistentPreRunE: checkIfAdmin,
243		RunE: func(cmd *cobra.Command, args []string) error {
244			ctx := cmd.Context()
245			be := backend.FromContext(ctx)
246			username := args[0]
247			email := args[1]
248			u, err := be.User(ctx, username)
249			if err != nil {
250				return err
251			}
252
253			return be.SetUserPrimaryEmail(ctx, u, email)
254		},
255	}
256
257	cmd.AddCommand(
258		userCreateCommand,
259		userAddPubkeyCommand,
260		userInfoCommand,
261		userListCommand,
262		userDeleteCommand,
263		userRemovePubkeyCommand,
264		userSetAdminCommand,
265		userSetUsernameCommand,
266		userAddEmailCommand,
267		userRemoveEmailCommand,
268		userSetPrimaryEmailCommand,
269	)
270
271	return cmd
272}