1package sqlite
2
3import (
4 "context"
5 "strings"
6
7 "github.com/charmbracelet/soft-serve/server/backend"
8 "github.com/charmbracelet/soft-serve/server/sshutils"
9 "github.com/charmbracelet/soft-serve/server/utils"
10 "github.com/jmoiron/sqlx"
11 "golang.org/x/crypto/ssh"
12)
13
14// User represents a user.
15type User struct {
16 username string
17 db *sqlx.DB
18}
19
20var _ backend.User = (*User)(nil)
21
22// IsAdmin returns whether the user is an admin.
23//
24// It implements backend.User.
25func (u *User) IsAdmin() bool {
26 var admin bool
27 if err := wrapTx(u.db, context.Background(), func(tx *sqlx.Tx) error {
28 return tx.Get(&admin, "SELECT admin FROM user WHERE username = ?", u.username)
29 }); err != nil {
30 return false
31 }
32
33 return admin
34}
35
36// PublicKeys returns the user's public keys.
37//
38// It implements backend.User.
39func (u *User) PublicKeys() []ssh.PublicKey {
40 var keys []ssh.PublicKey
41 if err := wrapTx(u.db, context.Background(), func(tx *sqlx.Tx) error {
42 var keyStrings []string
43 if err := tx.Select(&keyStrings, `SELECT public_key
44 FROM public_key
45 INNER JOIN user ON user.id = public_key.user_id
46 WHERE user.username = ?;`, u.username); err != nil {
47 return err
48 }
49
50 for _, keyString := range keyStrings {
51 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyString))
52 if err != nil {
53 return err
54 }
55 keys = append(keys, key)
56 }
57
58 return nil
59 }); err != nil {
60 return nil
61 }
62
63 return keys
64}
65
66// Username returns the user's username.
67//
68// It implements backend.User.
69func (u *User) Username() string {
70 return u.username
71}
72
73// AccessLevel returns the access level of a user for a repository.
74//
75// It implements backend.Backend.
76func (d *SqliteBackend) AccessLevel(repo string, username string) backend.AccessLevel {
77 anon := d.AnonAccess()
78 user, _ := d.User(username)
79 // If the user is an admin, they have admin access.
80 if user != nil && user.IsAdmin() {
81 return backend.AdminAccess
82 }
83
84 // If the repository exists, check if the user is a collaborator.
85 r, _ := d.Repository(repo)
86 if r != nil {
87 // If the user is a collaborator, they have read/write access.
88 isCollab, _ := d.IsCollaborator(repo, username)
89 if isCollab {
90 if anon > backend.ReadWriteAccess {
91 return anon
92 }
93 return backend.ReadWriteAccess
94 }
95
96 // If the repository is private, the user has no access.
97 if r.IsPrivate() {
98 return backend.NoAccess
99 }
100
101 // Otherwise, the user has read-only access.
102 return backend.ReadOnlyAccess
103 }
104
105 if user != nil {
106 // If the repository doesn't exist, the user has read/write access.
107 if anon > backend.ReadWriteAccess {
108 return anon
109 }
110
111 return backend.ReadWriteAccess
112 }
113
114 // If the user doesn't exist, give them the anonymous access level.
115 return anon
116}
117
118// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
119//
120// It implements backend.Backend.
121func (d *SqliteBackend) AccessLevelByPublicKey(repo string, pk ssh.PublicKey) backend.AccessLevel {
122 for _, k := range d.cfg.AdminKeys() {
123 if sshutils.KeysEqual(pk, k) {
124 return backend.AdminAccess
125 }
126 }
127
128 user, _ := d.UserByPublicKey(pk)
129 if user != nil {
130 return d.AccessLevel(repo, user.Username())
131 }
132
133 return d.AccessLevel(repo, "")
134}
135
136// AddPublicKey adds a public key to a user.
137//
138// It implements backend.Backend.
139func (d *SqliteBackend) AddPublicKey(username string, pk ssh.PublicKey) error {
140 username = strings.ToLower(username)
141 if err := utils.ValidateUsername(username); err != nil {
142 return err
143 }
144
145 return wrapDbErr(
146 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
147 var userID int
148 if err := tx.Get(&userID, "SELECT id FROM user WHERE username = ?", username); err != nil {
149 return err
150 }
151
152 _, err := tx.Exec(`INSERT INTO public_key (user_id, public_key, updated_at)
153 VALUES (?, ?, CURRENT_TIMESTAMP);`, userID, sshutils.MarshalAuthorizedKey(pk))
154 return err
155 }),
156 )
157}
158
159// CreateUser creates a new user.
160//
161// It implements backend.Backend.
162func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (backend.User, error) {
163 username = strings.ToLower(username)
164 if err := utils.ValidateUsername(username); err != nil {
165 return nil, err
166 }
167
168 var user *User
169 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
170 stmt, err := tx.Prepare("INSERT INTO user (username, admin, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP);")
171 if err != nil {
172 return err
173 }
174
175 defer stmt.Close() // nolint: errcheck
176 r, err := stmt.Exec(username, opts.Admin)
177 if err != nil {
178 return err
179 }
180
181 if len(opts.PublicKeys) > 0 {
182 userID, err := r.LastInsertId()
183 if err != nil {
184 d.logger.Error("error getting last insert id")
185 return err
186 }
187
188 for _, pk := range opts.PublicKeys {
189 stmt, err := tx.Prepare(`INSERT INTO public_key (user_id, public_key, updated_at)
190 VALUES (?, ?, CURRENT_TIMESTAMP);`)
191 if err != nil {
192 return err
193 }
194
195 defer stmt.Close() // nolint: errcheck
196 if _, err := stmt.Exec(userID, sshutils.MarshalAuthorizedKey(pk)); err != nil {
197 return err
198 }
199 }
200 }
201
202 user = &User{
203 db: d.db,
204 username: username,
205 }
206 return nil
207 }); err != nil {
208 return nil, wrapDbErr(err)
209 }
210
211 return user, nil
212}
213
214// DeleteUser deletes a user.
215//
216// It implements backend.Backend.
217func (d *SqliteBackend) DeleteUser(username string) error {
218 username = strings.ToLower(username)
219 if err := utils.ValidateUsername(username); err != nil {
220 return err
221 }
222
223 return wrapDbErr(
224 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
225 _, err := tx.Exec("DELETE FROM user WHERE username = ?", username)
226 return err
227 }),
228 )
229}
230
231// RemovePublicKey removes a public key from a user.
232//
233// It implements backend.Backend.
234func (d *SqliteBackend) RemovePublicKey(username string, pk ssh.PublicKey) error {
235 return wrapDbErr(
236 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
237 _, err := tx.Exec(`DELETE FROM public_key
238 WHERE user_id = (SELECT id FROM user WHERE username = ?)
239 AND public_key = ?;`, username, sshutils.MarshalAuthorizedKey(pk))
240 return err
241 }),
242 )
243}
244
245// ListPublicKeys lists the public keys of a user.
246func (d *SqliteBackend) ListPublicKeys(username string) ([]ssh.PublicKey, error) {
247 username = strings.ToLower(username)
248 if err := utils.ValidateUsername(username); err != nil {
249 return nil, err
250 }
251
252 keys := make([]ssh.PublicKey, 0)
253 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
254 var keyStrings []string
255 if err := tx.Select(&keyStrings, `SELECT public_key
256 FROM public_key
257 INNER JOIN user ON user.id = public_key.user_id
258 WHERE user.username = ?;`, username); err != nil {
259 return err
260 }
261
262 for _, keyString := range keyStrings {
263 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyString))
264 if err != nil {
265 return err
266 }
267 keys = append(keys, key)
268 }
269
270 return nil
271 }); err != nil {
272 return nil, wrapDbErr(err)
273 }
274
275 return keys, nil
276}
277
278// SetUsername sets the username of a user.
279//
280// It implements backend.Backend.
281func (d *SqliteBackend) SetUsername(username string, newUsername string) error {
282 username = strings.ToLower(username)
283 if err := utils.ValidateUsername(username); err != nil {
284 return err
285 }
286
287 return wrapDbErr(
288 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
289 _, err := tx.Exec("UPDATE user SET username = ? WHERE username = ?", newUsername, username)
290 return err
291 }),
292 )
293}
294
295// SetAdmin sets the admin flag of a user.
296//
297// It implements backend.Backend.
298func (d *SqliteBackend) SetAdmin(username string, admin bool) error {
299 username = strings.ToLower(username)
300 if err := utils.ValidateUsername(username); err != nil {
301 return err
302 }
303
304 return wrapDbErr(
305 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
306 _, err := tx.Exec("UPDATE user SET admin = ? WHERE username = ?", admin, username)
307 return err
308 }),
309 )
310}
311
312// User finds a user by username.
313//
314// It implements backend.Backend.
315func (d *SqliteBackend) User(username string) (backend.User, error) {
316 username = strings.ToLower(username)
317 if err := utils.ValidateUsername(username); err != nil {
318 return nil, err
319 }
320
321 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
322 return tx.Get(&username, "SELECT username FROM user WHERE username = ?", username)
323 }); err != nil {
324 return nil, wrapDbErr(err)
325 }
326
327 return &User{
328 db: d.db,
329 username: username,
330 }, nil
331}
332
333// UserByPublicKey finds a user by public key.
334//
335// It implements backend.Backend.
336func (d *SqliteBackend) UserByPublicKey(pk ssh.PublicKey) (backend.User, error) {
337 var username string
338 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
339 return tx.Get(&username, `SELECT user.username
340 FROM public_key
341 INNER JOIN user ON user.id = public_key.user_id
342 WHERE public_key.public_key = ?;`, sshutils.MarshalAuthorizedKey(pk))
343 }); err != nil {
344 return nil, wrapDbErr(err)
345 }
346
347 return &User{
348 db: d.db,
349 username: username,
350 }, nil
351}
352
353// Users returns all users.
354//
355// It implements backend.Backend.
356func (d *SqliteBackend) Users() ([]string, error) {
357 var users []string
358 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
359 return tx.Select(&users, "SELECT username FROM user")
360 }); err != nil {
361 return nil, wrapDbErr(err)
362 }
363
364 return users, nil
365}