1package cmd
2
3import (
4 "sort"
5 "strings"
6
7 "github.com/charmbracelet/log"
8 "github.com/charmbracelet/soft-serve/server/backend"
9 "github.com/spf13/cobra"
10 "golang.org/x/crypto/ssh"
11)
12
13func userCommand() *cobra.Command {
14 cmd := &cobra.Command{
15 Use: "user",
16 Aliases: []string{"users"},
17 Short: "Manage users",
18 }
19
20 var admin bool
21 var key string
22 userAddCommand := &cobra.Command{
23 Use: "add USERNAME",
24 Short: "Add a user",
25 Args: cobra.ExactArgs(1),
26 PersistentPreRunE: checkIfAdmin,
27 RunE: func(cmd *cobra.Command, args []string) error {
28 var pubkeys []ssh.PublicKey
29 cfg, _ := fromContext(cmd)
30 username := args[0]
31 if key != "" {
32 pk, _, err := backend.ParseAuthorizedKey(key)
33 if err != nil {
34 return err
35 }
36
37 pubkeys = []ssh.PublicKey{pk}
38 }
39
40 opts := backend.UserOptions{
41 Admin: admin,
42 PublicKeys: pubkeys,
43 }
44
45 _, err := cfg.Backend.CreateUser(username, opts)
46 return err
47 },
48 }
49
50 userAddCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
51 userAddCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
52
53 userRemoveCommand := &cobra.Command{
54 Use: "remove USERNAME",
55 Short: "Remove a user",
56 Args: cobra.ExactArgs(1),
57 PersistentPreRunE: checkIfAdmin,
58 RunE: func(cmd *cobra.Command, args []string) error {
59 cfg, _ := fromContext(cmd)
60 username := args[0]
61
62 return cfg.Backend.DeleteUser(username)
63 },
64 }
65
66 userListCommand := &cobra.Command{
67 Use: "list",
68 Aliases: []string{"ls"},
69 Short: "List users",
70 Args: cobra.NoArgs,
71 PersistentPreRunE: checkIfAdmin,
72 RunE: func(cmd *cobra.Command, _ []string) error {
73 cfg, _ := fromContext(cmd)
74 users, err := cfg.Backend.Users()
75 if err != nil {
76 return err
77 }
78
79 sort.Strings(users)
80 for _, u := range users {
81 cmd.Println(u)
82 }
83
84 return nil
85 },
86 }
87
88 userAddPubkeyCommand := &cobra.Command{
89 Use: "add-pubkey USERNAME AUTHORIZED_KEY",
90 Short: "Add a public key to a user",
91 Args: cobra.MinimumNArgs(2),
92 PersistentPreRunE: checkIfAdmin,
93 RunE: func(cmd *cobra.Command, args []string) error {
94 cfg, _ := fromContext(cmd)
95 username := args[0]
96 pubkey := strings.Join(args[1:], " ")
97 pk, _, err := backend.ParseAuthorizedKey(pubkey)
98 if err != nil {
99 return err
100 }
101
102 return cfg.Backend.AddPublicKey(username, pk)
103 },
104 }
105
106 userRemovePubkeyCommand := &cobra.Command{
107 Use: "remove-pubkey USERNAME AUTHORIZED_KEY",
108 Short: "Remove a public key from a user",
109 Args: cobra.MinimumNArgs(2),
110 PersistentPreRunE: checkIfAdmin,
111 RunE: func(cmd *cobra.Command, args []string) error {
112 cfg, _ := fromContext(cmd)
113 username := args[0]
114 pubkey := strings.Join(args[1:], " ")
115 log.Debugf("key is %q", pubkey)
116 pk, _, err := backend.ParseAuthorizedKey(pubkey)
117 if err != nil {
118 return err
119 }
120
121 return cfg.Backend.RemovePublicKey(username, pk)
122 },
123 }
124
125 userSetAdminCommand := &cobra.Command{
126 Use: "set-admin USERNAME [true|false]",
127 Short: "Make a user an admin",
128 Args: cobra.ExactArgs(2),
129 PersistentPreRunE: checkIfAdmin,
130 RunE: func(cmd *cobra.Command, args []string) error {
131 cfg, _ := fromContext(cmd)
132 username := args[0]
133
134 return cfg.Backend.SetAdmin(username, args[1] == "true")
135 },
136 }
137
138 userInfoCommand := &cobra.Command{
139 Use: "info USERNAME",
140 Short: "Show information about a user",
141 Args: cobra.ExactArgs(1),
142 PersistentPreRunE: checkIfAdmin,
143 RunE: func(cmd *cobra.Command, args []string) error {
144 cfg, s := fromContext(cmd)
145 ak := backend.MarshalAuthorizedKey(s.PublicKey())
146 username := args[0]
147
148 user, err := cfg.Backend.User(username)
149 if err != nil {
150 return err
151 }
152
153 isAdmin := user.IsAdmin()
154 for _, k := range cfg.InitialAdminKeys {
155 if ak == k {
156 isAdmin = true
157 break
158 }
159 }
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", backend.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 cfg, _ := fromContext(cmd)
179 username := args[0]
180 newUsername := args[1]
181
182 return cfg.Backend.SetUsername(username, newUsername)
183 },
184 }
185
186 cmd.AddCommand(
187 userAddCommand,
188 userAddPubkeyCommand,
189 userInfoCommand,
190 userListCommand,
191 userRemoveCommand,
192 userRemovePubkeyCommand,
193 userSetAdminCommand,
194 userSetUsernameCommand,
195 )
196
197 return cmd
198}