Skip to content

Commit

Permalink
20 new rest endpoints for key management (#22)
Browse files Browse the repository at this point in the history
* working now

* working now

* start tests

* working on test

* tests pass

* bring back refactor

* remove comment

* add volumes to docker-compose
  • Loading branch information
therealpaulgg authored Oct 30, 2024
1 parent 5276782 commit a7ae1e9
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 44 deletions.
22 changes: 16 additions & 6 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ services:
max-size: 10m
ports:
- '3000:3000'
image: b44427a64de93c20123c068387b0adc0434434ba709fbd91dd03d33ade489c3e
container_name: sshdbg
image: ssh-sync-server-prerelease
container_name: ssh-sync-server
ssh-sync-db:
image: therealpaulgg/ssh-sync-db:latest
container_name: ssh-sync-db-debug
Expand All @@ -26,18 +26,28 @@ services:
- POSTGRES_DB=sshsync
restart: always
ssh-sync:
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
image: ssh-debug
container_name: ssh-sync
stdin_open: true # Allows Docker container to keep STDIN open
tty: true # Allocates a pseudo-TTY
volumes:
- ssh-sync-volume:/root
ssh-sync-2:
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
image: ssh-debug
container_name: ssh-sync-2
stdin_open: true # Allows Docker container to keep STDIN open
tty: true # Allocates a pseudo-TTY
volumes:
- ssh-sync-2-volume:/root
ssh-sync-3:
image: 62eab8fb32b34e0a2cf36e8635d810c20a38baa2d7beaf5b6918139339e23c23
image: ssh-debug
container_name: ssh-sync-3
stdin_open: true # Allows Docker container to keep STDIN open
tty: true # Allocates a pseudo-TTY
#http://ssh-sync-server-debug:3000
volumes:
- ssh-sync-3-volume:/root

volumes:
ssh-sync-volume:
ssh-sync-2-volume:
ssh-sync-3-volume:
21 changes: 21 additions & 0 deletions pkg/database/query/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package query

import (
"context"
"net/http"

"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"github.com/rs/zerolog/log"
"github.com/therealpaulgg/ssh-sync-server/pkg/database"
)

Expand Down Expand Up @@ -63,3 +65,22 @@ func (q *QueryServiceTxImpl[T]) Insert(tx pgx.Tx, query string, args ...interfac
_, err := tx.Exec(context.Background(), query, args...)
return err
}

func RollbackFunc(txQueryService TransactionService, tx pgx.Tx, w http.ResponseWriter, err *error) {
rb := func(tx pgx.Tx) {
err := txQueryService.Rollback(tx)
if err != nil {
log.Err(err).Msg("error rolling back transaction")
}
}
if *err != nil {
rb(tx)
} else {
internalErr := txQueryService.Commit(tx)
if internalErr != nil {
log.Err(internalErr).Msg("error committing transaction")
rb(tx)
w.WriteHeader(http.StatusInternalServerError)
}
}
}
3 changes: 1 addition & 2 deletions pkg/database/repository/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ func (repo *MachineRepo) DeleteMachine(id uuid.UUID) error {
if _, err := tx.Exec(context.TODO(), "delete from machines where id = $1", id); err != nil {
return err
}
err = tx.Commit(context.TODO())
return err
return tx.Commit(context.TODO())
}

func (repo *MachineRepo) GetMachine(id uuid.UUID) (*models.Machine, error) {
Expand Down
19 changes: 19 additions & 0 deletions pkg/database/repository/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ type UserRepository interface {
DeleteUser(id uuid.UUID) error
GetUserConfig(id uuid.UUID) ([]models.SshConfig, error)
GetUserKeys(id uuid.UUID) ([]models.SshKey, error)
GetUserKey(userId uuid.UUID, keyId uuid.UUID) (*models.SshKey, error)
AddAndUpdateKeys(user *models.User) error
AddAndUpdateKeysTx(user *models.User, tx pgx.Tx) error
AddAndUpdateConfig(user *models.User) error
AddAndUpdateConfigTx(user *models.User, tx pgx.Tx) error
DeleteUserKeyTx(user *models.User, id uuid.UUID, tx pgx.Tx) error
}

type UserRepo struct {
Expand Down Expand Up @@ -195,3 +197,20 @@ func (repo *UserRepo) AddAndUpdateConfigTx(user *models.User, tx pgx.Tx) error {
}
return nil
}

func (repo *UserRepo) GetUserKey(userId uuid.UUID, keyId uuid.UUID) (*models.SshKey, error) {
q := do.MustInvoke[query.QueryService[models.SshKey]](repo.Injector)
key, err := q.QueryOne("select * from ssh_keys where user_id = $1 and id = $2", userId, keyId)
if err != nil {
return nil, err
}
if key == nil {
return nil, sql.ErrNoRows
}
return key, nil
}

func (repo *UserRepo) DeleteUserKeyTx(user *models.User, id uuid.UUID, tx pgx.Tx) error {
_, err := tx.Exec(context.TODO(), "delete from ssh_keys where user_id = $1 and id = $2", user.ID, id)
return err
}
29 changes: 29 additions & 0 deletions pkg/database/repository/usermock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 41 additions & 18 deletions pkg/web/router/routes/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"

"github.com/go-chi/chi"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/rs/zerolog/log"
"github.com/samber/do"
Expand Down Expand Up @@ -115,24 +116,7 @@ func addData(i *do.Injector) http.HandlerFunc {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer func() {
rb := func(tx pgx.Tx) {
err := txQueryService.Rollback(tx)
if err != nil {
log.Err(err).Msg("error rolling back transaction")
}
}
if err != nil {
rb(tx)
} else {
internalErr := txQueryService.Commit(tx)
if internalErr != nil {
log.Err(err).Msg("error committing transaction")
rb(tx)
w.WriteHeader(http.StatusInternalServerError)
}
}
}()
defer query.RollbackFunc(txQueryService, tx, w, &err)
if err = userRepo.AddAndUpdateConfigTx(user, tx); err != nil {
log.Err(err).Msg("could not add config")
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -168,10 +152,49 @@ func addData(i *do.Injector) http.HandlerFunc {
}
}

func deleteData(i *do.Injector) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, ok := r.Context().Value(context_keys.UserContextKey).(*models.User)
if !ok {
log.Err(errors.New("could not get user from context"))
w.WriteHeader(http.StatusInternalServerError)
return
}
keyIdStr := chi.URLParam(r, "id")
keyId, err := uuid.Parse(keyIdStr)
if err != nil {
log.Err(err).Msg("could not parse key id")
w.WriteHeader(http.StatusBadRequest)
return
}
userRepo := do.MustInvoke[repository.UserRepository](i)
key, err := userRepo.GetUserKey(user.ID, keyId)
if err != nil {
log.Err(err).Msg("could not get key")
w.WriteHeader(http.StatusNotFound)
return
}
txQueryService := do.MustInvoke[query.TransactionService](i)
tx, err := txQueryService.StartTx(pgx.TxOptions{})
if err != nil {
log.Err(err).Msg("error starting transaction")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer query.RollbackFunc(txQueryService, tx, w, &err)
if err = userRepo.DeleteUserKeyTx(user, key.ID, tx); err != nil {
log.Err(err).Msg("could not delete key")
w.WriteHeader(http.StatusInternalServerError)
return
}
}
}

func DataRoutes(i *do.Injector) chi.Router {
r := chi.NewRouter()
r.Use(middleware.ConfigureAuth(i))
r.Get("/", getData(i))
r.Post("/", addData(i))
r.Delete("/key/{id}", deleteData(i))
return r
}
83 changes: 83 additions & 0 deletions pkg/web/router/routes/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"

"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/samber/do"
Expand Down Expand Up @@ -258,3 +260,84 @@ func TestAddDataError(t *testing.T) {
status, http.StatusOK)
}
}

