Skip to content

Commit

Permalink
Merge pull request #8 from isd-sgcu/JOH-26/sign-out
Browse files Browse the repository at this point in the history
[JOH-26] Sign out
  • Loading branch information
Nitiwat-owen authored Dec 30, 2023
2 parents 42e6222 + b8e5a6b commit 7ceeb7d
Show file tree
Hide file tree
Showing 13 changed files with 457 additions and 91 deletions.
16 changes: 9 additions & 7 deletions src/internal/domain/dto/token/token.dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ import (
)

type UserCredential struct {
UserID string `json:"user_id"`
Role constant.Role `json:"role"`
UserID string `json:"user_id"`
Role constant.Role `json:"role"`
AuthSessionID string `json:"auth_session_id"`
RefreshToken string `json:"refresh_token"`
}

type AuthPayload struct {
jwt.RegisteredClaims
UserID string `json:"user_id"`
Role constant.Role `json:"role"`
AuthSessionID string `json:"auth_session_id"`
UserID string `json:"user_id"`
AuthSessionID string `json:"auth_session_id"`
}

type AccessTokenCache struct {
Token string `json:"token"`
RefreshToken string `json:"refresh_token"`
Token string `json:"token"`
Role constant.Role `json:"role"`
RefreshToken string `json:"refresh_token"`
}

