Skip to content

Commit a7ae1e9

Browse files
20 new rest endpoints for key management (#22)
* working now * working now * start tests * working on test * tests pass * bring back refactor * remove comment * add volumes to docker-compose
1 parent 5276782 commit a7ae1e9

File tree

8 files changed

+211
-44
lines changed

8 files changed

+211
-44
lines changed

docker-compose.yaml

+16-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ services:
1515
max-size: 10m
1616
ports:
1717
- '3000:3000'
18-
image: b44427a64de93c20123c068387b0adc0434434ba709fbd91dd03d33ade489c3e
19-
container_name: sshdbg
18+
image: ssh-sync-server-prerelease
19+
container_name: ssh-sync-server
2020
ssh-sync-db:
2121
image: therealpaulgg/ssh-sync-db:latest
2222
container_name: ssh-sync-db-debug
@@ -26,18 +26,28 @@ services:
2626
- POSTGRES_DB=sshsync
2727
restart: always
2828
ssh-sync:
29-
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
29+
image: ssh-debug
3030
container_name: ssh-sync
3131
stdin_open: true # Allows Docker container to keep STDIN open
3232
tty: true # Allocates a pseudo-TTY
33+
volumes:
34+
- ssh-sync-volume:/root
3335
ssh-sync-2:
34-
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
36+
image: ssh-debug
3537
container_name: ssh-sync-2
3638
stdin_open: true # Allows Docker container to keep STDIN open
3739
tty: true # Allocates a pseudo-TTY
40+
volumes:
41+
- ssh-sync-2-volume:/root
3842
ssh-sync-3:
39-
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
43+
image: ssh-debug
4044
container_name: ssh-sync-3
4145
stdin_open: true # Allows Docker container to keep STDIN open
4246
tty: true # Allocates a pseudo-TTY
43-
#http://ssh-sync-server-debug:3000
47+
volumes:
48+
- ssh-sync-3-volume:/root
49+
50+
volumes:
51+
ssh-sync-volume:
52+
ssh-sync-2-volume:
53+
ssh-sync-3-volume:

pkg/database/query/transaction.go

+21
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package query
22

33
import (
44
"context"
5+
"net/http"
56

67
"github.com/georgysavva/scany/v2/pgxscan"
78
"github.com/jackc/pgx/v5"
9+
"github.com/rs/zerolog/log"
810
"github.com/therealpaulgg/ssh-sync-server/pkg/database"
911
)
1012

@@ -63,3 +65,22 @@ func (q *QueryServiceTxImpl[T]) Insert(tx pgx.Tx, query string, args ...interfac
6365
_, err := tx.Exec(context.Background(), query, args...)
6466
return err
6567
}
68+
69+
func RollbackFunc(txQueryService TransactionService, tx pgx.Tx, w http.ResponseWriter, err *error) {
70+
rb := func(tx pgx.Tx) {
71+
err := txQueryService.Rollback(tx)
72+
if err != nil {
73+
log.Err(err).Msg("error rolling back transaction")
74+
}
75+
}
76+
if *err != nil {
77+
rb(tx)
78+
} else {
79+
internalErr := txQueryService.Commit(tx)
80+
if internalErr != nil {
81+
log.Err(internalErr).Msg("error committing transaction")
82+
rb(tx)
83+
w.WriteHeader(http.StatusInternalServerError)
84+
}
85+
}
86+
}

pkg/database/repository/machine.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ func (repo *MachineRepo) DeleteMachine(id uuid.UUID) error {
4545
if _, err := tx.Exec(context.TODO(), "delete from machines where id = $1", id); err != nil {
4646
return err
4747
}
48-
err = tx.Commit(context.TODO())
49-
return err
48+
return tx.Commit(context.TODO())
5049
}
5150

5251
func (repo *MachineRepo) GetMachine(id uuid.UUID) (*models.Machine, error) {

pkg/database/repository/user.go

+19
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ type UserRepository interface {
2323
DeleteUser(id uuid.UUID) error
2424
GetUserConfig(id uuid.UUID) ([]models.SshConfig, error)
2525
GetUserKeys(id uuid.UUID) ([]models.SshKey, error)
26+
GetUserKey(userId uuid.UUID, keyId uuid.UUID) (*models.SshKey, error)
2627
AddAndUpdateKeys(user *models.User) error
2728
AddAndUpdateKeysTx(user *models.User, tx pgx.Tx) error
2829
AddAndUpdateConfig(user *models.User) error
2930
AddAndUpdateConfigTx(user *models.User, tx pgx.Tx) error
31+
DeleteUserKeyTx(user *models.User, id uuid.UUID, tx pgx.Tx) error
3032
}
3133

3234
type UserRepo struct {
@@ -195,3 +197,20 @@ func (repo *UserRepo) AddAndUpdateConfigTx(user *models.User, tx pgx.Tx) error {
195197
}
196198
return nil
197199
}
200+
201+
func (repo *UserRepo) GetUserKey(userId uuid.UUID, keyId uuid.UUID) (*models.SshKey, error) {
202+
q := do.MustInvoke[query.QueryService[models.SshKey]](repo.Injector)
203+
key, err := q.QueryOne("select * from ssh_keys where user_id = $1 and id = $2", userId, keyId)
204+
if err != nil {
205+
return nil, err
206+
}
207+
if key == nil {
208+
return nil, sql.ErrNoRows
209+
}
210+
return key, nil
211+
}
212+
213+
func (repo *UserRepo) DeleteUserKeyTx(user *models.User, id uuid.UUID, tx pgx.Tx) error {
214+
_, err := tx.Exec(context.TODO(), "delete from ssh_keys where user_id = $1 and id = $2", user.ID, id)
215+
return err
216+
}

pkg/database/repository/usermock.go

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/web/router/routes/data.go

+41-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99

1010
"github.com/go-chi/chi"
11+
"github.com/google/uuid"
1112
"github.com/jackc/pgx/v5"
1213
"github.com/rs/zerolog/log"
1314
"github.com/samber/do"
@@ -115,24 +116,7 @@ func addData(i *do.Injector) http.HandlerFunc {
115116
w.WriteHeader(http.StatusInternalServerError)
116117
return
117118
}
118-
defer func() {
119-
rb := func(tx pgx.Tx) {
120-
err := txQueryService.Rollback(tx)
121-
if err != nil {
122-
log.Err(err).Msg("error rolling back transaction")
123-
}
124-
}
125-
if err != nil {
126-
rb(tx)
127-
} else {
128-
internalErr := txQueryService.Commit(tx)
129-
if internalErr != nil {
130-
log.Err(err).Msg("error committing transaction")
131-
rb(tx)
132-
w.WriteHeader(http.StatusInternalServerError)
133-
}
134-
}
135-
}()
119+
defer query.RollbackFunc(txQueryService, tx, w, &err)
136120
if err = userRepo.AddAndUpdateConfigTx(user, tx); err != nil {
137121
log.Err(err).Msg("could not add config")
138122
w.WriteHeader(http.StatusInternalServerError)
@@ -168,10 +152,49 @@ func addData(i *do.Injector) http.HandlerFunc {
168152
}
169153
}
170154

155+
func deleteData(i *do.Injector) http.HandlerFunc {
156+
return func(w http.ResponseWriter, r *http.Request) {
157+
user, ok := r.Context().Value(context_keys.UserContextKey).(*models.User)
158+
if !ok {
159+
log.Err(errors.New("could not get user from context"))
160+
w.WriteHeader(http.StatusInternalServerError)
161+
return
162+
}
163+
keyIdStr := chi.URLParam(r, "id")
164+
keyId, err := uuid.Parse(keyIdStr)
165+
if err != nil {
166+
log.Err(err).Msg("could not parse key id")
167+
w.WriteHeader(http.StatusBadRequest)
168+
return
169+
}
170+
userRepo := do.MustInvoke[repository.UserRepository](i)
171+
key, err := userRepo.GetUserKey(user.ID, keyId)
172+
if err != nil {
173+
log.Err(err).Msg("could not get key")
174+
w.WriteHeader(http.StatusNotFound)
175+
return
176+
}
177+
txQueryService := do.MustInvoke[query.TransactionService](i)
178+
tx, err := txQueryService.StartTx(pgx.TxOptions{})
179+
if err != nil {
180+
log.Err(err).Msg("error starting transaction")
181+
w.WriteHeader(http.StatusInternalServerError)
182+
return
183+
}
184+
defer query.RollbackFunc(txQueryService, tx, w, &err)
185+
if err = userRepo.DeleteUserKeyTx(user, key.ID, tx); err != nil {
186+
log.Err(err).Msg("could not delete key")
187+
w.WriteHeader(http.StatusInternalServerError)
188+
return
189+
}
190+
}
191+
}
192+
171193
func DataRoutes(i *do.Injector) chi.Router {
172194
r := chi.NewRouter()
173195
r.Use(middleware.ConfigureAuth(i))
174196
r.Get("/", getData(i))
175197
r.Post("/", addData(i))
198+
r.Delete("/key/{id}", deleteData(i))
176199
return r
177200
}

pkg/web/router/routes/data_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55
"crypto/rand"
66
"encoding/json"
77
"errors"
8+
"fmt"
89
"mime/multipart"
910
"net/http"
1011
"net/http/httptest"
1112
"testing"
1213

14+
"github.com/go-chi/chi"
1315
"github.com/golang/mock/gomock"
1416
"github.com/google/uuid"
1517
"github.com/samber/do"
@@ -258,3 +260,84 @@ func TestAddDataError(t *testing.T) {
258260
status, http.StatusOK)
259261
}
260262
}
263+
264+
func TestDeleteKey(t *testing.T) {
265+
// Arrange
266+
keyId := uuid.New()
267+
req := httptest.NewRequest("DELETE", fmt.Sprintf("/%s", keyId.String()), nil)
268+
user := testutils.GenerateUser()
269+
req = testutils.AddUserContext(req, user)
270+
key := &models.SshKey{
271+
ID: keyId,
272+
UserID: user.ID,
273+
}
274+
275+
injector := do.New()
276+
ctrl := gomock.NewController(t)
277+
defer ctrl.Finish()
278+
mockUserRepo := repository.NewMockUserRepository(ctrl)
279+
txMock := pgx.NewMockTx(ctrl)
280+
mockUserRepo.EXPECT().GetUserKey(user.ID, keyId).Return(key, nil)
281+
mockUserRepo.EXPECT().DeleteUserKeyTx(gomock.Any(), keyId, txMock).Return(nil)
282+
do.Provide(injector, func(i *do.Injector) (repository.UserRepository, error) {
283+
return mockUserRepo, nil
284+
})
285+
mockTransactionService := query.NewMockTransactionService(ctrl)
286+
mockTransactionService.EXPECT().StartTx(gomock.Any()).Return(txMock, nil)
287+
mockTransactionService.EXPECT().Commit(txMock).Return(nil)
288+
do.Provide(injector, func(i *do.Injector) (query.TransactionService, error) {
289+
return mockTransactionService, nil
290+
})
291+
// Act
292+
rr := httptest.NewRecorder()
293+
handler := chi.NewRouter()
294+
handler.Delete("/{id}", deleteData(injector))
295+
handler.ServeHTTP(rr, req)
296+
// Assert
297+
if status := rr.Code; status != http.StatusOK {
298+
t.Errorf("deleteData returned wrong status code: got %v want %v",
299+
status, http.StatusOK)
300+
}
301+
}
302+
303+
func TestDeleteKeyError(t *testing.T) {
304+
// Arrange
305+
keyId := uuid.New()
306+
req := httptest.NewRequest("DELETE", fmt.Sprintf("/%s", keyId.String()), nil)
307+
user := testutils.GenerateUser()
308+
req = testutils.AddUserContext(req, user)
309+
key := &models.SshKey{
310+
ID: keyId,
311+
UserID: user.ID,
312+
}
313+
314+
injector := do.New()
315+
ctrl := gomock.NewController(t)
316+
defer ctrl.Finish()
317+
mockUserRepo := repository.NewMockUserRepository(ctrl)
318+
txMock := pgx.NewMockTx(ctrl)
319+
mockUserRepo.EXPECT().GetUserKey(user.ID, keyId).Return(key, nil)
320+
mockUserRepo.EXPECT().DeleteUserKeyTx(gomock.Any(), keyId, txMock).Return(errors.New("error"))
321+
do.Provide(injector, func(i *do.Injector) (repository.UserRepository, error) {
322+
return mockUserRepo, nil
323+
})
324+
mockTransactionService := query.NewMockTransactionService(ctrl)
325+
mockTransactionService.EXPECT().StartTx(gomock.Any()).Return(txMock, nil)
326+
mockTransactionService.EXPECT().Rollback(txMock).Return(nil)
327+
do.Provide(injector, func(i *do.Injector) (query.TransactionService, error) {
328+
return mockTransactionService, nil
329+
})
330+
331+
// Act
332+
rr := httptest.NewRecorder()
333+
handler := chi.NewRouter()
334+
handler.Delete("/{id}", deleteData(injector))
335+
handler.ServeHTTP(rr, req)
336+
337+
// Assert
338+
339+
if status := rr.Code; status != http.StatusInternalServerError {
340+
t.Errorf("deleteData returned wrong status code: got %v want %v",
341+
status, http.StatusInternalServerError)
342+
}
343+
}

pkg/web/router/routes/setup.go

+1-18
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,7 @@ func initialSetup(i *do.Injector) http.HandlerFunc {
6767
w.WriteHeader(http.StatusInternalServerError)
6868
return
6969
}
70-
defer func() {
71-
rb := func(tx pgx.Tx) {
72-
err := txQueryService.Rollback(tx)
73-
if err != nil {
74-
log.Err(err).Msg("error rolling back transaction")
75-
}
76-
}
77-
if err != nil {
78-
rb(tx)
79-
} else {
80-
internalErr := txQueryService.Commit(tx)
81-
if internalErr != nil {
82-
log.Err(err).Msg("error committing transaction")
83-
rb(tx)
84-
w.WriteHeader(http.StatusInternalServerError)
85-
}
86-
}
87-
}()
70+
defer query.RollbackFunc(txQueryService, tx, w, &err)
8871
userRepo := do.MustInvoke[repository.UserRepository](i)
8972
user := &models.User{}
9073
user.Username = userDto.Username

0 commit comments

Comments
 (0)