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}