Skip to content

Commit

Permalink
Merge pull request #499 from anandrgitnirman/jwttoken
Browse files Browse the repository at this point in the history
#312 - Support for Token Generation and Verification
  • Loading branch information
anandrgitnirman authored Jun 1, 2020
2 parents d38512c + 59cb441 commit 1a52984
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const (
PvtKeyForMetering = "pvt_key_for_metering"
NotificationServiceEndpoint = "notification_svc_end_point"
ServiceHeartbeatType = "service_heartbeat_type"
TokenExpiryInSeconds = "token_expiry_in_seconds"
TokenSecretKey = "token_secret_key"
//none|grpc|http
//This defaultConfigJson will eventually be replaced by DefaultDaemonConfigurationSchema
defaultConfigJson string = `
Expand Down
12 changes: 12 additions & 0 deletions snetd/cmd/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/singnet/snet-daemon/configuration_service"
"github.com/singnet/snet-daemon/metrics"
"github.com/singnet/snet-daemon/pricing"
"github.com/singnet/snet-daemon/token"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -50,6 +51,7 @@ type Components struct {
freeCallUserService escrow.FreeCallUserService
freeCallUserStorage *escrow.FreeCallUserStorage
freeCallLockerStorage *escrow.PrefixedAtomicStorage
tokenService token.Service
}

func InitComponents(cmd *cobra.Command) (components *Components) {
Expand Down Expand Up @@ -505,3 +507,13 @@ func (components *Components) ConfigurationService() *configuration_service.Conf

return components.configurationService
}

func (components *Components) TokenService() token.Service {
if components.tokenService != nil {
return components.tokenService
}

components.tokenService = token.NewJWTTokenService(*components.OrganizationMetaData())

return components.tokenService
}
70 changes: 70 additions & 0 deletions token/jwttoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package token

import (
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/singnet/snet-daemon/blockchain"
"github.com/singnet/snet-daemon/config"
"strings"
"time"
)

type customJWTokenServiceImpl struct {
getGroupId func() string
}

//This will be used in components as a service to Create and Validate tokens
func NewJWTTokenService(data blockchain.OrganizationMetaData) Service {
return &customJWTokenServiceImpl{
getGroupId: func() string {
return data.GetGroupIdString()
},
}
}

func (service customJWTokenServiceImpl) CreateToken(payLoad PayLoad) (CustomToken, error) {
atClaims := jwt.MapClaims{}
atClaims["payload"] = fmt.Sprintf("%v", payLoad)
atClaims["orgId"] = config.GetString(config.OrganizationId)
atClaims["groupId"] = service.getGroupId()
//set the Expiry of the Token generated
atClaims["exp"] = time.Now().UTC().
Add(time.Second * time.Duration(config.GetInt(config.TokenExpiryInSeconds))).Unix()
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
return jwtToken.SignedString([]byte(config.GetString(config.TokenSecretKey)))
}

func (service customJWTokenServiceImpl) VerifyToken(receivedToken CustomToken, payLoad PayLoad) (err error) {
tokenString := fmt.Sprintf("%v", receivedToken)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method : %v", token.Header["alg"])
}
return []byte(config.GetString(config.TokenSecretKey)), nil
})
if err != nil {
return err
}

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
if err = service.checkJwtTokenClaims(claims, payLoad); err != nil {
return err
}
}
return nil
}

func (service customJWTokenServiceImpl) checkJwtTokenClaims(claims jwt.MapClaims, payload PayLoad) (err error) {
if strings.Compare(fmt.Sprintf("%v", claims["payload"]), fmt.Sprintf("%v", payload)) != 0 {
return fmt.Errorf("payload %v used to generate the Token doesnt match expected values", claims["payload"])
}

if strings.Compare(fmt.Sprintf("%v", claims["orgId"]), config.GetString(config.OrganizationId)) != 0 {
return fmt.Errorf("organization %v is not associated with this Daemon", claims["orgId"])
}

if strings.Compare(fmt.Sprintf("%v", claims["groupId"]), service.getGroupId()) != 0 {
return fmt.Errorf("groupId %v is not associated with this Daemon", claims["groupId"])
}
return nil
}
51 changes: 51 additions & 0 deletions token/jwttoken_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package token

import (
"testing"
"time"

"github.com/singnet/snet-daemon/config"
"github.com/stretchr/testify/assert"
)

func Test_customJWTokenClaimsImpl_CreateToken(t *testing.T) {
tokenImpl := &customJWTokenServiceImpl{
getGroupId: func() string {
return "GroupID"
},
}
token, err := tokenImpl.CreateToken("any struct")
assert.Nil(t, err)
assert.NotNil(t, token)
err = tokenImpl.VerifyToken(token, "any struct")
config.Vip().Set(config.TokenExpiryInSeconds, 1)
token, err = tokenImpl.CreateToken("any struct")
time.Sleep(time.Second * 5)
assert.Nil(t, err)
err = tokenImpl.VerifyToken(token, "any struct")
assert.Equal(t, "Token is expired", err.Error())

}

func Test_customJWTokenClaimsImpl_checkJwtTokenClaims(t *testing.T) {
tokenImpl := &customJWTokenServiceImpl{
getGroupId: func() string {
return "GroupID"
},
}
config.Vip().Set(config.TokenExpiryInSeconds, 50)
token, err := tokenImpl.CreateToken("any struct")
err = tokenImpl.VerifyToken(token, "different struct")
assert.Equal(t, "payload any struct used to generate the Token doesnt match expected values", err.Error())
config.Vip().Set(config.OrganizationId, "differentOrganization")
err = tokenImpl.VerifyToken(token, "any struct")
assert.Equal(t, "organization ExampleOrganizationId is not associated with this Daemon", err.Error())
config.Vip().Set(config.OrganizationId, "ExampleOrganizationId")
tokenImpl2 := &customJWTokenServiceImpl{
getGroupId: func() string {
return "GroupID2"
},
}
err = tokenImpl2.VerifyToken(token, "any struct")
assert.Equal(t, "groupId GroupID is not associated with this Daemon", err.Error())
}
10 changes: 10 additions & 0 deletions token/token_service_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package token

type PayLoad interface{}
type CustomToken interface{}

// Token.Service interface is an API for creating and verifying tokens
type Service interface {
CreateToken(key PayLoad) (token CustomToken, err error)
VerifyToken(token CustomToken, key PayLoad) (err error)
}

0 comments on commit 1a52984

Please sign in to comment.