Skip to content

Commit

Permalink
Update account username and email (#135)
Browse files Browse the repository at this point in the history
* Adding update account handler

* Adding update account feature

* Fixing account controller interface

* Fixing account controller mock

* Fixing account update handler

* Fixing lint

* Adding handler test

* Adding updation validate

* Testing update account handler

* Improving update account controller

* Testing update account controller

* Adding vuln as risk accepeted

hash 45aa5c46df5ba51d7e59da826544412352c189a6acf5707f941922181c94f989
  • Loading branch information
nathannascimentozup authored Nov 17, 2020
1 parent 562cd27 commit 1445845
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 14 deletions.
11 changes: 10 additions & 1 deletion development-kit/pkg/entities/auth/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ package auth

import (
"encoding/json"
accountEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/account"
"time"

accountEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/account"

"github.com/ZupIT/horusec/development-kit/pkg/enums/errors"
"github.com/ZupIT/horusec/development-kit/pkg/utils/crypto"
validation "github.com/go-ozzo/ozzo-validation/v4"
Expand Down Expand Up @@ -79,6 +80,14 @@ func (a *Account) Validate() error {
)
}

func (a *Account) UpdationValidate() error {
return validation.ValidateStruct(a,
validation.Field(
&a.Email, validation.When(a.Username == "", validation.Required), validation.Length(1, 255), is.Email),
validation.Field(&a.Username, validation.Length(1, 255), validation.When(a.Email == "", validation.Required)),
)
}

func (a *Account) GetTable() string {
return "accounts"
}
Expand Down
29 changes: 29 additions & 0 deletions development-kit/pkg/entities/auth/dto/update_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dto

import authEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/auth"

type UpdateAccount struct {
Email string `json:"email"`
Username string `json:"username"`
}

func (c *UpdateAccount) ToAccount() *authEntities.Account {
return &authEntities.Account{
Email: c.Email,
Username: c.Username,
}
}
30 changes: 30 additions & 0 deletions development-kit/pkg/entities/auth/dto/update_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dto

import (
"testing"

authEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/auth"
"github.com/stretchr/testify/assert"
)

func TestToAccount_UpdateAccount(t *testing.T) {
updateAccount := &UpdateAccount{}

t.Run("should success parse update account to account", func(t *testing.T) {
assert.IsType(t, &authEntities.Account{}, updateAccount.ToAccount())
})
}
1 change: 1 addition & 0 deletions development-kit/pkg/enums/errors/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ var ErrorUsernameAlreadyInUse = errors.New("{ACCOUNT} username already in use")
var ErrorRepositoryNameAlreadyInUse = errors.New("{ACCOUNT} repository name already in use")
var ErrorInvalidKeycloakToken = errors.New("{ACCOUNT} keycloak token without email or username")
var ErrorUserLoggedIsNotApplicationAdmin = errors.New("{ACCOUNT} user logged is not application admin")
var ErrorInvalidUpdateAccountData = errors.New("{ACCOUNT} the data to update account is not valid")
20 changes: 17 additions & 3 deletions development-kit/pkg/usecases/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"time"

"github.com/Nerzal/gocloak/v7"
authEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/auth"
"github.com/ZupIT/horusec/development-kit/pkg/entities/auth/dto"
Expand All @@ -28,9 +32,6 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/google/uuid"
"io"
"math/rand"
"time"
)