type RefreshTokenCache struct {
Expand Down
21 changes: 2 additions & 19 deletions src/internal/repository/cache/cache.repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,9 @@ func (r *repositoryImpl) GetValue(key string, value interface{}) error {
return json.Unmarshal([]byte(v), value)
}

func (r *repositoryImpl) AddSetMember(key string, value interface{}) error {
func (r *repositoryImpl) DeleteValue(key string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

v, err := json.Marshal(value)
if err != nil {
return err
}

return r.client.SAdd(ctx, key, v).Err()
}

func (r *repositoryImpl) IsSetMember(key string, value interface{}) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

v, err := json.Marshal(value)
if err != nil {
return false, err
}

return r.client.SIsMember(ctx, key, v).Result()
return r.client.Del(ctx, key).Err()
}
5 changes: 5 additions & 0 deletions src/internal/repository/cache/cache.repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,8 @@ func (t *CacheRepositoryTest) TestGetValueSuccess() {
assert.Nil(t.T(), err)
assert.Equal(t.T(), t.value, value)
}

func (t *CacheRepositoryTest) TestDeleteValueSuccess() {
err := t.cacheRepo.DeleteValue(t.key)
assert.Nil(t.T(), err)
}
17 changes: 16 additions & 1 deletion src/internal/service/auth/auth.service.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,22 @@ func (s *serviceImpl) SignIn(_ context.Context, request *authProto.SignInRequest
}

func (s *serviceImpl) SignOut(_ context.Context, request *authProto.SignOutRequest) (*authProto.SignOutResponse, error) {
return nil, nil
userCredential, err := s.tokenService.Validate(request.Token)
if err != nil {
return nil, status.Error(codes.Internal, constant.InternalServerErrorMessage)
}

err = s.tokenService.RemoveTokenCache(userCredential.RefreshToken)
if err != nil {
return nil, status.Error(codes.Internal, constant.InternalServerErrorMessage)
}

err = s.authRepo.Delete(userCredential.AuthSessionID)
if err != nil {
return nil, status.Error(codes.Internal, constant.InternalServerErrorMessage)
}

return &authProto.SignOutResponse{IsSuccess: true}, nil
}

func (s *serviceImpl) createAuthSession(userId uuid.UUID, role constant.Role) (*authProto.Credential, error) {
Expand Down
127 changes: 124 additions & 3 deletions src/internal/service/auth/auth.service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/isd-sgcu/johnjud-auth/src/internal/constant"
tokenDto "github.com/isd-sgcu/johnjud-auth/src/internal/domain/dto/token"
"github.com/isd-sgcu/johnjud-auth/src/internal/domain/model"
mock_auth "github.com/isd-sgcu/johnjud-auth/src/mocks/repository/auth"
"github.com/isd-sgcu/johnjud-auth/src/mocks/repository/user"
Expand All @@ -24,9 +25,10 @@ import (

type AuthServiceTest struct {
suite.Suite
ctx context.Context
signupRequest *authProto.SignUpRequest
signInRequest *authProto.SignInRequest
ctx context.Context
signupRequest *authProto.SignUpRequest
signInRequest *authProto.SignInRequest
signOutRequest *authProto.SignOutRequest
}

func TestAuthService(t *testing.T) {
Expand All @@ -45,10 +47,14 @@ func (t *AuthServiceTest) SetupTest() {
Email: faker.Email(),
Password: faker.Password(),
}
signOutRequest := &authProto.SignOutRequest{
Token: faker.Word(),
}

t.ctx = ctx
t.signupRequest = signupRequest
t.signInRequest = signInRequest
t.signOutRequest = signOutRequest
}

func (t *AuthServiceTest) TestSignupSuccess() {
Expand Down Expand Up @@ -381,3 +387,118 @@ func (t *AuthServiceTest) TestRefreshTokenNotFound() {}
func (t *AuthServiceTest) TestRefreshTokenCreateCredentialFailed() {}

func (t *AuthServiceTest) TestRefreshTokenUpdateTokenFailed() {}

func (t *AuthServiceTest) TestSignOutSuccess() {
userCredential := &tokenDto.UserCredential{
UserID: faker.UUIDDigit(),
Role: constant.USER,
AuthSessionID: faker.UUIDDigit(),
RefreshToken: faker.UUIDDigit(),
}

expected := &authProto.SignOutResponse{
IsSuccess: true,
}

controller := gomock.NewController(t.T())

authRepo := mock_auth.NewMockRepository(controller)
userRepo := user.UserRepositoryMock{}
tokenService := token.TokenServiceMock{}
bcryptUtil := utils.BcryptUtilMock{}

tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil)
tokenService.On("RemoveTokenCache", userCredential.RefreshToken).Return(nil)
authRepo.EXPECT().Delete(userCredential.AuthSessionID).Return(nil)

authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil)
actual, err := authSvc.SignOut(t.ctx, t.signOutRequest)

assert.Nil(t.T(), err)
assert.Equal(t.T(), expected, actual)
}

func (t *AuthServiceTest) TestSignOutValidateFailed() {
validateErr := errors.New("internal server error")
expected := status.Error(codes.Internal, constant.InternalServerErrorMessage)
controller := gomock.NewController(t.T())

authRepo := mock_auth.NewMockRepository(controller)
userRepo := user.UserRepositoryMock{}
tokenService := token.TokenServiceMock{}
bcryptUtil := utils.BcryptUtilMock{}

tokenService.On("Validate", t.signOutRequest.Token).Return(nil, validateErr)

authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil)
actual, err := authSvc.SignOut(t.ctx, t.signOutRequest)

st, ok := status.FromError(err)
assert.Nil(t.T(), actual)
assert.Equal(t.T(), codes.Internal, st.Code())
assert.True(t.T(), ok)
assert.Equal(t.T(), expected.Error(), err.Error())
}

func (t *AuthServiceTest) TestSignOutRemoveTokenCacheFailed() {
userCredential := &tokenDto.UserCredential{
UserID: faker.UUIDDigit(),
Role: constant.USER,
AuthSessionID: faker.UUIDDigit(),
RefreshToken: faker.UUIDDigit(),
}
removeTokenErr := errors.New("internal server error")

expected := status.Error(codes.Internal, constant.InternalServerErrorMessage)

controller := gomock.NewController(t.T())

authRepo := mock_auth.NewMockRepository(controller)
userRepo := user.UserRepositoryMock{}
tokenService := token.TokenServiceMock{}
bcryptUtil := utils.BcryptUtilMock{}

tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil)
tokenService.On("RemoveTokenCache", userCredential.RefreshToken).Return(removeTokenErr)

authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil)
actual, err := authSvc.SignOut(t.ctx, t.signOutRequest)

st, ok := status.FromError(err)
assert.Nil(t.T(), actual)
assert.Equal(t.T(), codes.Internal, st.Code())
assert.True(t.T(), ok)
assert.Equal(t.T(), expected.Error(), err.Error())
}

func (t *AuthServiceTest) TestSignOutDeleteAuthSessionFailed() {
userCredential := &tokenDto.UserCredential{
UserID: faker.UUIDDigit(),
Role: constant.USER,
AuthSessionID: faker.UUIDDigit(),
RefreshToken: faker.UUIDDigit(),
}
deleteAuthErr := errors.New("internal server error")

expected := status.Error(codes.Internal, constant.InternalServerErrorMessage)

controller := gomock.NewController(t.T())

authRepo := mock_auth.NewMockRepository(controller)
userRepo := user.UserRepositoryMock{}
tokenService := token.TokenServiceMock{}
bcryptUtil := utils.BcryptUtilMock{}

tokenService.On("Validate", t.signOutRequest.Token).Return(userCredential, nil)
tokenService.On("RemoveTokenCache", userCredential.RefreshToken).Return(nil)
authRepo.EXPECT().Delete(userCredential.AuthSessionID).Return(deleteAuthErr)

authSvc := NewService(authRepo, &userRepo, &tokenService, &bcryptUtil)
actual, err := authSvc.SignOut(t.ctx, t.signOutRequest)

st, ok := status.FromError(err)
assert.Nil(t.T(), actual)
assert.Equal(t.T(), codes.Internal, st.Code())
assert.True(t.T(), ok)
assert.Equal(t.T(), expected.Error(), err.Error())
}
1 change: 0 additions & 1 deletion src/internal/service/jwt/jwt.service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ func (s *serviceImpl) SignAuth(userId string, role constant.Role, authSessionId
IssuedAt: s.jwtUtil.GetNumericDate(time.Now()),
},
UserID: userId,
Role: role,
AuthSessionID: authSessionId,
}

