Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20 new rest endpoints for key management #22

Merged
merged 8 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading