diff --git a/src/internal/domain/dto/token/token.dto.go b/src/internal/domain/dto/token/token.dto.go index bee6c8f..76f5962 100644 --- a/src/internal/domain/dto/token/token.dto.go +++ b/src/internal/domain/dto/token/token.dto.go @@ -1,8 +1,17 @@ package token -import "github.com/isd-sgcu/johnjud-auth/src/internal/constant" +import ( + "github.com/golang-jwt/jwt/v4" + "github.com/isd-sgcu/johnjud-auth/src/internal/constant" +) type UserCredential struct { UserId string `json:"user_id"` Role constant.Role `json:"role"` } + +type AuthPayload struct { + jwt.RegisteredClaims + UserId string `json:"user_id"` + Role constant.Role `json:"role"` +} diff --git a/src/internal/repository/user/user.repository_test.go b/src/internal/repository/user/user.repository_test.go index 3f96f93..b462b9c 100644 --- a/src/internal/repository/user/user.repository_test.go +++ b/src/internal/repository/user/user.repository_test.go @@ -33,7 +33,7 @@ func (t *UserRepositoryTest) SetupTest() { err = db.AutoMigrate(&model.User{}) assert.NoError(t.T(), err) - userRepository := &repositoryImpl{Db: db} + userRepository := NewRepository(db) initialUser := &model.User{ Email: faker.Email(), diff --git a/src/internal/service/jwt/jwt.service.go b/src/internal/service/jwt/jwt.service.go index ea77094..6388166 100644 --- a/src/internal/service/jwt/jwt.service.go +++ b/src/internal/service/jwt/jwt.service.go @@ -1,28 +1,52 @@ package jwt import ( + "fmt" _jwt "github.com/golang-jwt/jwt/v4" "github.com/isd-sgcu/johnjud-auth/src/config" + "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/strategy" + "github.com/pkg/errors" + "time" ) type serviceImpl struct { config config.Jwt strategy strategy.JwtStrategy + jwtUtil utils.IJwtUtil } -func NewService(config config.Jwt, strategy strategy.JwtStrategy) *serviceImpl { - return &serviceImpl{config: config, strategy: strategy} +func NewService(config config.Jwt, strategy strategy.JwtStrategy, jwtUtil utils.IJwtUtil) *serviceImpl { + return &serviceImpl{config: config, strategy: strategy, jwtUtil: jwtUtil} } -func (s *serviceImpl) SignAuth(userId string) (string, error) { - return "", nil +func (s *serviceImpl) SignAuth(userId string, role constant.Role) (string, error) { + payloads := tokenDto.AuthPayload{ + RegisteredClaims: _jwt.RegisteredClaims{ + Issuer: s.config.Issuer, + ExpiresAt: s.jwtUtil.GetNumericDate(time.Now().Add(time.Second * time.Duration(s.config.ExpiresIn))), + IssuedAt: s.jwtUtil.GetNumericDate(time.Now()), + }, + UserId: userId, + Role: role, + } + + token := s.jwtUtil.GenerateJwtToken(_jwt.SigningMethodHS256, payloads) + + tokenStr, err := s.jwtUtil.SignedTokenString(token, s.config.Secret) + if err != nil { + return "", errors.New(fmt.Sprintf("Error while signing the token due to: %s", err.Error())) + } + + return tokenStr, nil } func (s *serviceImpl) VerifyAuth(token string) (*_jwt.Token, error) { - return nil, nil + return s.jwtUtil.ParseToken(token, s.strategy.AuthDecode) } func (s *serviceImpl) GetConfig() *config.Jwt { - return nil + return &s.config } diff --git a/src/internal/service/jwt/jwt.service_test.go b/src/internal/service/jwt/jwt.service_test.go index 3b00a66..564041d 100644 --- a/src/internal/service/jwt/jwt.service_test.go +++ b/src/internal/service/jwt/jwt.service_test.go @@ -1,16 +1,30 @@ package jwt import ( + "fmt" + "github.com/go-faker/faker/v4" + "github.com/golang-jwt/jwt/v4" "github.com/isd-sgcu/johnjud-auth/src/config" - "github.com/isd-sgcu/johnjud-auth/src/pkg/strategy" + "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/mocks/strategy" + "github.com/isd-sgcu/johnjud-auth/src/mocks/utils" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "testing" + "time" ) type JwtServiceTest struct { suite.Suite - config config.Jwt - strategy *strategy.JwtStrategy + config config.Jwt + userId string + role constant.Role + numericDate *jwt.NumericDate + payloads tokenDto.AuthPayload + token *jwt.Token } func TestJwtService(t *testing.T) { @@ -18,5 +32,118 @@ func TestJwtService(t *testing.T) { } func (t *JwtServiceTest) SetupTest() { + config := config.Jwt{ + Secret: "testSecret", + ExpiresIn: 3600, + Issuer: "testIssuer", + } + userId := faker.UUIDDigit() + role := constant.USER + numericDate := jwt.NewNumericDate(time.Now()) + + payloads := tokenDto.AuthPayload{ + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: t.config.Issuer, + ExpiresAt: numericDate, + IssuedAt: numericDate, + }, + UserId: userId, + Role: role, + } + + token := &jwt.Token{ + Header: map[string]interface{}{ + "typ": "JWT", + "alg": jwt.SigningMethodHS256.Alg(), + }, + Method: jwt.SigningMethodHS256, + Claims: payloads, + } + + t.config = config + t.userId = userId + t.role = role + t.numericDate = numericDate + t.payloads = payloads + t.token = token +} + +func (t *JwtServiceTest) TestSignAuthSuccess() { + expected := "signedTokenStr" + + jwtStrategy := strategy.JwtStrategyMock{} + jwtUtil := utils.JwtUtilMock{} + + jwtUtil.On("GetNumericDate", mock.AnythingOfType("time.Time")).Return(t.numericDate) + jwtUtil.On("GenerateJwtToken", jwt.SigningMethodHS256, t.payloads).Return(t.token) + jwtUtil.On("SignedTokenString", t.token, t.config.Secret).Return(expected, nil) + + jwtSvc := NewService(t.config, &jwtStrategy, &jwtUtil) + actual, err := jwtSvc.SignAuth(t.userId, t.role) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), expected, actual) +} + +func (t *JwtServiceTest) TestSignAuthSignedStringFailed() { + signedTokenError := errors.New("Some Error") + expected := errors.New(fmt.Sprintf("Error while signing the token due to: %s", signedTokenError.Error())) + + jwtStrategy := strategy.JwtStrategyMock{} + jwtUtil := utils.JwtUtilMock{} + + jwtUtil.On("GetNumericDate", mock.AnythingOfType("time.Time")).Return(t.numericDate) + jwtUtil.On("GenerateJwtToken", jwt.SigningMethodHS256, t.payloads).Return(t.token) + jwtUtil.On("SignedTokenString", t.token, t.config.Secret).Return("", signedTokenError) + + jwtSvc := NewService(t.config, &jwtStrategy, &jwtUtil) + actual, err := jwtSvc.SignAuth(t.userId, t.role) + + assert.Equal(t.T(), "", actual) + assert.Equal(t.T(), expected.Error(), err.Error()) +} + +func (t *JwtServiceTest) TestVerifyAuthSuccess() { + tokenStr := "validSignedToken" + expected := t.token + + jwtStrategy := strategy.JwtStrategyMock{} + jwtUtil := utils.JwtUtilMock{} + + jwtUtil.On("ParseToken", tokenStr, mock.AnythingOfType("jwt.Keyfunc")).Return(expected, nil) + + jwtSvc := NewService(t.config, &jwtStrategy, &jwtUtil) + actual, err := jwtSvc.VerifyAuth(tokenStr) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), *expected, *actual) +} + +func (t *JwtServiceTest) TestVerifyAuthFailed() { + tokenStr := "invalidSignedToken" + expected := errors.New("invalid token") + + jwtStrategy := strategy.JwtStrategyMock{} + jwtUtil := utils.JwtUtilMock{} + + jwtUtil.On("ParseToken", tokenStr, mock.AnythingOfType("jwt.Keyfunc")).Return(nil, expected) + + jwtSvc := NewService(t.config, &jwtStrategy, &jwtUtil) + actual, err := jwtSvc.VerifyAuth(tokenStr) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *JwtServiceTest) TestGetConfigSuccess() { + expected := &t.config + + jwtStrategy := strategy.JwtStrategyMock{} + jwtUtil := utils.JwtUtilMock{} + + jwtSvc := NewService(t.config, &jwtStrategy, &jwtUtil) + actual := jwtSvc.GetConfig() + + assert.Equal(t.T(), *expected, *actual) } diff --git a/src/internal/service/token/token.service.go b/src/internal/service/token/token.service.go index 8beda60..5ca80d9 100644 --- a/src/internal/service/token/token.service.go +++ b/src/internal/service/token/token.service.go @@ -1,6 +1,7 @@ package token import ( + "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/pkg/service/jwt" authProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/auth/auth/v1" @@ -14,7 +15,7 @@ func NewService(jwtService jwt.Service) *serviceImpl { return &serviceImpl{jwtService: jwtService} } -func (s *serviceImpl) CreateCredential(userId string, secret string) (*authProto.Credential, error) { +func (s *serviceImpl) CreateCredential(userId string, role constant.Role) (*authProto.Credential, error) { return nil, nil } diff --git a/src/internal/strategy/jwt.strategy_test.go b/src/internal/strategy/jwt.strategy_test.go new file mode 100644 index 0000000..865f8bb --- /dev/null +++ b/src/internal/strategy/jwt.strategy_test.go @@ -0,0 +1,55 @@ +package strategy + +import ( + "fmt" + "github.com/golang-jwt/jwt/v4" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "testing" +) + +type JwtStrategyTest struct { + suite.Suite + secret string +} + +func TestJwtStrategy(t *testing.T) { + suite.Run(t, new(JwtStrategyTest)) +} + +func (t *JwtStrategyTest) SetupTest() { + secret := "testSecret" + + t.secret = secret +} + +func (t *JwtStrategyTest) TestAuthDecodeSuccess() { + token := &jwt.Token{ + Method: jwt.SigningMethodHS256, + } + expected := []byte(t.secret) + + jwtStrategy := NewJwtStrategy(t.secret) + actual, err := jwtStrategy.AuthDecode(token) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), expected, actual) +} + +func (t *JwtStrategyTest) TestAuthDecodeFailed() { + token := &jwt.Token{ + Method: jwt.SigningMethodES256, + Header: map[string]interface{}{ + "typ": "JWT", + "alg": jwt.SigningMethodES256.Alg(), + }, + } + expected := errors.New(fmt.Sprintf("invalid token %v\n", token.Header["alg"])) + + jwtStrategy := NewJwtStrategy(t.secret) + actual, err := jwtStrategy.AuthDecode(token) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected.Error(), err.Error()) +} diff --git a/src/internal/utils/jwt.utils.go b/src/internal/utils/jwt.utils.go new file mode 100644 index 0000000..3a4694e --- /dev/null +++ b/src/internal/utils/jwt.utils.go @@ -0,0 +1,35 @@ +package utils + +import ( + "github.com/golang-jwt/jwt/v4" + "time" +) + +type IJwtUtil interface { + GenerateJwtToken(method jwt.SigningMethod, payloads jwt.Claims) *jwt.Token + GetNumericDate(time time.Time) *jwt.NumericDate + SignedTokenString(token *jwt.Token, secret string) (string, error) + ParseToken(tokenStr string, keyFunc jwt.Keyfunc) (*jwt.Token, error) +} + +type jwtUtilImpl struct{} + +func NewJwtUtil() *jwtUtilImpl { + return &jwtUtilImpl{} +} + +func (u *jwtUtilImpl) GenerateJwtToken(method jwt.SigningMethod, payloads jwt.Claims) *jwt.Token { + return jwt.NewWithClaims(method, payloads) +} + +func (u *jwtUtilImpl) GetNumericDate(time time.Time) *jwt.NumericDate { + return jwt.NewNumericDate(time) +} + +func (u *jwtUtilImpl) SignedTokenString(token *jwt.Token, secret string) (string, error) { + return token.SignedString([]byte(secret)) +} + +func (u *jwtUtilImpl) ParseToken(tokenStr string, keyFunc jwt.Keyfunc) (*jwt.Token, error) { + return jwt.Parse(tokenStr, keyFunc) +} diff --git a/src/main.go b/src/main.go index c09348b..7ebd4d1 100644 --- a/src/main.go +++ b/src/main.go @@ -6,6 +6,7 @@ import ( "github.com/isd-sgcu/johnjud-auth/src/config" "github.com/isd-sgcu/johnjud-auth/src/database" "github.com/isd-sgcu/johnjud-auth/src/internal/strategy" + "github.com/isd-sgcu/johnjud-auth/src/internal/utils" "github.com/isd-sgcu/johnjud-auth/src/pkg/repository/user" "github.com/isd-sgcu/johnjud-auth/src/pkg/service/auth" "github.com/isd-sgcu/johnjud-auth/src/pkg/service/jwt" @@ -110,7 +111,8 @@ func main() { userRepo := user.NewRepository(db) jwtStrategy := strategy.NewJwtStrategy(conf.Jwt.Secret) - jwtService := jwt.NewService(conf.Jwt, jwtStrategy) + jwtUtil := utils.NewJwtUtil() + jwtService := jwt.NewService(conf.Jwt, jwtStrategy, jwtUtil) tokenService := token.NewService(jwtService) diff --git a/src/mocks/strategy/strategy.mock.go b/src/mocks/strategy/strategy.mock.go new file mode 100644 index 0000000..fe71def --- /dev/null +++ b/src/mocks/strategy/strategy.mock.go @@ -0,0 +1,18 @@ +package strategy + +import ( + "github.com/golang-jwt/jwt/v4" + "github.com/stretchr/testify/mock" +) + +type JwtStrategyMock struct { + mock.Mock +} + +func (m *JwtStrategyMock) AuthDecode(token *jwt.Token) (interface{}, error) { + args := m.Called(token) + if args.Get(0) != nil { + return args.Get(0), nil + } + return nil, args.Error(1) +} diff --git a/src/mocks/utils/jwt.mock.go b/src/mocks/utils/jwt.mock.go new file mode 100644 index 0000000..f2f8746 --- /dev/null +++ b/src/mocks/utils/jwt.mock.go @@ -0,0 +1,39 @@ +package utils + +import ( + "github.com/golang-jwt/jwt/v4" + "github.com/stretchr/testify/mock" + "time" +) + +type JwtUtilMock struct { + mock.Mock +} + +func (m *JwtUtilMock) GenerateJwtToken(method jwt.SigningMethod, payloads jwt.Claims) *jwt.Token { + args := m.Called(method, payloads) + return args.Get(0).(*jwt.Token) +} + +func (m *JwtUtilMock) GetNumericDate(time time.Time) *jwt.NumericDate { + args := m.Called(time) + return args.Get(0).(*jwt.NumericDate) +} + +func (m *JwtUtilMock) SignedTokenString(token *jwt.Token, secret string) (string, error) { + args := m.Called(token, secret) + if args.Get(0) != "" { + return args.Get(0).(string), nil + } + + return "", args.Error(1) +} + +func (m *JwtUtilMock) ParseToken(tokenStr string, keyFunc jwt.Keyfunc) (*jwt.Token, error) { + args := m.Called(tokenStr, keyFunc) + if args.Get(0) != nil { + return args.Get(0).(*jwt.Token), nil + } + + return nil, args.Error(1) +} diff --git a/src/pkg/service/jwt/jwt.service.go b/src/pkg/service/jwt/jwt.service.go index db66000..35314d9 100644 --- a/src/pkg/service/jwt/jwt.service.go +++ b/src/pkg/service/jwt/jwt.service.go @@ -3,16 +3,18 @@ package jwt import ( _jwt "github.com/golang-jwt/jwt/v4" "github.com/isd-sgcu/johnjud-auth/src/config" + "github.com/isd-sgcu/johnjud-auth/src/internal/constant" "github.com/isd-sgcu/johnjud-auth/src/internal/service/jwt" + "github.com/isd-sgcu/johnjud-auth/src/internal/utils" "github.com/isd-sgcu/johnjud-auth/src/pkg/strategy" ) type Service interface { - SignAuth(userId string) (string, error) + SignAuth(userId string, role constant.Role) (string, error) VerifyAuth(token string) (*_jwt.Token, error) GetConfig() *config.Jwt } -func NewService(config config.Jwt, strategy strategy.JwtStrategy) Service { - return jwt.NewService(config, strategy) +func NewService(config config.Jwt, strategy strategy.JwtStrategy, jwtUtil utils.IJwtUtil) Service { + return jwt.NewService(config, strategy, jwtUtil) } diff --git a/src/pkg/service/token/token.service.go b/src/pkg/service/token/token.service.go index a212cd7..038b8f0 100644 --- a/src/pkg/service/token/token.service.go +++ b/src/pkg/service/token/token.service.go @@ -1,6 +1,7 @@ package token import ( + "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/service/token" "github.com/isd-sgcu/johnjud-auth/src/pkg/service/jwt" @@ -8,7 +9,7 @@ import ( ) type Service interface { - CreateCredential(userId string, secret string) (*authProto.Credential, error) + CreateCredential(userId string, role constant.Role) (*authProto.Credential, error) Validate(token string) (*tokenDto.UserCredential, error) }