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 isCollab, _ := d.IsCollaborator(repo, username)
86 if isCollab {
87 if anon > backend.ReadWriteAccess {
88 return anon
89 }
90 return backend.ReadWriteAccess
91 }
92
93 // If the repository is private, the user has no access.
94 if r.IsPrivate() {
95 return backend.NoAccess
96 }
97
98 // Otherwise, the user has read-only access.
99 return backend.ReadOnlyAccess
100 }
101
102 // If the repository doesn't exist, the user has read/write access.
103 if user != nil {
104 // If the repository doesn't exist, the user has read/write access.
105 if anon > backend.ReadWriteAccess {
106 return anon
107 }
108
109 return backend.ReadWriteAccess
110 }
111
112 // If the user doesn't exist, give them the anonymous access level.
113 return anon
114}
115
116// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
117//
118// It implements backend.Backend.
119func (d *SqliteBackend) AccessLevelByPublicKey(repo string, pk ssh.PublicKey) backend.AccessLevel {
120 ak := backend.MarshalAuthorizedKey(pk)
121 for _, k := range d.AdditionalAdmins {
122 if k == ak {
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 return wrapDbErr(
140 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
141 var userID int
142 if err := tx.Get(&userID, "SELECT id FROM user WHERE username = ?", username); err != nil {
143 return err
144 }
145
146 _, err := tx.Exec(`INSERT INTO public_key (user_id, public_key, updated_at)
147 VALUES (?, ?, CURRENT_TIMESTAMP);`, userID, backend.MarshalAuthorizedKey(pk))
148 return err
149 }),
150 )
151}
152
153// CreateUser creates a new user.
154//
155// It implements backend.Backend.
156func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (backend.User, error) {
157 var user *User
158 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
159 into := "INSERT INTO user (username"
160 values := "VALUES (?"
161 args := []interface{}{username}
162 if opts.Admin {
163 into += ", admin"
164 values += ", ?"
165 args = append(args, opts.Admin)
166 }
167 into += ", updated_at)"
168 values += ", CURRENT_TIMESTAMP)"
169
170 r, err := tx.Exec(into+" "+values, args...)
171 if err != nil {
172 return err
173 }
174
175 if len(opts.PublicKeys) > 0 {
176 userID, err := r.LastInsertId()
177 if err != nil {
178 return err
179 }
180
181 for _, pk := range opts.PublicKeys {
182 if _, err := tx.Exec(`INSERT INTO public_key (user_id, public_key, updated_at)
183 VALUES (?, ?, CURRENT_TIMESTAMP);`, userID, backend.MarshalAuthorizedKey(pk)); err != nil {
184 return err
185 }
186 }
187 }
188
189 user = &User{
190 db: d.db,
191 username: username,
192 }
193 return nil
194 }); err != nil {
195 return nil, wrapDbErr(err)
196 }
197
198 return user, nil
199}
200
201// DeleteUser deletes a user.
202//
203// It implements backend.Backend.
204func (d *SqliteBackend) DeleteUser(username string) error {
205 return wrapDbErr(
206 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
207 _, err := tx.Exec("DELETE FROM user WHERE username = ?", username)
208 return err
209 }),
210 )
211}
212
213// RemovePublicKey removes a public key from a user.
214//
215// It implements backend.Backend.
216func (d *SqliteBackend) RemovePublicKey(username string, pk ssh.PublicKey) error {
217 return wrapDbErr(
218 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
219 _, err := tx.Exec(`DELETE FROM public_key
220 WHERE user_id = (SELECT id FROM user WHERE username = ?)
221 AND public_key = ?;`, username, backend.MarshalAuthorizedKey(pk))
222 return err
223 }),
224 )
225}
226
227// ListPublicKeys lists the public keys of a user.
228func (d *SqliteBackend) ListPublicKeys(username string) ([]ssh.PublicKey, error) {
229 keys := make([]ssh.PublicKey, 0)
230 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
231 var keyStrings []string
232 if err := tx.Select(&keyStrings, `SELECT public_key
233 FROM public_key
234 INNER JOIN user ON user.id = public_key.user_id
235 WHERE user.username = ?;`, username); err != nil {
236 return err
237 }
238
239 for _, keyString := range keyStrings {
240 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyString))
241 if err != nil {
242 return err
243 }
244 keys = append(keys, key)
245 }
246
247 return nil
248 }); err != nil {
249 return nil, wrapDbErr(err)
250 }
251
252 return keys, nil
253}
254
255// SetUsername sets the username of a user.
256//
257// It implements backend.Backend.
258func (d *SqliteBackend) SetUsername(username string, newUsername string) error {
259 return wrapDbErr(
260 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
261 _, err := tx.Exec("UPDATE user SET username = ? WHERE username = ?", newUsername, username)
262 return err
263 }),
264 )
265}
266
267// SetAdmin sets the admin flag of a user.
268//
269// It implements backend.Backend.
270func (d *SqliteBackend) SetAdmin(username string, admin bool) error {
271 return wrapDbErr(
272 wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
273 _, err := tx.Exec("UPDATE user SET admin = ? WHERE username = ?", admin, username)
274 return err
275 }),
276 )
277}
278
279// User finds a user by username.
280//
281// It implements backend.Backend.
282func (d *SqliteBackend) User(username string) (backend.User, error) {
283 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
284 return tx.Get(&username, "SELECT username FROM user WHERE username = ?", username)
285 }); err != nil {
286 return nil, wrapDbErr(err)
287 }
288
289 return &User{
290 db: d.db,
291 username: username,
292 }, nil
293}
294
295// UserByPublicKey finds a user by public key.
296//
297// It implements backend.Backend.
298func (d *SqliteBackend) UserByPublicKey(pk ssh.PublicKey) (backend.User, error) {
299 var username string
300 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
301 return tx.Get(&username, `SELECT user.username
302 FROM public_key
303 INNER JOIN user ON user.id = public_key.user_id
304 WHERE public_key.public_key = ?;`, backend.MarshalAuthorizedKey(pk))
305 }); err != nil {
306 return nil, wrapDbErr(err)
307 }
308
309 return &User{
310 db: d.db,
311 username: username,
312 }, nil
313}
314
315// Users returns all users.
316//
317// It implements backend.Backend.
318func (d *SqliteBackend) Users() ([]string, error) {
319 var users []string
320 if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
321 return tx.Select(&users, "SELECT username FROM user")
322 }); err != nil {
323 return nil, wrapDbErr(err)
324 }
325
326 return users, nil
327}