1package cmd
2
3import (
4 "sort"
5 "strconv"
6 "strings"
7
8 "github.com/charmbracelet/log"
9 "github.com/charmbracelet/soft-serve/server/auth/sqlite"
10 "github.com/charmbracelet/soft-serve/server/sshutils"
11 "github.com/spf13/cobra"
12 "golang.org/x/crypto/ssh"
13)
14
15func userCommand(sb *sqlite.SqliteAuthStore) *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 ctx := cmd.Context()
31 var pubkeys []ssh.PublicKey
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 := sqlite.UserOptions{
43 Admin: admin,
44 PublicKeys: pubkeys,
45 }
46
47 _, err := sb.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 username := args[0]
63
64 return sb.DeleteUser(ctx, username)
65 },
66 }
67
68 userListCommand := &cobra.Command{
69 Use: "list",
70 Aliases: []string{"ls"},
71 Short: "List users",
72 Args: cobra.NoArgs,
73 PersistentPreRunE: checkIfAdmin,
74 RunE: func(cmd *cobra.Command, _ []string) error {
75 ctx := cmd.Context()
76 users, err := sb.Users(ctx)
77 if err != nil {
78 return err
79 }
80
81 sort.Strings(users)
82 for _, u := range users {
83 cmd.Println(u)
84 }
85
86 return nil
87 },
88 }
89
90 userAddPubkeyCommand := &cobra.Command{
91 Use: "add-pubkey USERNAME AUTHORIZED_KEY",
92 Short: "Add a public key to a user",
93 Args: cobra.MinimumNArgs(2),
94 PersistentPreRunE: checkIfAdmin,
95 RunE: func(cmd *cobra.Command, args []string) error {
96 ctx := cmd.Context()
97 username := args[0]
98 pubkey := strings.Join(args[1:], " ")
99 pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
100 if err != nil {
101 return err
102 }
103
104 return sb.AddPublicKey(ctx, username, pk)
105 },
106 }
107
108 userRemovePubkeyCommand := &cobra.Command{
109 Use: "remove-pubkey USERNAME AUTHORIZED_KEY",
110 Short: "Remove a public key from a user",
111 Args: cobra.MinimumNArgs(2),
112 PersistentPreRunE: checkIfAdmin,
113 RunE: func(cmd *cobra.Command, args []string) error {
114 ctx := cmd.Context()
115 username := args[0]
116 pubkey := strings.Join(args[1:], " ")
117 log.Debugf("key is %q", pubkey)
118 pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
119 if err != nil {
120 return err
121 }
122
123 return sb.RemovePublicKey(ctx, username, pk)
124 },
125 }
126
127 userSetAdminCommand := &cobra.Command{
128 Use: "set-admin USERNAME [true|false]",
129 Short: "Make a user an admin",
130 Args: cobra.ExactArgs(2),
131 PersistentPreRunE: checkIfAdmin,
132 RunE: func(cmd *cobra.Command, args []string) error {
133 ctx := cmd.Context()
134 username := args[0]
135 isAdmin, _ := strconv.ParseBool(args[1])
136
137 return sb.SetAdmin(ctx, username, isAdmin)
138 },
139 }
140
141 userInfoCommand := &cobra.Command{
142 Use: "info USERNAME",
143 Short: "Show information about a user",
144 Args: cobra.ExactArgs(1),
145 PersistentPreRunE: checkIfAdmin,
146 RunE: func(cmd *cobra.Command, args []string) error {
147 ctx := cmd.Context()
148 username := args[0]
149
150 user, err := sb.User(ctx, username)
151 if err != nil {
152 return err
153 }
154
155 isAdmin := user.IsAdmin()
156
157 cmd.Printf("Username: %s\n", user.Username())
158 cmd.Printf("Admin: %t\n", isAdmin)
159 cmd.Printf("Public keys:\n")
160 for _, pk := range user.PublicKeys() {
161 cmd.Printf(" %s\n", sshutils.MarshalAuthorizedKey(pk))
162 }
163
164 return nil
165 },
166 }
167
168 userSetUsernameCommand := &cobra.Command{
169 Use: "set-username USERNAME NEW_USERNAME",
170 Short: "Change a user's username",
171 Args: cobra.ExactArgs(2),
172 PersistentPreRunE: checkIfAdmin,
173 RunE: func(cmd *cobra.Command, args []string) error {
174 ctx := cmd.Context()
175 username := args[0]
176 newUsername := args[1]
177
178 return sb.SetUsername(ctx, username, newUsername)
179 },
180 }
181
182 cmd.AddCommand(
183 userCreateCommand,
184 userAddPubkeyCommand,
185 userInfoCommand,
186 userListCommand,
187 userDeleteCommand,
188 userRemovePubkeyCommand,
189 userSetAdminCommand,
190 userSetUsernameCommand,
191 )
192
193 return cmd
194}