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 cfg, _ := fromContext(cmd)
29 username := args[0]
30 pk, _, err := backend.ParseAuthorizedKey(key)
31 if err != nil {
32 return err
33 }
34
35 opts := backend.UserOptions{
36 Admin: admin,
37 PublicKeys: []ssh.PublicKey{pk},
38 }
39
40 _, err = cfg.Backend.CreateUser(username, opts)
41 return err
42 },
43 }
44
45 userAddCommand.Flags().BoolVarP(&admin, "admin", "a", false, "make the user an admin")
46 userAddCommand.Flags().StringVarP(&key, "key", "k", "", "add a public key to the user")
47
48 userRemoveCommand := &cobra.Command{
49 Use: "remove USERNAME",
50 Short: "Remove a user",
51 Args: cobra.ExactArgs(1),
52 PersistentPreRunE: checkIfAdmin,
53 RunE: func(cmd *cobra.Command, args []string) error {
54 cfg, _ := fromContext(cmd)
55 username := args[0]
56
57 return cfg.Backend.DeleteUser(username)
58 },
59 }
60
61 userListCommand := &cobra.Command{
62 Use: "list",
63 Aliases: []string{"ls"},
64 Short: "List users",
65 Args: cobra.NoArgs,
66 PersistentPreRunE: checkIfAdmin,
67 RunE: func(cmd *cobra.Command, _ []string) error {
68 cfg, _ := fromContext(cmd)
69 users, err := cfg.Backend.Users()
70 if err != nil {
71 return err
72 }
73
74 sort.Strings(users)
75 for _, u := range users {
76 cmd.Println(u)
77 }
78
79 return nil
80 },
81 }
82
83 userAddPubkeyCommand := &cobra.Command{
84 Use: "add-pubkey USERNAME AUTHORIZED_KEY",
85 Short: "Add a public key to a user",
86 Args: cobra.MinimumNArgs(2),
87 PersistentPreRunE: checkIfAdmin,
88 RunE: func(cmd *cobra.Command, args []string) error {
89 cfg, _ := fromContext(cmd)
90 username := args[0]
91 pubkey := strings.Join(args[1:], " ")
92 pk, _, err := backend.ParseAuthorizedKey(pubkey)
93 if err != nil {
94 return err
95 }
96
97 return cfg.Backend.AddPublicKey(username, pk)
98 },
99 }
100
101 userRemovePubkeyCommand := &cobra.Command{
102 Use: "remove-pubkey USERNAME AUTHORIZED_KEY",
103 Short: "Remove a public key from a user",
104 Args: cobra.MinimumNArgs(2),
105 PersistentPreRunE: checkIfAdmin,
106 RunE: func(cmd *cobra.Command, args []string) error {
107 cfg, _ := fromContext(cmd)
108 username := args[0]
109 pubkey := strings.Join(args[1:], " ")
110 log.Debugf("key is %q", pubkey)
111 pk, _, err := backend.ParseAuthorizedKey(pubkey)
112 if err != nil {
113 return err
114 }
115
116 return cfg.Backend.RemovePublicKey(username, pk)
117 },
118 }
119
120 userSetAdminCommand := &cobra.Command{
121 Use: "set-admin USERNAME [true|false]",
122 Short: "Make a user an admin",
123 Args: cobra.ExactArgs(2),
124 PersistentPreRunE: checkIfAdmin,
125 RunE: func(cmd *cobra.Command, args []string) error {
126 cfg, _ := fromContext(cmd)
127 username := args[0]
128
129 return cfg.Backend.SetAdmin(username, args[1] == "true")
130 },
131 }
132
133 userInfoCommand := &cobra.Command{
134 Use: "info USERNAME",
135 Short: "Show information about a user",
136 Args: cobra.ExactArgs(1),
137 PersistentPreRunE: checkIfAdmin,
138 RunE: func(cmd *cobra.Command, args []string) error {
139 cfg, s := fromContext(cmd)
140 ak := backend.MarshalAuthorizedKey(s.PublicKey())
141 username := args[0]
142
143 user, err := cfg.Backend.User(username)
144 if err != nil {
145 return err
146 }
147
148 isAdmin := user.IsAdmin()
149 for _, k := range cfg.InitialAdminKeys {
150 if ak == k {
151 isAdmin = true
152 break
153 }
154 }
155
156 cmd.Printf("Username: %s\n", user.Username())
157 cmd.Printf("Admin: %t\n", isAdmin)
158 cmd.Printf("Public keys:\n")
159 for _, pk := range user.PublicKeys() {
160 cmd.Printf(" %s\n", backend.MarshalAuthorizedKey(pk))
161 }
162
163 return nil
164 },
165 }
166
167 userSetUsernameCommand := &cobra.Command{
168 Use: "set-username USERNAME NEW_USERNAME",
169 Short: "Change a user's username",
170 Args: cobra.ExactArgs(2),
171 PersistentPreRunE: checkIfAdmin,
172 RunE: func(cmd *cobra.Command, args []string) error {
173 cfg, _ := fromContext(cmd)
174 username := args[0]
175 newUsername := args[1]
176
177 return cfg.Backend.SetUsername(username, newUsername)
178 },
179 }
180
181 cmd.AddCommand(
182 userAddCommand,
183 userAddPubkeyCommand,
184 userInfoCommand,
185 userListCommand,
186 userRemoveCommand,
187 userRemovePubkeyCommand,
188 userSetAdminCommand,
189 userSetUsernameCommand,
190 )
191
192 return cmd
193}