func TestDeleteKey(t *testing.T) {
// Arrange
keyId := uuid.New()
req := httptest.NewRequest("DELETE", fmt.Sprintf("/%s", keyId.String()), nil)
user := testutils.GenerateUser()
req = testutils.AddUserContext(req, user)
key := &models.SshKey{
ID: keyId,
UserID: user.ID,
}

injector := do.New()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockUserRepo := repository.NewMockUserRepository(ctrl)
txMock := pgx.NewMockTx(ctrl)
mockUserRepo.EXPECT().GetUserKey(user.ID, keyId).Return(key, nil)
mockUserRepo.EXPECT().DeleteUserKeyTx(gomock.Any(), keyId, txMock).Return(nil)
do.Provide(injector, func(i *do.Injector) (repository.UserRepository, error) {
return mockUserRepo, nil
})
mockTransactionService := query.NewMockTransactionService(ctrl)
mockTransactionService.EXPECT().StartTx(gomock.Any()).Return(txMock, nil)
mockTransactionService.EXPECT().Commit(txMock).Return(nil)
do.Provide(injector, func(i *do.Injector) (query.TransactionService, error) {
return mockTransactionService, nil
})
// Act
rr := httptest.NewRecorder()
handler := chi.NewRouter()
handler.Delete("/{id}", deleteData(injector))
handler.ServeHTTP(rr, req)
// Assert
if status := rr.Code; status != http.StatusOK {
t.Errorf("deleteData returned wrong status code: got %v want %v",
status, http.StatusOK)
}
}