type IUseCases interface {
Expand All @@ -53,6 +54,7 @@ type IUseCases interface {
NewPasswordFromReadCloser(body io.ReadCloser) (password string, err error)
NewRefreshTokenFromReadCloser(body io.ReadCloser) (token string, err error)
NewValidateUniqueFromReadCloser(body io.ReadCloser) (validateUnique *dto.ValidateUnique, err error)
NewAccountUpdateFromReadCloser(body io.ReadCloser) (*authEntities.Account, error)
}

type UseCases struct {
Expand Down Expand Up @@ -203,6 +205,18 @@ func (u *UseCases) NewAccountFromReadCloser(body io.ReadCloser) (*authEntities.A
return account, account.Validate()
}

func (u *UseCases) NewAccountUpdateFromReadCloser(body io.ReadCloser) (*authEntities.Account, error) {
updateAccount := &dto.UpdateAccount{}
err := json.NewDecoder(body).Decode(&updateAccount)
_ = body.Close()
if err != nil {
return nil, err
}

account := updateAccount.ToAccount()
return account, account.UpdationValidate()
}

func (u *UseCases) NewEmailDataFromReadCloser(body io.ReadCloser) (data *dto.EmailData, err error) {
err = json.NewDecoder(body).Decode(&data)
_ = body.Close()
Expand Down
8 changes: 7 additions & 1 deletion horusec-auth/internal/controller/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package account

import (
"fmt"
"time"

SQL "github.com/ZupIT/horusec/development-kit/pkg/databases/relational"
repositoryAccount "github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/account"
repoAccountRepository "github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/account_repository"
Expand All @@ -35,7 +37,6 @@ import (
"github.com/ZupIT/horusec/development-kit/pkg/utils/env"
"github.com/ZupIT/horusec/horusec-auth/config/app"
"github.com/google/uuid"
"time"
)

type IAccount interface {
Expand All @@ -53,6 +54,7 @@ type IAccount interface {
DeleteAccount(accountID uuid.UUID) error
GetAccountIDByEmail(email string) (uuid.UUID, error)
GetAccountID(token string) (uuid.UUID, error)
UpdateAccount(account *authEntities.Account) error
}

type Account struct {
Expand Down Expand Up @@ -342,3 +344,7 @@ func (a *Account) GetAccountID(token string) (uuid.UUID, error) {

return uuid.Nil, errors.ErrorUnauthorized
}

func (a *Account) UpdateAccount(account *authEntities.Account) error {
return a.accountRepository.Update(account)
}
8 changes: 7 additions & 1 deletion horusec-auth/internal/controller/account/account_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
package account

import (
"time"

authEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/auth"
"github.com/ZupIT/horusec/development-kit/pkg/entities/auth/dto"
mockUtils "github.com/ZupIT/horusec/development-kit/pkg/utils/mock"
"github.com/google/uuid"
"github.com/stretchr/testify/mock"
"time"
)

type Mock struct {
Expand Down Expand Up @@ -96,3 +97,8 @@ func (m *Mock) GetAccountID(token string) (uuid.UUID, error) {
args := m.MethodCalled("GetAccountID")
return args.Get(0).(uuid.UUID), mockUtils.ReturnNilOrError(args, 1)
}

func (m *Mock) UpdateAccount(account *authEntities.Account) error {
args := m.MethodCalled("UpdateAccount", account)
return mockUtils.ReturnNilOrError(args, 0)
}
30 changes: 27 additions & 3 deletions horusec-auth/internal/controller/account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ package account

import (
"errors"
"os"
"testing"
"time"

"github.com/Nerzal/gocloak/v7"
"github.com/ZupIT/horusec/development-kit/pkg/databases/relational"
repositoryAccount "github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/account"
Expand All @@ -36,9 +40,6 @@ import (
"github.com/google/uuid"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)

func TestMock(t *testing.T) {
Expand Down Expand Up @@ -1051,3 +1052,26 @@ func TestGetAccountIDByEmail(t *testing.T) {
assert.Equal(t, uuid.Nil, accountID)
})
}

func TestUpdateAccount(t *testing.T) {
t.Run("should success update account with no errors", func(t *testing.T) {
brokerMock := &broker.Mock{}
mockRead := &relational.MockRead{}
mockWrite := &relational.MockWrite{}
cacheRepositoryMock := &cache.Mock{}

mockWrite.On("Update").Return(&response.Response{})

appConfig := app.NewConfig()
controller := NewAccountController(brokerMock, mockRead, mockWrite, cacheRepositoryMock, appConfig)
assert.NotNil(t, controller)

account := &authEntities.Account{
Email: "test@test.com",
Username: "test",
}

err := controller.UpdateAccount(account)
assert.NoError(t, err)
})
}
46 changes: 45 additions & 1 deletion horusec-auth/internal/handler/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
package account

import (
"net/http"

SQL "github.com/ZupIT/horusec/development-kit/pkg/databases/relational"
cacheRepository "github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/cache"
_ "github.com/ZupIT/horusec/development-kit/pkg/entities/account" // [swagger-import]
"github.com/ZupIT/horusec/development-kit/pkg/entities/auth"
"github.com/ZupIT/horusec/development-kit/pkg/entities/auth/dto"
"github.com/ZupIT/horusec/development-kit/pkg/enums/errors"
brokerLib "github.com/ZupIT/horusec/development-kit/pkg/services/broker"
Expand All @@ -29,7 +32,6 @@ import (
accountController "github.com/ZupIT/horusec/horusec-auth/internal/controller/account"
"github.com/go-chi/chi"
"github.com/google/uuid"
"net/http"
)

type Handler struct {
Expand Down Expand Up @@ -379,3 +381,45 @@ func (h *Handler) DeleteAccount(w http.ResponseWriter, r *http.Request) {

httpUtil.StatusNoContent(w)
}

// @Tags Account
// @Description Update account username and/or email
// @ID update-account
// @Accept json
// @Produce json
// @Success 200 {object} http.Response{content=string} "OK"
// @Failure 401 {object} http.Response{content=string} "UNAUTHORIZED"
// @Failure 500 {object} http.Response{content=string} "INTERNAL SERVER ERROR"
// @Router /api/account/delete [delete]
// @Security ApiKeyAuth
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
data, err := h.getAccountUpdateData(w, r)
if err != nil {
return
}

err = h.controller.UpdateAccount(data)
if err != nil {
httpUtil.StatusInternalServerError(w, err)
}

httpUtil.StatusOK(w, "account updated")
}

func (h *Handler) getAccountUpdateData(w http.ResponseWriter, r *http.Request) (*auth.Account, error) {
accountID, err := h.controller.GetAccountID(r.Header.Get("Authorization"))
if err != nil {
httpUtil.StatusUnauthorized(w, errors.ErrorDoNotHavePermissionToThisAction)
return nil, errors.ErrorDoNotHavePermissionToThisAction
}

data, err := h.useCases.NewAccountUpdateFromReadCloser(r.Body)
if err != nil {
httpUtil.StatusBadRequest(w, errors.ErrorInvalidUpdateAccountData)
return nil, errors.ErrorInvalidUpdateAccountData
}

data.AccountID = accountID

return data, nil
}
62 changes: 59 additions & 3 deletions horusec-auth/internal/handler/account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/ZupIT/horusec/development-kit/pkg/databases/relational"
"github.com/ZupIT/horusec/development-kit/pkg/databases/relational/repository/cache"
authEntities "github.com/ZupIT/horusec/development-kit/pkg/entities/auth"
Expand All @@ -36,9 +40,6 @@ import (
"github.com/google/uuid"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)

func TestOptions(t *testing.T) {
Expand Down Expand Up @@ -983,3 +984,58 @@ func TestDeleteAccount(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}

func TestUpdateAccount(t *testing.T) {
t.Run("should return status code 200 when updated with success", func(t *testing.T) {
mockWrite := &relational.MockWrite{}

account := &authEntities.Account{AccountID: uuid.New(), Email: "test@test.com", Username: "test"}
token, _, _ := jwt.CreateToken(account, nil)
mockWrite.On("Update").Return(&response.Response{})

appConfig := app.NewConfig()
handler := NewHandler(nil, nil, mockWrite, nil, appConfig)
r, _ := http.NewRequest(http.MethodPatch, "api/account/update", bytes.NewReader(account.ToBytes()))
r.Header.Add("Authorization", token)
w := httptest.NewRecorder()

handler.Update(w, r)

assert.Equal(t, http.StatusOK, w.Code)
})

t.Run("should return status code 401 when request does not have a token", func(t *testing.T) {
mockWrite := &relational.MockWrite{}

account := &authEntities.Account{AccountID: uuid.New(), Email: "test@test.com", Username: "test"}
mockWrite.On("Update").Return(&response.Response{})

appConfig := app.NewConfig()
handler := NewHandler(nil, nil, mockWrite, nil, appConfig)
r, _ := http.NewRequest(http.MethodPatch, "api/account/update", bytes.NewReader(account.ToBytes()))
w := httptest.NewRecorder()

handler.Update(w, r)

assert.Equal(t, http.StatusUnauthorized, w.Code)
})

t.Run("should return status code 500 when update fails", func(t *testing.T) {
mockWrite := &relational.MockWrite{}

response := &response.Response{}
mockWrite.On("Update").Return(response.SetError(errors.New("test")))
account := &authEntities.Account{AccountID: uuid.New(), Email: "test@test.com", Username: "test"}
token, _, _ := jwt.CreateToken(account, nil)

appConfig := app.NewConfig()
handler := NewHandler(nil, nil, mockWrite, nil, appConfig)
r, _ := http.NewRequest(http.MethodPatch, "api/account/update", bytes.NewReader(account.ToBytes()))
r.Header.Add("Authorization", token)
w := httptest.NewRecorder()

handler.Update(w, r)

assert.Equal(t, http.StatusInternalServerError, w.Code)
})
}
Loading

0 comments on commit 1445845

Please sign in to comment.