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",
26 Short: "Create a new user",
27 Args: cobra.ExactArgs(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 //nolint:wrapcheck
38 }
39
40 pubkeys = []ssh.PublicKey{pk}
41 }
42
43 opts := proto.UserOptions{
44 Admin: admin,
45 PublicKeys: pubkeys,
46 }
47
48 _, err := be.CreateUser(ctx, username, opts)
49 return err //nolint:wrapcheck
50 },
51 }
52
53 userCreateCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
54 userCreateCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
55
56 userDeleteCommand := &cobra.Command{
57 Use: "delete USERNAME",
58 Short: "Delete a user",
59 Args: cobra.ExactArgs(1),
60 PersistentPreRunE: checkIfAdmin,
61 RunE: func(cmd *cobra.Command, args []string) error {
62 ctx := cmd.Context()
63 be := backend.FromContext(ctx)
64 username := args[0]
65
66 return be.DeleteUser(ctx, username)
67 },
68 }
69
70 userListCommand := &cobra.Command{
71 Use: "list",
72 Aliases: []string{"ls"},
73 Short: "List users",
74 Args: cobra.NoArgs,
75 PersistentPreRunE: checkIfAdmin,
76 RunE: func(cmd *cobra.Command, _ []string) error {
77 ctx := cmd.Context()
78 be := backend.FromContext(ctx)
79 users, err := be.Users(ctx)
80 if err != nil {
81 return err //nolint:wrapcheck
82 }
83
84 sort.Strings(users)
85 for _, u := range users {
86 cmd.Println(u)
87 }
88
89 return nil
90 },
91 }
92
93 userAddPubkeyCommand := &cobra.Command{
94 Use: "add-pubkey USERNAME AUTHORIZED_KEY",
95 Short: "Add a public key to a user",
96 Args: cobra.MinimumNArgs(2),
97 PersistentPreRunE: checkIfAdmin,
98 RunE: func(cmd *cobra.Command, args []string) error {
99 ctx := cmd.Context()
100 be := backend.FromContext(ctx)
101 username := args[0]
102 pubkey := strings.Join(args[1:], " ")
103 pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
104 if err != nil {
105 return err //nolint:wrapcheck
106 }
107
108 return be.AddPublicKey(ctx, username, pk)
109 },
110 }
111
112 userRemovePubkeyCommand := &cobra.Command{
113 Use: "remove-pubkey USERNAME AUTHORIZED_KEY",
114 Short: "Remove a public key from a user",
115 Args: cobra.MinimumNArgs(2),
116 PersistentPreRunE: checkIfAdmin,
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 //nolint:wrapcheck
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 PersistentPreRunE: checkIfAdmin,
136 RunE: func(cmd *cobra.Command, args []string) error {
137 ctx := cmd.Context()
138 be := backend.FromContext(ctx)
139 username := args[0]
140
141 return be.SetAdmin(ctx, username, args[1] == "true")
142 },
143 }
144
145 userInfoCommand := &cobra.Command{
146 Use: "info USERNAME",
147 Short: "Show information about a user",
148 Args: cobra.ExactArgs(1),
149 PersistentPreRunE: checkIfAdmin,
150 RunE: func(cmd *cobra.Command, args []string) error {
151 ctx := cmd.Context()
152 be := backend.FromContext(ctx)
153 username := args[0]
154
155 user, err := be.User(ctx, username)
156 if err != nil {
157 return err //nolint:wrapcheck
158 }
159
160 isAdmin := user.IsAdmin()
161
162 cmd.Printf("Username: %s\n", user.Username())
163 cmd.Printf("Admin: %t\n", isAdmin)
164 cmd.Printf("Public keys:\n")
165 for _, pk := range user.PublicKeys() {
166 cmd.Printf(" %s\n", sshutils.MarshalAuthorizedKey(pk))
167 }
168
169 return nil
170 },
171 }
172
173 userSetUsernameCommand := &cobra.Command{
174 Use: "set-username USERNAME NEW_USERNAME",
175 Short: "Change a user's username",
176 Args: cobra.ExactArgs(2),
177 PersistentPreRunE: checkIfAdmin,
178 RunE: func(cmd *cobra.Command, args []string) error {
179 ctx := cmd.Context()
180 be := backend.FromContext(ctx)
181 username := args[0]
182 newUsername := args[1]
183
184 return be.SetUsername(ctx, username, newUsername)
185 },
186 }
187
188 cmd.AddCommand(
189 userCreateCommand,
190 userAddPubkeyCommand,
191 userInfoCommand,
192 userListCommand,
193 userDeleteCommand,
194 userRemovePubkeyCommand,
195 userSetAdminCommand,
196 userSetUsernameCommand,
197 )
198
199 return cmd
200}