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