user.go

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