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