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}