user.go

  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}