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}