From 098b1a8b889d8c843a32ad630e1829da3606aef8 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Tue, 9 Jan 2024 23:13:17 +0700 Subject: [PATCH 01/11] feat: update proto --- go.mod | 2 +- go.sum | 2 ++ internal/service/auth/auth.service.go | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a93ea58..9215924 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/isd-sgcu/johnjud-go-proto v0.1.5 // indirect + github.com/isd-sgcu/johnjud-go-proto v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.4.3 // indirect diff --git a/go.sum b/go.sum index c6fc2ba..9791fa8 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/isd-sgcu/johnjud-go-proto v0.1.2 h1:gp1MGHCnJdeuUEI4yCbvpQJ1BfLzR9Tr9 github.com/isd-sgcu/johnjud-go-proto v0.1.2/go.mod h1:1OK6aiCgtXQiLhxp0r6iLEejYIRpckWQZDrCZ9Trbo4= github.com/isd-sgcu/johnjud-go-proto v0.1.5 h1:ZhMb73xXOSM2OlsfQ8wv6rmbfSIrXwbBumIx/vtdgHc= github.com/isd-sgcu/johnjud-go-proto v0.1.5/go.mod h1:1OK6aiCgtXQiLhxp0r6iLEejYIRpckWQZDrCZ9Trbo4= +github.com/isd-sgcu/johnjud-go-proto v0.3.1 h1:WyWfzl+5nWOw3AmINtcTfojg4CJh8ZRNbZC6qA//OXU= +github.com/isd-sgcu/johnjud-go-proto v0.3.1/go.mod h1:1OK6aiCgtXQiLhxp0r6iLEejYIRpckWQZDrCZ9Trbo4= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/internal/service/auth/auth.service.go b/internal/service/auth/auth.service.go index 2695458..7b205b5 100644 --- a/internal/service/auth/auth.service.go +++ b/internal/service/auth/auth.service.go @@ -151,3 +151,11 @@ func (s *serviceImpl) SignOut(_ context.Context, request *authProto.SignOutReque return &authProto.SignOutResponse{IsSuccess: true}, nil } + +func (s *serviceImpl) ForgotPassword(_ context.Context, request *authProto.ForgotPasswordRequest) (*authProto.ForgotPasswordResponse, error) { + return nil, nil +} + +func (s *serviceImpl) ResetPassword(_ context.Context, request *authProto.ResetPasswordRequest) (*authProto.ResetPasswordResponse, error) { + return nil, nil +} From 644a0a9f3ab0c088a7be6e0fa79b750c9dc7ed4a Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 01:28:32 +0700 Subject: [PATCH 02/11] feat: implement resetPasswordTokenCache method --- internal/domain/dto/token/token.dto.go | 4 ++ internal/service/token/token.service.go | 56 +++++++++++++++++++++---- pkg/service/token/token.service.go | 3 ++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/internal/domain/dto/token/token.dto.go b/internal/domain/dto/token/token.dto.go index e9450f0..523a2b9 100644 --- a/internal/domain/dto/token/token.dto.go +++ b/internal/domain/dto/token/token.dto.go @@ -29,3 +29,7 @@ type RefreshTokenCache struct { UserID string `json:"user_id"` Role constant.Role `json:"role"` } + +type ResetPasswordTokenCache struct { + UserID string `json:"user_id"` +} diff --git a/internal/service/token/token.service.go b/internal/service/token/token.service.go index ea4dee7..fd722a3 100644 --- a/internal/service/token/token.service.go +++ b/internal/service/token/token.service.go @@ -17,18 +17,20 @@ import ( ) type serviceImpl struct { - jwtService jwt.Service - accessTokenCache cache.Repository - refreshTokenCache cache.Repository - uuidUtil utils.IUuidUtil + jwtService jwt.Service + accessTokenCache cache.Repository + refreshTokenCache cache.Repository + resetPasswordTokenCache cache.Repository + uuidUtil utils.IUuidUtil } -func NewService(jwtService jwt.Service, accessTokenCache cache.Repository, refreshTokenCache cache.Repository, uuidUtil utils.IUuidUtil) token.Service { +func NewService(jwtService jwt.Service, accessTokenCache cache.Repository, refreshTokenCache cache.Repository, resetPasswordTokenCache cache.Repository, uuidUtil utils.IUuidUtil) token.Service { return &serviceImpl{ - jwtService: jwtService, - accessTokenCache: accessTokenCache, - refreshTokenCache: refreshTokenCache, - uuidUtil: uuidUtil, + jwtService: jwtService, + accessTokenCache: accessTokenCache, + refreshTokenCache: refreshTokenCache, + resetPasswordTokenCache: resetPasswordTokenCache, + uuidUtil: uuidUtil, } } @@ -145,3 +147,39 @@ func (s *serviceImpl) RemoveRefreshTokenCache(refreshToken string) error { return nil } + +func (s *serviceImpl) CreateResetPasswordToken(userId string) (string, error) { + resetPasswordToken := s.CreateRefreshToken() + tokenCache := &tokenDto.ResetPasswordTokenCache{ + UserID: userId, + } + err := s.resetPasswordTokenCache.SetValue(resetPasswordToken, tokenCache, 900) + if err != nil { + return "", err + } + return resetPasswordToken, nil +} + +func (s *serviceImpl) FindResetPasswordToken(token string) (*tokenDto.ResetPasswordTokenCache, error) { + tokenCache := &tokenDto.ResetPasswordTokenCache{} + err := s.resetPasswordTokenCache.GetValue(token, tokenCache) + if err != nil { + if err != redis.Nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + return tokenCache, nil +} + +func (s *serviceImpl) RemoveResetPasswordToken(token string) error { + err := s.resetPasswordTokenCache.DeleteValue(token) + if err != nil { + if err != redis.Nil { + return err + } + } + + return nil +} diff --git a/pkg/service/token/token.service.go b/pkg/service/token/token.service.go index 7ba74b3..2948dae 100644 --- a/pkg/service/token/token.service.go +++ b/pkg/service/token/token.service.go @@ -13,4 +13,7 @@ type Service interface { RemoveAccessTokenCache(authSessionId string) error FindRefreshTokenCache(refreshToken string) (*tokenDto.RefreshTokenCache, error) RemoveRefreshTokenCache(refreshToken string) error + CreateResetPasswordToken(userId string) (string, error) + FindResetPasswordToken(token string) (*tokenDto.ResetPasswordTokenCache, error) + RemoveResetPasswordToken(token string) error } From 1a6348ef58b8cd27c18b96d8e848add73254aad5 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 01:28:43 +0700 Subject: [PATCH 03/11] feat: add unit test --- internal/service/token/token.service_test.go | 212 +++++++++++++++++-- 1 file changed, 193 insertions(+), 19 deletions(-) diff --git a/internal/service/token/token.service_test.go b/internal/service/token/token.service_test.go index a0e0255..fde9823 100644 --- a/internal/service/token/token.service_test.go +++ b/internal/service/token/token.service_test.go @@ -83,6 +83,7 @@ func (t *TokenServiceTest) TestCreateCredentialSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("SignAuth", t.userId, t.role, t.authSessionId).Return(t.accessToken, nil) @@ -91,7 +92,7 @@ func (t *TokenServiceTest) TestCreateCredentialSuccess() { accessTokenRepo.EXPECT().SetValue(t.authSessionId, accessTokenCache, t.jwtConfig.ExpiresIn).Return(nil) refreshTokenRepo.EXPECT().SetValue(t.refreshToken.String(), refreshTokenCache, t.jwtConfig.RefreshTokenTTL).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateCredential(t.userId, t.role, t.authSessionId) assert.Nil(t.T(), err) @@ -109,11 +110,12 @@ func (t *TokenServiceTest) TestCreateCredentialSignAuthFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("SignAuth", t.userId, t.role, t.authSessionId).Return("", signAuthError) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateCredential(t.userId, t.role, t.authSessionId) assert.Nil(t.T(), actual) @@ -134,6 +136,7 @@ func (t *TokenServiceTest) TestCreateCredentialSetAccessTokenFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("SignAuth", t.userId, t.role, t.authSessionId).Return(t.accessToken, nil) @@ -141,7 +144,7 @@ func (t *TokenServiceTest) TestCreateCredentialSetAccessTokenFailed() { uuidUtil.On("GetNewUUID").Return(t.refreshToken) accessTokenRepo.EXPECT().SetValue(t.authSessionId, accessTokenCache, t.jwtConfig.ExpiresIn).Return(setCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateCredential(t.userId, t.role, t.authSessionId) assert.Nil(t.T(), actual) @@ -167,6 +170,7 @@ func (t *TokenServiceTest) TestCreateCredentialSetRefreshTokenFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("SignAuth", t.userId, t.role, t.authSessionId).Return(t.accessToken, nil) @@ -175,7 +179,7 @@ func (t *TokenServiceTest) TestCreateCredentialSetRefreshTokenFailed() { accessTokenRepo.EXPECT().SetValue(t.authSessionId, accessTokenCache, t.jwtConfig.ExpiresIn).Return(nil) refreshTokenRepo.EXPECT().SetValue(t.refreshToken.String(), refreshTokenCache, t.jwtConfig.RefreshTokenTTL).Return(setCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateCredential(t.userId, t.role, t.authSessionId) assert.Nil(t.T(), actual) @@ -207,13 +211,14 @@ func (t *TokenServiceTest) TestValidateSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) accessTokenRepo.EXPECT().GetValue(payloads["auth_session_id"].(string), accessTokenCache).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), err) @@ -241,12 +246,13 @@ func (t *TokenServiceTest) TestValidateInvalidIssuer() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), actual) @@ -273,12 +279,13 @@ func (t *TokenServiceTest) TestValidateExpireToken() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), actual) @@ -293,11 +300,12 @@ func (t *TokenServiceTest) TestValidateVerifyFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(nil, expected) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), actual) @@ -325,13 +333,14 @@ func (t *TokenServiceTest) TestValidateGetCacheKeyNotFound() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) accessTokenRepo.EXPECT().GetValue(payloads["auth_session_id"].(string), accessTokenCache).Return(redis.Nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), actual) @@ -360,13 +369,14 @@ func (t *TokenServiceTest) TestValidateGetCacheInternalFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", t.validateToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) accessTokenRepo.EXPECT().GetValue(payloads["auth_session_id"].(string), accessTokenCache).Return(getCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(t.validateToken) assert.Nil(t.T(), actual) @@ -395,13 +405,14 @@ func (t *TokenServiceTest) TestValidateInvalidToken() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} jwtService.On("VerifyAuth", invalidToken).Return(jwtToken, nil) jwtService.On("GetConfig").Return(t.jwtConfig) accessTokenRepo.EXPECT().GetValue(payloads["auth_session_id"].(string), accessTokenCache).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.Validate(invalidToken) assert.Nil(t.T(), actual) @@ -416,11 +427,12 @@ func (t *TokenServiceTest) TestCreateRefreshTokenSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} uuidUtil.On("GetNewUUID").Return(t.refreshToken) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual := tokenSvc.CreateRefreshToken() assert.Equal(t.T(), expected, actual) @@ -432,11 +444,12 @@ func (t *TokenServiceTest) TestRemoveAccessTokenCacheSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} accessTokenRepo.EXPECT().DeleteValue(t.authSessionId).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) err := tokenSvc.RemoveAccessTokenCache(t.authSessionId) assert.Nil(t.T(), err) @@ -452,11 +465,12 @@ func (t *TokenServiceTest) TestRemoveAccessTokenCacheDeleteInternalFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} accessTokenRepo.EXPECT().DeleteValue(t.authSessionId).Return(deleteAccessTokenCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) err := tokenSvc.RemoveAccessTokenCache(t.authSessionId) assert.Equal(t.T(), expected, err) @@ -470,11 +484,12 @@ func (t *TokenServiceTest) TestFindRefreshTokenCacheSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} refreshTokenRepo.EXPECT().GetValue(t.refreshToken.String(), &tokenDto.RefreshTokenCache{}).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.FindRefreshTokenCache(t.refreshToken.String()) assert.Nil(t.T(), err) @@ -491,11 +506,12 @@ func (t *TokenServiceTest) TestFindRefreshTokenCacheInvalid() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} refreshTokenRepo.EXPECT().GetValue(t.refreshToken.String(), &tokenDto.RefreshTokenCache{}).Return(getCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.FindRefreshTokenCache(t.refreshToken.String()) assert.Nil(t.T(), actual) @@ -512,11 +528,12 @@ func (t *TokenServiceTest) TestFindRefreshTokenCacheInternalError() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} refreshTokenRepo.EXPECT().GetValue(t.refreshToken.String(), &tokenDto.RefreshTokenCache{}).Return(getCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.FindRefreshTokenCache(t.refreshToken.String()) assert.Nil(t.T(), actual) @@ -529,11 +546,12 @@ func (t *TokenServiceTest) TestRemoveRefreshTokenCacheSuccess() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} refreshTokenRepo.EXPECT().DeleteValue(t.refreshToken.String()).Return(nil) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) err := tokenSvc.RemoveRefreshTokenCache(t.refreshToken.String()) assert.Nil(t.T(), err) @@ -549,12 +567,168 @@ func (t *TokenServiceTest) TestRemoveRefreshTokenCacheDeleteInternalFailed() { jwtService := jwt.JwtServiceMock{} accessTokenRepo := mock_cache.NewMockRepository(controller) refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) uuidUtil := utils.UuidUtilMock{} refreshTokenRepo.EXPECT().DeleteValue(t.refreshToken.String()).Return(deleteRefreshTokenCacheErr) - tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, &uuidUtil) + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) err := tokenSvc.RemoveRefreshTokenCache(t.refreshToken.String()) assert.Equal(t.T(), expected, err) } + +func (t *TokenServiceTest) TestCreateResetPasswordTokenSuccess() { + tokenCache := &tokenDto.ResetPasswordTokenCache{ + UserID: t.userId, + } + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + uuidUtil.On("GetNewUUID").Return(t.refreshToken) + resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, 900).Return(nil) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + actual, err := tokenSvc.CreateResetPasswordToken(t.userId) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), t.refreshToken.String(), actual) +} + +func (t *TokenServiceTest) TestCreateResetPasswordTokenFailed() { + tokenCache := &tokenDto.ResetPasswordTokenCache{ + UserID: t.userId, + } + cacheErr := errors.New("Internal error") + + expected := cacheErr + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + uuidUtil.On("GetNewUUID").Return(t.refreshToken) + resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, 900).Return(cacheErr) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + actual, err := tokenSvc.CreateResetPasswordToken(t.userId) + + assert.Equal(t.T(), "", actual) + assert.Equal(t.T(), expected, err) +} + +func (t *TokenServiceTest) TestFindResetPasswordTokenSuccess() { + tokenCache := &tokenDto.ResetPasswordTokenCache{} + + expected := tokenCache + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + resetPasswordTokenRepo.EXPECT().GetValue(t.refreshToken.String(), tokenCache).Return(nil) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + actual, err := tokenSvc.FindResetPasswordToken(t.refreshToken.String()) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), expected, actual) +} + +func (t *TokenServiceTest) TestFindResetPasswordTokenNotFound() { + tokenCache := &tokenDto.ResetPasswordTokenCache{} + cacheErr := redis.Nil + + expected := status.Error(codes.InvalidArgument, cacheErr.Error()) + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + resetPasswordTokenRepo.EXPECT().GetValue(t.refreshToken.String(), tokenCache).Return(cacheErr) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + actual, err := tokenSvc.FindResetPasswordToken(t.refreshToken.String()) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *TokenServiceTest) TestFindResetPasswordTokenInternalError() { + tokenCache := &tokenDto.ResetPasswordTokenCache{} + cacheErr := errors.New("Internal error") + + expected := status.Error(codes.Internal, cacheErr.Error()) + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + resetPasswordTokenRepo.EXPECT().GetValue(t.refreshToken.String(), tokenCache).Return(cacheErr) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + actual, err := tokenSvc.FindResetPasswordToken(t.refreshToken.String()) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *TokenServiceTest) TestRemoveResetPasswordTokenSuccess() { + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + resetPasswordTokenRepo.EXPECT().DeleteValue(t.refreshToken.String()).Return(nil) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + err := tokenSvc.RemoveResetPasswordToken(t.refreshToken.String()) + + assert.Nil(t.T(), err) +} + +func (t *TokenServiceTest) TestRemoveResetPasswordTokenFailed() { + cacheErr := errors.New("Internal error") + + expected := cacheErr + + controller := gomock.NewController(t.T()) + + jwtService := jwt.JwtServiceMock{} + accessTokenRepo := mock_cache.NewMockRepository(controller) + refreshTokenRepo := mock_cache.NewMockRepository(controller) + resetPasswordTokenRepo := mock_cache.NewMockRepository(controller) + uuidUtil := utils.UuidUtilMock{} + + resetPasswordTokenRepo.EXPECT().DeleteValue(t.refreshToken.String()).Return(cacheErr) + + tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) + err := tokenSvc.RemoveResetPasswordToken(t.refreshToken.String()) + + assert.Equal(t.T(), expected, err) +} From d7ec7ef521bb4f193d27f3b47b6982b708726ada Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 01:32:13 +0700 Subject: [PATCH 04/11] feat: add method on tokenServiceMock --- mocks/service/token/token.mock.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mocks/service/token/token.mock.go b/mocks/service/token/token.mock.go index df64d75..b294964 100644 --- a/mocks/service/token/token.mock.go +++ b/mocks/service/token/token.mock.go @@ -52,3 +52,26 @@ func (m *TokenServiceMock) RemoveRefreshTokenCache(refreshToken string) error { args := m.Called(refreshToken) return args.Error(0) } + +func (m *TokenServiceMock) CreateResetPasswordToken(userId string) (string, error) { + args := m.Called(userId) + if args.Get(0) != "" { + return args.Get(0).(string), nil + } + + return "", args.Error(1) +} + +func (m *TokenServiceMock) FindResetPasswordToken(token string) (*tokenDto.ResetPasswordTokenCache, error) { + args := m.Called(token) + if args.Get(0) != nil { + return args.Get(0).(*tokenDto.ResetPasswordTokenCache), nil + } + + return nil, args.Error(1) +} + +func (m *TokenServiceMock) RemoveResetPasswordToken(token string) error { + args := m.Called(token) + return args.Error(0) +} From bc1a971a56fa756076ab13a0f4e44cdfdf4f463a Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 22:19:01 +0700 Subject: [PATCH 05/11] feat: add sendgrid library --- cfgldr/config.go | 7 +++++++ config/config.example.yaml | 7 ++++++- go.mod | 2 ++ go.sum | 4 ++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cfgldr/config.go b/cfgldr/config.go index 2983ef4..b06afbb 100644 --- a/cfgldr/config.go +++ b/cfgldr/config.go @@ -34,11 +34,18 @@ type Redis struct { Dbnum int `mapstructure:"dbnum"` } +type Sendgrid struct { + ApiKey string `mapstructure:"api_key"` + Name string `mapstructure:"name"` + Address string `mapstructure:"address"` +} + type Config struct { App App `mapstructure:"app"` Database Database `mapstructure:"database"` Jwt Jwt `mapstructure:"jwt"` Redis Redis `mapstructure:"redis"` + Sendgrid Sendgrid `mapstructure:"sendgrid"` } func LoadConfig() (config *Config, err error) { diff --git a/config/config.example.yaml b/config/config.example.yaml index ad75341..5278960 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -20,4 +20,9 @@ redis: host: localhost port: 6379 password: "" - dbnum: 0 \ No newline at end of file + dbnum: 0 + +sendgrid: + api_key: + name: johnjud + address: johnjud@gmail.com \ No newline at end of file diff --git a/go.mod b/go.mod index 9215924..f442839 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,8 @@ require ( github.com/rs/zerolog v1.31.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sendgrid/rest v2.6.9+incompatible // indirect + github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index 9791fa8..bfe40ae 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,10 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= +github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= From 9fde4a796a613063b1cfe2d43c7ba6266faa29c1 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 22:28:51 +0700 Subject: [PATCH 06/11] feat: implement send email service --- internal/service/email/email.service.go | 30 +++++++++++++++++++++++++ mocks/service/email/email.mock.go | 12 ++++++++++ pkg/service/email/email.service.go | 5 +++++ 3 files changed, 47 insertions(+) create mode 100644 internal/service/email/email.service.go create mode 100644 mocks/service/email/email.mock.go create mode 100644 pkg/service/email/email.service.go diff --git a/internal/service/email/email.service.go b/internal/service/email/email.service.go new file mode 100644 index 0000000..8ebdab2 --- /dev/null +++ b/internal/service/email/email.service.go @@ -0,0 +1,30 @@ +package email + +import ( + "github.com/isd-sgcu/johnjud-auth/cfgldr" + "github.com/isd-sgcu/johnjud-auth/pkg/service/email" + "github.com/sendgrid/sendgrid-go" + "github.com/sendgrid/sendgrid-go/helpers/mail" +) + +type serviceImpl struct { + config cfgldr.Sendgrid + client *sendgrid.Client +} + +func (s *serviceImpl) SendEmail(subject string, toName string, toAddress string, content string) error { + from := mail.NewEmail(s.config.Name, s.config.Address) + to := mail.NewEmail(toName, toAddress) + message := mail.NewSingleEmail(from, subject, to, content, content) + + _, err := s.client.Send(message) + if err != nil { + return err + } + return nil +} + +func NewService(config cfgldr.Sendgrid) email.Service { + client := sendgrid.NewSendClient(config.ApiKey) + return &serviceImpl{config: config, client: client} +} diff --git a/mocks/service/email/email.mock.go b/mocks/service/email/email.mock.go new file mode 100644 index 0000000..3910f63 --- /dev/null +++ b/mocks/service/email/email.mock.go @@ -0,0 +1,12 @@ +package email + +import "github.com/stretchr/testify/mock" + +type EmailServiceMock struct { + mock.Mock +} + +func (m *EmailServiceMock) SendEmail(subject string, toName string, toAddress string, content string) error { + args := m.Called(subject, toName, toAddress, content) + return args.Error(0) +} diff --git a/pkg/service/email/email.service.go b/pkg/service/email/email.service.go new file mode 100644 index 0000000..f09e612 --- /dev/null +++ b/pkg/service/email/email.service.go @@ -0,0 +1,5 @@ +package email + +type Service interface { + SendEmail(subject string, toName string, toAddress string, content string) error +} From 44da0c811537e69ae33092dc2af25da186394367 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 22:35:14 +0700 Subject: [PATCH 07/11] feat: add field in auth service --- cfgldr/config.go | 5 ++ cmd/main.go | 4 +- config/config.example.yaml | 3 + internal/service/auth/auth.service.go | 8 ++- internal/service/auth/auth.service_test.go | 70 +++++++++++++++------- 5 files changed, 67 insertions(+), 23 deletions(-) diff --git a/cfgldr/config.go b/cfgldr/config.go index b06afbb..d6ce2f3 100644 --- a/cfgldr/config.go +++ b/cfgldr/config.go @@ -34,6 +34,10 @@ type Redis struct { Dbnum int `mapstructure:"dbnum"` } +type Auth struct { + ClientURL string `mapstructure:"client_url"` +} + type Sendgrid struct { ApiKey string `mapstructure:"api_key"` Name string `mapstructure:"name"` @@ -45,6 +49,7 @@ type Config struct { Database Database `mapstructure:"database"` Jwt Jwt `mapstructure:"jwt"` Redis Redis `mapstructure:"redis"` + Auth Auth `mapstructure:"auth"` Sendgrid Sendgrid `mapstructure:"sendgrid"` } diff --git a/cmd/main.go b/cmd/main.go index bc0ad6d..605236d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,7 @@ import ( cacheRp "github.com/isd-sgcu/johnjud-auth/internal/repository/cache" userRp "github.com/isd-sgcu/johnjud-auth/internal/repository/user" authSvc "github.com/isd-sgcu/johnjud-auth/internal/service/auth" + emailSvc "github.com/isd-sgcu/johnjud-auth/internal/service/email" jwtSvc "github.com/isd-sgcu/johnjud-auth/internal/service/jwt" tokenSvc "github.com/isd-sgcu/johnjud-auth/internal/service/token" userSvc "github.com/isd-sgcu/johnjud-auth/internal/service/user" @@ -137,7 +138,8 @@ func main() { jwtService := jwtSvc.NewService(conf.Jwt, jwtStrategy, jwtUtil) tokenService := tokenSvc.NewService(jwtService, accessTokenCache, refreshTokenCache, uuidUtil) - authService := authSvc.NewService(authRepo, userRepo, tokenService, bcryptUtil) + emailService := emailSvc.NewService(conf.Sendgrid) + authService := authSvc.NewService(authRepo, userRepo, tokenService, emailService, bcryptUtil, conf.Auth) grpc_health_v1.RegisterHealthServer(grpcServer, health.NewServer()) authPb.RegisterAuthServiceServer(grpcServer, authService) diff --git a/config/config.example.yaml b/config/config.example.yaml index 5278960..0fb9ced 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -22,6 +22,9 @@ redis: password: "" dbnum: 0 +auth: + client_url: localhost:3000 + sendgrid: api_key: name: johnjud diff --git a/internal/service/auth/auth.service.go b/internal/service/auth/auth.service.go index 7b205b5..e4d2cc9 100644 --- a/internal/service/auth/auth.service.go +++ b/internal/service/auth/auth.service.go @@ -2,11 +2,13 @@ package auth import ( "context" + "github.com/isd-sgcu/johnjud-auth/cfgldr" "github.com/isd-sgcu/johnjud-auth/internal/constant" "github.com/isd-sgcu/johnjud-auth/internal/domain/model" "github.com/isd-sgcu/johnjud-auth/internal/utils" "github.com/isd-sgcu/johnjud-auth/pkg/repository/auth" "github.com/isd-sgcu/johnjud-auth/pkg/repository/user" + "github.com/isd-sgcu/johnjud-auth/pkg/service/email" "github.com/isd-sgcu/johnjud-auth/pkg/service/token" authProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/auth/auth/v1" @@ -21,15 +23,19 @@ type serviceImpl struct { authRepo auth.Repository userRepo user.Repository tokenService token.Service + emailService email.Service bcryptUtil utils.IBcryptUtil + config cfgldr.Auth } -func NewService(authRepo auth.Repository, userRepo user.Repository, tokenService token.Service, bcryptUtil utils.IBcryptUtil) authProto.AuthServiceServer { +func NewService(authRepo auth.Repository, userRepo user.Repository, tokenService token.Service, emailService email.Service, bcryptUtil utils.IBcryptUtil, config cfgldr.Auth) authProto.AuthServiceServer { return &serviceImpl{ authRepo: authRepo, userRepo: userRepo, tokenService: tokenService, + emailService: emailService, bcryptUtil: bcryptUtil, + config: config, } } diff --git a/internal/service/auth/auth.service_test.go b/internal/service/auth/auth.service_test.go index bd9325f..90dc47a 100644 --- a/internal/service/auth/auth.service_test.go +++ b/internal/service/auth/auth.service_test.go @@ -2,11 +2,13 @@ package auth import ( "context" + "github.com/isd-sgcu/johnjud-auth/cfgldr" "github.com/isd-sgcu/johnjud-auth/internal/constant" tokenDto "github.com/isd-sgcu/johnjud-auth/internal/domain/dto/token" "github.com/isd-sgcu/johnjud-auth/internal/domain/model" "github.com/isd-sgcu/johnjud-auth/mocks/repository/auth" "github.com/isd-sgcu/johnjud-auth/mocks/repository/user" + "github.com/isd-sgcu/johnjud-auth/mocks/service/email" "github.com/isd-sgcu/johnjud-auth/mocks/service/token" "github.com/isd-sgcu/johnjud-auth/mocks/utils" "testing" @@ -32,6 +34,7 @@ type AuthServiceTest struct { signOutRequest *authProto.SignOutRequest refreshTokenRequest *authProto.RefreshTokenRequest validateRequest *authProto.ValidateRequest + authConfig cfgldr.Auth } func TestAuthService(t *testing.T) { @@ -59,6 +62,9 @@ func (t *AuthServiceTest) SetupTest() { refreshTokenRequest := &authProto.RefreshTokenRequest{ RefreshToken: faker.UUIDDigit(), } + authConfig := cfgldr.Auth{ + ClientURL: "localhost", + } t.ctx = ctx t.signupRequest = signupRequest @@ -66,6 +72,7 @@ func (t *AuthServiceTest) SetupTest() { t.signOutRequest = signOutRequest t.validateRequest = validateRequest t.refreshTokenRequest = refreshTokenRequest + t.authConfig = authConfig } func (t *AuthServiceTest) TestSignupSuccess() { @@ -102,12 +109,13 @@ func (t *AuthServiceTest) TestSignupSuccess() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} bcryptUtil.On("GenerateHashedPassword", t.signupRequest.Password).Return(hashedPassword, nil) userRepo.On("Create", newUser).Return(createdUser, nil) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignUp(t.ctx, t.signupRequest) assert.Nil(t.T(), err) @@ -124,11 +132,12 @@ func (t *AuthServiceTest) TestSignupHashPasswordFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} bcryptUtil.On("GenerateHashedPassword", t.signupRequest.Password).Return("", hashPasswordErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignUp(t.ctx, t.signupRequest) status, ok := status.FromError(err) @@ -157,12 +166,13 @@ func (t *AuthServiceTest) TestSignupCreateUserDuplicateConstraint() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} bcryptUtil.On("GenerateHashedPassword", t.signupRequest.Password).Return(hashedPassword, nil) userRepo.On("Create", newUser).Return(nil, createUserErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignUp(t.ctx, t.signupRequest) status, ok := status.FromError(err) @@ -191,12 +201,13 @@ func (t *AuthServiceTest) TestSignupCreateUserInternalFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} bcryptUtil.On("GenerateHashedPassword", t.signupRequest.Password).Return(hashedPassword, nil) userRepo.On("Create", newUser).Return(nil, createUserErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignUp(t.ctx, t.signupRequest) status, ok := status.FromError(err) @@ -234,6 +245,7 @@ func (t *AuthServiceTest) TestSignInSuccess() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} userRepo.On("FindByEmail", t.signInRequest.Email, &model.User{}).Return(existUser, nil) @@ -241,7 +253,7 @@ func (t *AuthServiceTest) TestSignInSuccess() { authRepo.EXPECT().Create(newAuthSession).Return(nil) tokenService.On("CreateCredential", existUser.ID.String(), existUser.Role, newAuthSession.ID.String()).Return(credential, nil) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignIn(t.ctx, t.signInRequest) assert.Nil(t.T(), err) @@ -259,11 +271,12 @@ func (t *AuthServiceTest) TestSignInUserNotFound() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} userRepo.On("FindByEmail", t.signInRequest.Email, &model.User{}).Return(nil, findUserErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignIn(t.ctx, t.signInRequest) status, ok := status.FromError(err) @@ -293,12 +306,13 @@ func (t *AuthServiceTest) TestSignInUnmatchedPassword() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} userRepo.On("FindByEmail", t.signInRequest.Email, &model.User{}).Return(existUser, nil) bcryptUtil.On("CompareHashedPassword", existUser.Password, t.signInRequest.Password).Return(comparePwdErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignIn(t.ctx, t.signInRequest) status, ok := status.FromError(err) @@ -331,13 +345,14 @@ func (t *AuthServiceTest) TestSignInCreateAuthSessionFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} userRepo.On("FindByEmail", t.signInRequest.Email, &model.User{}).Return(existUser, nil) bcryptUtil.On("CompareHashedPassword", existUser.Password, t.signInRequest.Password).Return(nil) authRepo.EXPECT().Create(newAuthSession).Return(createAuthSessionErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignIn(t.ctx, t.signInRequest) st, ok := status.FromError(err) @@ -370,6 +385,7 @@ func (t *AuthServiceTest) TestSignInCreateCredentialFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} userRepo.On("FindByEmail", t.signInRequest.Email, &model.User{}).Return(existUser, nil) @@ -377,7 +393,7 @@ func (t *AuthServiceTest) TestSignInCreateCredentialFailed() { authRepo.EXPECT().Create(newAuthSession).Return(nil) tokenService.On("CreateCredential", existUser.ID.String(), existUser.Role, newAuthSession.ID.String()).Return(nil, createCredentialErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignIn(t.ctx, t.signInRequest) status, ok := status.FromError(err) @@ -405,11 +421,12 @@ func (t *AuthServiceTest) TestValidateSuccess() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.validateRequest.Token).Return(userCredential, nil) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.Validate(t.ctx, t.validateRequest) assert.Nil(t.T(), err) @@ -425,11 +442,12 @@ func (t *AuthServiceTest) TestValidateFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.validateRequest.Token).Return(nil, validateErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.Validate(t.ctx, t.validateRequest) st, ok := status.FromError(err) @@ -460,13 +478,14 @@ func (t *AuthServiceTest) TestRefreshTokenSuccess() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("FindRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(refreshTokenCache, nil) tokenService.On("CreateCredential", refreshTokenCache.UserID, refreshTokenCache.Role, refreshTokenCache.AuthSessionID).Return(credential, nil) tokenService.On("RemoveRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(nil) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.RefreshToken(t.ctx, t.refreshTokenRequest) assert.Nil(t.T(), err) @@ -483,11 +502,12 @@ func (t *AuthServiceTest) TestRefreshTokenInvalid() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("FindRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(nil, findTokenErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.RefreshToken(t.ctx, t.refreshTokenRequest) st, ok := status.FromError(err) @@ -507,11 +527,12 @@ func (t *AuthServiceTest) TestRefreshTokenFindTokenFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("FindRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(nil, findTokenErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.RefreshToken(t.ctx, t.refreshTokenRequest) st, ok := status.FromError(err) @@ -536,12 +557,13 @@ func (t *AuthServiceTest) TestRefreshTokenCreateCredentialFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("FindRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(refreshTokenCache, nil) tokenService.On("CreateCredential", refreshTokenCache.UserID, refreshTokenCache.Role, refreshTokenCache.AuthSessionID).Return(nil, createCredentialErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.RefreshToken(t.ctx, t.refreshTokenRequest) st, ok := status.FromError(err) @@ -571,13 +593,14 @@ func (t *AuthServiceTest) TestRefreshTokenRemoveTokenFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("FindRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(refreshTokenCache, nil) tokenService.On("CreateCredential", refreshTokenCache.UserID, refreshTokenCache.Role, refreshTokenCache.AuthSessionID).Return(credential, nil) tokenService.On("RemoveRefreshTokenCache", t.refreshTokenRequest.RefreshToken).Return(removeTokenErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.RefreshToken(t.ctx, t.refreshTokenRequest) st, ok := status.FromError(err) @@ -604,6 +627,7 @@ func (t *AuthServiceTest) TestSignOutSuccess() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil) @@ -611,7 +635,7 @@ func (t *AuthServiceTest) TestSignOutSuccess() { tokenService.On("RemoveAccessTokenCache", userCredential.AuthSessionID).Return(nil) authRepo.EXPECT().Delete(userCredential.AuthSessionID).Return(nil) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignOut(t.ctx, t.signOutRequest) assert.Nil(t.T(), err) @@ -626,11 +650,12 @@ func (t *AuthServiceTest) TestSignOutValidateFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.signOutRequest.Token).Return(nil, validateErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignOut(t.ctx, t.signOutRequest) st, ok := status.FromError(err) @@ -656,12 +681,13 @@ func (t *AuthServiceTest) TestSignOutRemoveRefreshTokenCacheFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil) tokenService.On("RemoveRefreshTokenCache", userCredential.RefreshToken).Return(removeTokenErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignOut(t.ctx, t.signOutRequest) st, ok := status.FromError(err) @@ -687,13 +713,14 @@ func (t *AuthServiceTest) TestSignOutRemoveAccessTokenCacheFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil) tokenService.On("RemoveRefreshTokenCache", userCredential.RefreshToken).Return(nil) tokenService.On("RemoveAccessTokenCache", userCredential.AuthSessionID).Return(removeTokenErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignOut(t.ctx, t.signOutRequest) st, ok := status.FromError(err) @@ -719,6 +746,7 @@ func (t *AuthServiceTest) TestSignOutDeleteAuthSessionFailed() { authRepo := mock_auth.NewMockRepository(controller) userRepo := user.UserRepositoryMock{} tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} bcryptUtil := utils.BcryptUtilMock{} tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil) @@ -726,7 +754,7 @@ func (t *AuthServiceTest) TestSignOutDeleteAuthSessionFailed() { tokenService.On("RemoveAccessTokenCache", userCredential.AuthSessionID).Return(nil) authRepo.EXPECT().Delete(userCredential.AuthSessionID).Return(deleteAuthErr) - authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil) + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) actual, err := authSvc.SignOut(t.ctx, t.signOutRequest) st, ok := status.FromError(err) From 5c48ecc154845131d5a3d9b682451af6e3cb361b Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sat, 13 Jan 2024 22:44:35 +0700 Subject: [PATCH 08/11] fix --- cmd/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index 605236d..9de5070 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -133,10 +133,11 @@ func main() { accessTokenCache := cacheRp.NewRepository(cacheDb) refreshTokenCache := cacheRp.NewRepository(cacheDb) + resetPasswordCache := cacheRp.NewRepository(cacheDb) jwtStrategy := strategy.NewJwtStrategy(conf.Jwt.Secret) jwtService := jwtSvc.NewService(conf.Jwt, jwtStrategy, jwtUtil) - tokenService := tokenSvc.NewService(jwtService, accessTokenCache, refreshTokenCache, uuidUtil) + tokenService := tokenSvc.NewService(jwtService, accessTokenCache, refreshTokenCache, resetPasswordCache, uuidUtil) emailService := emailSvc.NewService(conf.Sendgrid) authService := authSvc.NewService(authRepo, userRepo, tokenService, emailService, bcryptUtil, conf.Auth) From c583f629df55167424b76a766f3e7db1c244f298 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sun, 14 Jan 2024 09:25:23 +0700 Subject: [PATCH 09/11] feat: implement forgot password function --- internal/constant/email.constant.go | 3 + internal/service/auth/auth.service.go | 23 +++- internal/service/auth/auth.service_test.go | 148 ++++++++++++++++++++- 3 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 internal/constant/email.constant.go diff --git a/internal/constant/email.constant.go b/internal/constant/email.constant.go new file mode 100644 index 0000000..a29f9ff --- /dev/null +++ b/internal/constant/email.constant.go @@ -0,0 +1,3 @@ +package constant + +const ResetPasswordSubject = "Reset Password Request" diff --git a/internal/service/auth/auth.service.go b/internal/service/auth/auth.service.go index e4d2cc9..790dc23 100644 --- a/internal/service/auth/auth.service.go +++ b/internal/service/auth/auth.service.go @@ -2,6 +2,7 @@ package auth import ( "context" + "fmt" "github.com/isd-sgcu/johnjud-auth/cfgldr" "github.com/isd-sgcu/johnjud-auth/internal/constant" "github.com/isd-sgcu/johnjud-auth/internal/domain/model" @@ -159,7 +160,27 @@ func (s *serviceImpl) SignOut(_ context.Context, request *authProto.SignOutReque } func (s *serviceImpl) ForgotPassword(_ context.Context, request *authProto.ForgotPasswordRequest) (*authProto.ForgotPasswordResponse, error) { - return nil, nil + user := &model.User{} + err := s.userRepo.FindByEmail(request.Email, user) + if err != nil { + return nil, status.Error(codes.NotFound, constant.UserNotFoundErrorMessage) + } + + resetPasswordToken, err := s.tokenService.CreateResetPasswordToken(user.ID.String()) + if err != nil { + return nil, status.Error(codes.Internal, constant.InternalServerErrorMessage) + } + + resetPasswordURL := fmt.Sprintf("%s/reset-password/%s", s.config.ClientURL, resetPasswordToken) + emailSubject := constant.ResetPasswordSubject + emailContent := fmt.Sprintf("Please click the following url to reset password %s", resetPasswordURL) + if err := s.emailService.SendEmail(emailSubject, user.Firstname, user.Email, emailContent); err != nil { + return nil, status.Error(codes.Internal, constant.InternalServerErrorMessage) + } + + return &authProto.ForgotPasswordResponse{ + Url: resetPasswordURL, + }, nil } func (s *serviceImpl) ResetPassword(_ context.Context, request *authProto.ResetPasswordRequest) (*authProto.ResetPasswordResponse, error) { diff --git a/internal/service/auth/auth.service_test.go b/internal/service/auth/auth.service_test.go index 90dc47a..6a06313 100644 --- a/internal/service/auth/auth.service_test.go +++ b/internal/service/auth/auth.service_test.go @@ -2,6 +2,7 @@ package auth import ( "context" + "fmt" "github.com/isd-sgcu/johnjud-auth/cfgldr" "github.com/isd-sgcu/johnjud-auth/internal/constant" tokenDto "github.com/isd-sgcu/johnjud-auth/internal/domain/dto/token" @@ -28,13 +29,14 @@ import ( type AuthServiceTest struct { suite.Suite - ctx context.Context - signupRequest *authProto.SignUpRequest - signInRequest *authProto.SignInRequest - signOutRequest *authProto.SignOutRequest - refreshTokenRequest *authProto.RefreshTokenRequest - validateRequest *authProto.ValidateRequest - authConfig cfgldr.Auth + ctx context.Context + signupRequest *authProto.SignUpRequest + signInRequest *authProto.SignInRequest + signOutRequest *authProto.SignOutRequest + refreshTokenRequest *authProto.RefreshTokenRequest + validateRequest *authProto.ValidateRequest + forgotPasswordRequest *authProto.ForgotPasswordRequest + authConfig cfgldr.Auth } func TestAuthService(t *testing.T) { @@ -62,6 +64,9 @@ func (t *AuthServiceTest) SetupTest() { refreshTokenRequest := &authProto.RefreshTokenRequest{ RefreshToken: faker.UUIDDigit(), } + forgotPasswordRequest := &authProto.ForgotPasswordRequest{ + Email: faker.Email(), + } authConfig := cfgldr.Auth{ ClientURL: "localhost", } @@ -72,6 +77,7 @@ func (t *AuthServiceTest) SetupTest() { t.signOutRequest = signOutRequest t.validateRequest = validateRequest t.refreshTokenRequest = refreshTokenRequest + t.forgotPasswordRequest = forgotPasswordRequest t.authConfig = authConfig } @@ -763,3 +769,131 @@ func (t *AuthServiceTest) TestSignOutDeleteAuthSessionFailed() { assert.True(t.T(), ok) assert.Equal(t.T(), expected.Error(), err.Error()) } + +func (t *AuthServiceTest) TestForgotPasswordSuccess() { + userDb := &model.User{ + Base: model.Base{ + ID: uuid.New(), + }, + Email: t.forgotPasswordRequest.Email, + Password: faker.Password(), + Firstname: faker.FirstName(), + Lastname: faker.LastName(), + Role: constant.USER, + } + resetPasswordToken := faker.Word() + resetPasswordURL := fmt.Sprintf("%s/reset-password/%s", t.authConfig.ClientURL, resetPasswordToken) + emailContent := fmt.Sprintf("Please click the following url to reset password %s", resetPasswordURL) + + expected := &authProto.ForgotPasswordResponse{Url: resetPasswordURL} + + controller := gomock.NewController(t.T()) + + authRepo := mock_auth.NewMockRepository(controller) + userRepo := user.UserRepositoryMock{} + tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} + bcryptUtil := utils.BcryptUtilMock{} + + userRepo.On("FindByEmail", t.forgotPasswordRequest.Email, &model.User{}).Return(userDb, nil) + tokenService.On("CreateResetPasswordToken", userDb.ID.String()).Return(resetPasswordToken, nil) + emailService.On("SendEmail", constant.ResetPasswordSubject, userDb.Firstname, userDb.Email, emailContent).Return(nil) + + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) + actual, err := authSvc.ForgotPassword(t.ctx, t.forgotPasswordRequest) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), expected, actual) +} + +func (t *AuthServiceTest) TestForgotPasswordUserNotFound() { + findUserErr := gorm.ErrRecordNotFound + + expected := status.Error(codes.NotFound, constant.UserNotFoundErrorMessage) + + controller := gomock.NewController(t.T()) + + authRepo := mock_auth.NewMockRepository(controller) + userRepo := user.UserRepositoryMock{} + tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} + bcryptUtil := utils.BcryptUtilMock{} + + userRepo.On("FindByEmail", t.forgotPasswordRequest.Email, &model.User{}).Return(nil, findUserErr) + + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) + actual, err := authSvc.ForgotPassword(t.ctx, t.forgotPasswordRequest) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *AuthServiceTest) TestForgotPasswordCreateTokenFailed() { + userDb := &model.User{ + Base: model.Base{ + ID: uuid.New(), + }, + Email: t.forgotPasswordRequest.Email, + Password: faker.Password(), + Firstname: faker.FirstName(), + Lastname: faker.LastName(), + Role: constant.USER, + } + createTokenFailed := errors.New("Internal error") + + expected := status.Error(codes.Internal, constant.InternalServerErrorMessage) + + controller := gomock.NewController(t.T()) + + authRepo := mock_auth.NewMockRepository(controller) + userRepo := user.UserRepositoryMock{} + tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} + bcryptUtil := utils.BcryptUtilMock{} + + userRepo.On("FindByEmail", t.forgotPasswordRequest.Email, &model.User{}).Return(userDb, nil) + tokenService.On("CreateResetPasswordToken", userDb.ID.String()).Return("", createTokenFailed) + + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) + actual, err := authSvc.ForgotPassword(t.ctx, t.forgotPasswordRequest) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *AuthServiceTest) TestForgotPasswordSendEmailFailed() { + userDb := &model.User{ + Base: model.Base{ + ID: uuid.New(), + }, + Email: t.forgotPasswordRequest.Email, + Password: faker.Password(), + Firstname: faker.FirstName(), + Lastname: faker.LastName(), + Role: constant.USER, + } + resetPasswordToken := faker.Word() + resetPasswordURL := fmt.Sprintf("%s/reset-password/%s", t.authConfig.ClientURL, resetPasswordToken) + emailContent := fmt.Sprintf("Please click the following url to reset password %s", resetPasswordURL) + sendEmailErr := errors.New("Internal error") + + expected := status.Error(codes.Internal, constant.InternalServerErrorMessage) + + controller := gomock.NewController(t.T()) + + authRepo := mock_auth.NewMockRepository(controller) + userRepo := user.UserRepositoryMock{} + tokenService := token.TokenServiceMock{} + emailService := email.EmailServiceMock{} + bcryptUtil := utils.BcryptUtilMock{} + + userRepo.On("FindByEmail", t.forgotPasswordRequest.Email, &model.User{}).Return(userDb, nil) + tokenService.On("CreateResetPasswordToken", userDb.ID.String()).Return(resetPasswordToken, nil) + emailService.On("SendEmail", constant.ResetPasswordSubject, userDb.Firstname, userDb.Email, emailContent).Return(sendEmailErr) + + authSvc := NewService(authRepo, &userRepo, &tokenService, &emailService, &bcryptUtil, t.authConfig) + actual, err := authSvc.ForgotPassword(t.ctx, t.forgotPasswordRequest) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} From 7f31f0ea9cdbcf350168e5d23b1ef91ec32be469 Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sun, 14 Jan 2024 10:29:32 +0700 Subject: [PATCH 10/11] feat: store reset_token_ttl in config --- cfgldr/config.go | 1 + config/config.example.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/cfgldr/config.go b/cfgldr/config.go index ae05282..b934608 100644 --- a/cfgldr/config.go +++ b/cfgldr/config.go @@ -25,6 +25,7 @@ type Jwt struct { ExpiresIn int `mapstructure:"expires_in"` RefreshTokenTTL int `mapstructure:"refresh_token_ttl"` Issuer string `mapstructure:"issuer"` + ResetTokenTTL int `mapstructure:"reset_token_ttl"` } type Redis struct { diff --git a/config/config.example.yaml b/config/config.example.yaml index 058242e..b468695 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -15,6 +15,7 @@ jwt: expires_in: 3600 refresh_token_ttl: 604800 issuer: + reset_token_ttl: 900 redis: host: localhost From a792f6fc5d26d112756df21dbe287cb6eb01f2fa Mon Sep 17 00:00:00 2001 From: NitiwatOwen Date: Sun, 14 Jan 2024 10:31:21 +0700 Subject: [PATCH 11/11] feat: update reset password ttl --- internal/service/token/token.service.go | 2 +- internal/service/token/token.service_test.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/service/token/token.service.go b/internal/service/token/token.service.go index 943f40a..fc69d28 100644 --- a/internal/service/token/token.service.go +++ b/internal/service/token/token.service.go @@ -175,7 +175,7 @@ func (s *serviceImpl) CreateResetPasswordToken(userId string) (string, error) { tokenCache := &tokenDto.ResetPasswordTokenCache{ UserID: userId, } - err := s.resetPasswordTokenCache.SetValue(resetPasswordToken, tokenCache, 900) + err := s.resetPasswordTokenCache.SetValue(resetPasswordToken, tokenCache, s.jwtService.GetConfig().ResetTokenTTL) if err != nil { return "", err } diff --git a/internal/service/token/token.service_test.go b/internal/service/token/token.service_test.go index fde9823..bbb273e 100644 --- a/internal/service/token/token.service_test.go +++ b/internal/service/token/token.service_test.go @@ -48,6 +48,7 @@ func (t *TokenServiceTest) SetupTest() { ExpiresIn: 3600, RefreshTokenTTL: 604800, Issuer: "testIssuer", + ResetTokenTTL: 900, } validateToken := "" @@ -592,7 +593,8 @@ func (t *TokenServiceTest) TestCreateResetPasswordTokenSuccess() { uuidUtil := utils.UuidUtilMock{} uuidUtil.On("GetNewUUID").Return(t.refreshToken) - resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, 900).Return(nil) + jwtService.On("GetConfig").Return(t.jwtConfig) + resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, t.jwtConfig.ResetTokenTTL).Return(nil) tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateResetPasswordToken(t.userId) @@ -618,7 +620,8 @@ func (t *TokenServiceTest) TestCreateResetPasswordTokenFailed() { uuidUtil := utils.UuidUtilMock{} uuidUtil.On("GetNewUUID").Return(t.refreshToken) - resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, 900).Return(cacheErr) + jwtService.On("GetConfig").Return(t.jwtConfig) + resetPasswordTokenRepo.EXPECT().SetValue(t.refreshToken.String(), tokenCache, t.jwtConfig.ResetTokenTTL).Return(cacheErr) tokenSvc := NewService(&jwtService, accessTokenRepo, refreshTokenRepo, resetPasswordTokenRepo, &uuidUtil) actual, err := tokenSvc.CreateResetPasswordToken(t.userId)