func TestDeleteKeyError(t *testing.T) {
// Arrange
keyId := uuid.New()
req := httptest.NewRequest("DELETE", fmt.Sprintf("/%s", keyId.String()), nil)
user := testutils.GenerateUser()
req = testutils.AddUserContext(req, user)
key := &models.SshKey{
ID: keyId,
UserID: user.ID,
}

injector := do.New()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockUserRepo := repository.NewMockUserRepository(ctrl)
txMock := pgx.NewMockTx(ctrl)
mockUserRepo.EXPECT().GetUserKey(user.ID, keyId).Return(key, nil)
mockUserRepo.EXPECT().DeleteUserKeyTx(gomock.Any(), keyId, txMock).Return(errors.New("error"))
do.Provide(injector, func(i *do.Injector) (repository.UserRepository, error) {
return mockUserRepo, nil
})
mockTransactionService := query.NewMockTransactionService(ctrl)
mockTransactionService.EXPECT().StartTx(gomock.Any()).Return(txMock, nil)
mockTransactionService.EXPECT().Rollback(txMock).Return(nil)
do.Provide(injector, func(i *do.Injector) (query.TransactionService, error) {
return mockTransactionService, nil
})

// Act
rr := httptest.NewRecorder()
handler := chi.NewRouter()
handler.Delete("/{id}", deleteData(injector))
handler.ServeHTTP(rr, req)

// Assert

if status := rr.Code; status != http.StatusInternalServerError {
t.Errorf("deleteData returned wrong status code: got %v want %v",
status, http.StatusInternalServerError)
}
}
19 changes: 1 addition & 18 deletions pkg/web/router/routes/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,7 @@ func initialSetup(i *do.Injector) http.HandlerFunc {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer func() {
rb := func(tx pgx.Tx) {
err := txQueryService.Rollback(tx)
if err != nil {
log.Err(err).Msg("error rolling back transaction")
}
}
if err != nil {
rb(tx)
} else {
internalErr := txQueryService.Commit(tx)
if internalErr != nil {
log.Err(err).Msg("error committing transaction")
rb(tx)
w.WriteHeader(http.StatusInternalServerError)
}
}
}()
defer query.RollbackFunc(txQueryService, tx, w, &err)
userRepo := do.MustInvoke[repository.UserRepository](i)
user := &models.User{}
user.Username = userDto.Username
Expand Down

0 comments on commit a7ae1e9

Please sign in to comment.