Expand Down
1 change: 0 additions & 1 deletion src/internal/service/jwt/jwt.service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ func (t *JwtServiceTest) SetupTest() {
IssuedAt: numericDate,
},
UserID: userId,
Role: role,
AuthSessionID: authSessionId,
}

Expand Down
56 changes: 50 additions & 6 deletions src/internal/service/token/token.service.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package token

import (
_jwt "github.com/golang-jwt/jwt/v4"
"github.com/isd-sgcu/johnjud-auth/src/internal/constant"
tokenDto "github.com/isd-sgcu/johnjud-auth/src/internal/domain/dto/token"
"github.com/isd-sgcu/johnjud-auth/src/internal/utils"
"github.com/isd-sgcu/johnjud-auth/src/pkg/repository/cache"
"github.com/isd-sgcu/johnjud-auth/src/pkg/service/jwt"
authProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/auth/auth/v1"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"time"
)

Expand Down Expand Up @@ -38,6 +40,7 @@ func (s *serviceImpl) CreateCredential(userId string, role constant.Role, authSe

accessTokenCache := &tokenDto.AccessTokenCache{
Token: accessToken,
Role: role,
RefreshToken: refreshToken,
}
err = s.accessTokenCache.SetValue(authSessionId, accessTokenCache, jwtConf.ExpiresIn)
Expand Down Expand Up @@ -65,28 +68,69 @@ func (s *serviceImpl) CreateCredential(userId string, role constant.Role, authSe
}

func (s *serviceImpl) Validate(token string) (*tokenDto.UserCredential, error) {
// verifyAuth -> jwt.Token
jwtToken, err := s.jwtService.VerifyAuth(token)
if err != nil {
return nil, err
}

payloads := jwtToken.Claims.(tokenDto.AuthPayload)
if payloads.Issuer != s.jwtService.GetConfig().Issuer {
payloads := jwtToken.Claims.(_jwt.MapClaims)
if payloads["iss"] != s.jwtService.GetConfig().Issuer {
return nil, errors.New("invalid token")
}

if time.Unix(payloads.ExpiresAt.Unix(), 0).Before(time.Now()) {
if time.Unix(int64(payloads["exp"].(float64)), 0).Before(time.Now()) {
return nil, errors.New("expired token")
}

accessTokenCache := &tokenDto.AccessTokenCache{}
err = s.accessTokenCache.GetValue(payloads["auth_session_id"].(string), accessTokenCache)
if err != nil {
if err != redis.Nil {
return nil, err
}
return nil, errors.New("invalid token")
}

if token != accessTokenCache.Token {
return nil, errors.New("invalid token")
}

userCredential := &tokenDto.UserCredential{
UserID: payloads.UserID,
Role: payloads.Role,
UserID: payloads["user_id"].(string),
Role: accessTokenCache.Role,
AuthSessionID: payloads["auth_session_id"].(string),
RefreshToken: accessTokenCache.RefreshToken,
}
return userCredential, nil
}

func (s *serviceImpl) CreateRefreshToken() string {
return s.uuidUtil.GetNewUUID().String()
}

func (s *serviceImpl) RemoveTokenCache(refreshToken string) error {
refreshTokenCache := &tokenDto.RefreshTokenCache{}
err := s.refreshTokenCache.GetValue(refreshToken, refreshTokenCache)
if err != nil {
if err != redis.Nil {
return err
}
return nil
}

err = s.refreshTokenCache.DeleteValue(refreshToken)
if err != nil {
if err != redis.Nil {
return err
}
}

err = s.accessTokenCache.DeleteValue(refreshTokenCache.AuthSessionID)
if err != nil {
if err != redis.Nil {
return err
}
}

return nil
}
Loading

0 comments on commit 7ceeb7d

Please sign in to comment.