1package backend
2
3import (
4 "context"
5 "strings"
6
7 "github.com/charmbracelet/soft-serve/server/access"
8 "github.com/charmbracelet/soft-serve/server/db"
9 "github.com/charmbracelet/soft-serve/server/db/models"
10 "github.com/charmbracelet/soft-serve/server/proto"
11 "github.com/charmbracelet/soft-serve/server/sshutils"
12 "github.com/charmbracelet/soft-serve/server/utils"
13 "golang.org/x/crypto/ssh"
14)
15
16// AccessLevel returns the access level of a user for a repository.
17//
18// It implements backend.Backend.
19func (d *Backend) AccessLevel(ctx context.Context, repo string, username string) access.AccessLevel {
20 anon := d.AnonAccess(ctx)
21 user, _ := d.User(ctx, username)
22 // If the user is an admin, they have admin access.
23 if user != nil && user.IsAdmin() {
24 return access.AdminAccess
25 }
26
27 // If the repository exists, check if the user is a collaborator.
28 r, _ := d.Repository(ctx, repo)
29 if r != nil {
30 // If the user is a collaborator, they have read/write access.
31 isCollab, _ := d.IsCollaborator(ctx, repo, username)
32 if isCollab {
33 if anon > access.ReadWriteAccess {
34 return anon
35 }
36 return access.ReadWriteAccess
37 }
38
39 // If the repository is private, the user has no access.
40 if r.IsPrivate() {
41 return access.NoAccess
42 }
43
44 // Otherwise, the user has read-only access.
45 return access.ReadOnlyAccess
46 }
47
48 if user != nil {
49 // If the repository doesn't exist, the user has read/write access.
50 if anon > access.ReadWriteAccess {
51 return anon
52 }
53
54 return access.ReadWriteAccess
55 }
56
57 // If the user doesn't exist, give them the anonymous access level.
58 return anon
59}
60
61// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
62//
63// It implements backend.Backend.
64func (d *Backend) AccessLevelByPublicKey(ctx context.Context, repo string, pk ssh.PublicKey) access.AccessLevel {
65 for _, k := range d.cfg.AdminKeys() {
66 if sshutils.KeysEqual(pk, k) {
67 return access.AdminAccess
68 }
69 }
70
71 user, _ := d.UserByPublicKey(ctx, pk)
72 if user != nil {
73 return d.AccessLevel(ctx, repo, user.Username())
74 }
75
76 return d.AccessLevel(ctx, repo, "")
77}
78
79// User finds a user by username.
80//
81// It implements backend.Backend.
82func (d *Backend) User(ctx context.Context, username string) (proto.User, error) {
83 username = strings.ToLower(username)
84 if err := utils.ValidateUsername(username); err != nil {
85 return nil, err
86 }
87
88 var m models.User
89 var pks []ssh.PublicKey
90 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
91 var err error
92 m, err = d.store.FindUserByUsername(ctx, tx, username)
93 if err != nil {
94 return err
95 }
96
97 pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
98 return err
99 }); err != nil {
100 return nil, db.WrapError(err)
101 }
102
103 return &user{
104 user: m,
105 publicKeys: pks,
106 }, nil
107}
108
109// UserByPublicKey finds a user by public key.
110//
111// It implements backend.Backend.
112func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.User, error) {
113 var m models.User
114 var pks []ssh.PublicKey
115 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
116 var err error
117 m, err = d.store.FindUserByPublicKey(ctx, tx, pk)
118 if err != nil {
119 return err
120 }
121
122 pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
123 return err
124 }); err != nil {
125 return nil, db.WrapError(err)
126 }
127
128 return &user{
129 user: m,
130 publicKeys: pks,
131 }, nil
132}
133
134// Users returns all users.
135//
136// It implements backend.Backend.
137func (d *Backend) Users(ctx context.Context) ([]string, error) {
138 var users []string
139 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
140 ms, err := d.store.GetAllUsers(ctx, tx)
141 if err != nil {
142 return err
143 }
144
145 for _, m := range ms {
146 users = append(users, m.Username)
147 }
148
149 return nil
150 }); err != nil {
151 return nil, db.WrapError(err)
152 }
153
154 return users, nil
155}
156
157// AddPublicKey adds a public key to a user.
158//
159// It implements backend.Backend.
160func (d *Backend) AddPublicKey(ctx context.Context, username string, pk ssh.PublicKey) error {
161 username = strings.ToLower(username)
162 if err := utils.ValidateUsername(username); err != nil {
163 return err
164 }
165
166 return db.WrapError(
167 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
168 return d.store.AddPublicKeyByUsername(ctx, tx, username, pk)
169 }),
170 )
171}
172
173// CreateUser creates a new user.
174//
175// It implements backend.Backend.
176func (d *Backend) CreateUser(ctx context.Context, username string, opts proto.UserOptions) (proto.User, error) {
177 username = strings.ToLower(username)
178 if err := utils.ValidateUsername(username); err != nil {
179 return nil, err
180 }
181
182 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
183 return d.store.CreateUser(ctx, tx, username, opts.Admin, opts.PublicKeys)
184 }); err != nil {
185 return nil, db.WrapError(err)
186 }
187
188 return d.User(ctx, username)
189}
190
191// DeleteUser deletes a user.
192//
193// It implements backend.Backend.
194func (d *Backend) DeleteUser(ctx context.Context, username string) error {
195 username = strings.ToLower(username)
196 if err := utils.ValidateUsername(username); err != nil {
197 return err
198 }
199
200 return db.WrapError(
201 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
202 return d.store.DeleteUserByUsername(ctx, tx, username)
203 }),
204 )
205}
206
207// RemovePublicKey removes a public key from a user.
208//
209// It implements backend.Backend.
210func (d *Backend) RemovePublicKey(ctx context.Context, username string, pk ssh.PublicKey) error {
211 return db.WrapError(
212 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
213 return d.store.RemovePublicKeyByUsername(ctx, tx, username, pk)
214 }),
215 )
216}
217
218// ListPublicKeys lists the public keys of a user.
219func (d *Backend) ListPublicKeys(ctx context.Context, username string) ([]ssh.PublicKey, error) {
220 username = strings.ToLower(username)
221 if err := utils.ValidateUsername(username); err != nil {
222 return nil, err
223 }
224
225 var keys []ssh.PublicKey
226 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
227 var err error
228 keys, err = d.store.ListPublicKeysByUsername(ctx, tx, username)
229 return err
230 }); err != nil {
231 return nil, db.WrapError(err)
232 }
233
234 return keys, nil
235}
236
237// SetUsername sets the username of a user.
238//
239// It implements backend.Backend.
240func (d *Backend) SetUsername(ctx context.Context, username string, newUsername string) error {
241 username = strings.ToLower(username)
242 if err := utils.ValidateUsername(username); err != nil {
243 return err
244 }
245
246 return db.WrapError(
247 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
248 return d.store.SetUsernameByUsername(ctx, tx, username, newUsername)
249 }),
250 )
251}
252
253// SetAdmin sets the admin flag of a user.
254//
255// It implements backend.Backend.
256func (d *Backend) SetAdmin(ctx context.Context, username string, admin bool) error {
257 username = strings.ToLower(username)
258 if err := utils.ValidateUsername(username); err != nil {
259 return err
260 }
261
262 return db.WrapError(
263 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
264 return d.store.SetAdminByUsername(ctx, tx, username, admin)
265 }),
266 )
267}
268
269type user struct {
270 user models.User
271 publicKeys []ssh.PublicKey
272}
273
274var _ proto.User = (*user)(nil)
275
276// IsAdmin implements store.User
277func (u *user) IsAdmin() bool {
278 return u.user.Admin
279}
280
281// PublicKeys implements store.User
282func (u *user) PublicKeys() []ssh.PublicKey {
283 return u.publicKeys
284}
285
286// Username implements store.User
287func (u *user) Username() string {
288 return u.user.Username
289}