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 the repository doesn't exist, the user has read/write access.
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 ak := backend.MarshalAuthorizedKey(pk)
123 for _, k := range d.AdditionalAdmins {
124 if k == ak {
125 return backend.AdminAccess
126 }
127 }
128
129 user, _ := d.UserByPublicKey(pk)
130 if user != nil {
131 return d.AccessLevel(repo, user.Username())
132 }
133
134 return d.AccessLevel(repo, "")
135}
136
137// AddPublicKey adds a public key to a user.
138//
139// It implements backend.Backend.
140func (d *SqliteBackend) AddPublicKey(username string, pk ssh.PublicKey) error {
141 username = strings.ToLower(username)
142 if err := utils.ValidateUsername(username); err != nil {
143 return err
144 }
145
146 return wrapDbErr(
147 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
148 var userID int
149 if err := tx.Get(&userID, "SELECT id FROM user WHERE username = ?", username); err != nil {
150 return err
151 }
152
153 _, err := tx.Exec(`INSERT INTO public_key (user_id, public_key, updated_at)
154 VALUES (?, ?, CURRENT_TIMESTAMP);`, userID, backend.MarshalAuthorizedKey(pk))
155 return err
156 }),
157 )
158}
159
160// CreateUser creates a new user.
161//
162// It implements backend.Backend.
163func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (backend.User, error) {
164 username = strings.ToLower(username)
165 if err := utils.ValidateUsername(username); err != nil {
166 return nil, err
167 }
168
169 var user *User
170 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
171 into := "INSERT INTO user (username, admin"
172 values := "VALUES (?, ?"
173 args := []interface{}{username, opts.Admin}
174 into += ", updated_at)"
175 values += ", CURRENT_TIMESTAMP)"
176
177 r, err := tx.Exec(into+" "+values, args...)
178 if err != nil {
179 return err
180 }
181
182 if len(opts.PublicKeys) > 0 {
183 userID, err := r.LastInsertId()
184 if err != nil {
185 return err
186 }
187
188 for _, pk := range opts.PublicKeys {
189 if _, err := tx.Exec(`INSERT INTO public_key (user_id, public_key, updated_at)
190 VALUES (?, ?, CURRENT_TIMESTAMP);`, userID, backend.MarshalAuthorizedKey(pk)); err != nil {
191 return err
192 }
193 }
194 }
195
196 user = &User{
197 db: d.db,
198 username: username,
199 }
200 return nil
201 }); err != nil {
202 return nil, wrapDbErr(err)
203 }
204
205 return user, nil
206}
207
208// DeleteUser deletes a user.
209//
210// It implements backend.Backend.
211func (d *SqliteBackend) DeleteUser(username string) error {
212 username = strings.ToLower(username)
213 if err := utils.ValidateUsername(username); err != nil {
214 return err
215 }
216
217 return wrapDbErr(
218 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
219 _, err := tx.Exec("DELETE FROM user WHERE username = ?", username)
220 return err
221 }),
222 )
223}
224
225// RemovePublicKey removes a public key from a user.
226//
227// It implements backend.Backend.
228func (d *SqliteBackend) RemovePublicKey(username string, pk ssh.PublicKey) error {
229 return wrapDbErr(
230 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
231 _, err := tx.Exec(`DELETE FROM public_key
232 WHERE user_id = (SELECT id FROM user WHERE username = ?)
233 AND public_key = ?;`, username, backend.MarshalAuthorizedKey(pk))
234 return err
235 }),
236 )
237}
238
239// ListPublicKeys lists the public keys of a user.
240func (d *SqliteBackend) ListPublicKeys(username string) ([]ssh.PublicKey, error) {
241 username = strings.ToLower(username)
242 if err := utils.ValidateUsername(username); err != nil {
243 return nil, err
244 }
245
246 keys := make([]ssh.PublicKey, 0)
247 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
248 var keyStrings []string
249 if err := tx.Select(&keyStrings, `SELECT public_key
250 FROM public_key
251 INNER JOIN user ON user.id = public_key.user_id
252 WHERE user.username = ?;`, username); err != nil {
253 return err
254 }
255
256 for _, keyString := range keyStrings {
257 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyString))
258 if err != nil {
259 return err
260 }
261 keys = append(keys, key)
262 }
263
264 return nil
265 }); err != nil {
266 return nil, wrapDbErr(err)
267 }
268
269 return keys, nil
270}
271
272// SetUsername sets the username of a user.
273//
274// It implements backend.Backend.
275func (d *SqliteBackend) SetUsername(username string, newUsername string) error {
276 username = strings.ToLower(username)
277 if err := utils.ValidateUsername(username); err != nil {
278 return err
279 }
280
281 return wrapDbErr(
282 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
283 _, err := tx.Exec("UPDATE user SET username = ? WHERE username = ?", newUsername, username)
284 return err
285 }),
286 )
287}
288
289// SetAdmin sets the admin flag of a user.
290//
291// It implements backend.Backend.
292func (d *SqliteBackend) SetAdmin(username string, admin bool) error {
293 username = strings.ToLower(username)
294 if err := utils.ValidateUsername(username); err != nil {
295 return err
296 }
297
298 return wrapDbErr(
299 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
300 _, err := tx.Exec("UPDATE user SET admin = ? WHERE username = ?", admin, username)
301 return err
302 }),
303 )
304}
305
306// User finds a user by username.
307//
308// It implements backend.Backend.
309func (d *SqliteBackend) User(username string) (backend.User, error) {
310 username = strings.ToLower(username)
311 if err := utils.ValidateUsername(username); err != nil {
312 return nil, err
313 }
314
315 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
316 return tx.Get(&username, "SELECT username FROM user WHERE username = ?", username)
317 }); err != nil {
318 return nil, wrapDbErr(err)
319 }
320
321 return &User{
322 db: d.db,
323 username: username,
324 }, nil
325}
326
327// UserByPublicKey finds a user by public key.
328//
329// It implements backend.Backend.
330func (d *SqliteBackend) UserByPublicKey(pk ssh.PublicKey) (backend.User, error) {
331 var username string
332 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
333 return tx.Get(&username, `SELECT user.username
334 FROM public_key
335 INNER JOIN user ON user.id = public_key.user_id
336 WHERE public_key.public_key = ?;`, backend.MarshalAuthorizedKey(pk))
337 }); err != nil {
338 return nil, wrapDbErr(err)
339 }
340
341 return &User{
342 db: d.db,
343 username: username,
344 }, nil
345}
346
347// Users returns all users.
348//
349// It implements backend.Backend.
350func (d *SqliteBackend) Users() ([]string, error) {
351 var users []string
352 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
353 return tx.Select(&users, "SELECT username FROM user")
354 }); err != nil {
355 return nil, wrapDbErr(err)
356 }
357
358 return users, nil
359}