diff --git a/server/db/providers/mongodb/user.go b/server/db/providers/mongodb/user.go index 4f60349..465b8df 100644 --- a/server/db/providers/mongodb/user.go +++ b/server/db/providers/mongodb/user.go @@ -68,7 +68,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) opts.SetSort(bson.M{"created_at": -1}) paginationClone := pagination - // TODO add pagination total userCollection := p.db.Collection(models.Collections.User, options.Collection()) count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count()) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 1fa84cc..35015dc 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -246,7 +246,7 @@ func AuthorizeHandler() gin.HandlerFunc { return } - memorystore.Provider.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, newSessionToken, newSessionTokenData.Nonce) cookie.SetSession(gc, newSessionToken) code := uuid.New().String() memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) @@ -284,8 +284,8 @@ func AuthorizeHandler() gin.HandlerFunc { return } memorystore.Provider.RemoveState(sessionToken) - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -308,7 +308,7 @@ func AuthorizeHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } if isQuery { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 5028c4f..419de67 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -200,12 +200,12 @@ func OAuthCallbackHandler() gin.HandlerFunc { params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token cookie.SetSession(c, authToken.FingerPrintHash) - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) if authToken.RefreshToken != nil { params = params + `&refresh_token=` + authToken.RefreshToken.Token - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } go db.Provider.AddSession(models.Session{ diff --git a/server/handlers/token.go b/server/handlers/token.go index 4bcbe83..0f9573a 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -185,8 +185,8 @@ func TokenHandler() gin.HandlerFunc { }) return } - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -204,7 +204,7 @@ func TokenHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } gc.JSON(http.StatusOK, res) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index c3dd0c3..1488254 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -42,7 +42,7 @@ func VerifyEmailHandler() gin.HandlerFunc { // verify if token exists in db hostname := parsers.GetHost(c) - claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(tokenInQuery) if err != nil { log.Debug("Error parsing token: ", err) errorRes["error_description"] = err.Error() @@ -50,7 +50,14 @@ func VerifyEmailHandler() gin.HandlerFunc { return } - user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) + if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil { + log.Debug("Error validating jwt claims: ", err) + errorRes["error_description"] = err.Error() + c.JSON(400, errorRes) + return + } + + user, err := db.Provider.GetUserByEmail(verificationRequest.Email) if err != nil { log.Debug("Error getting user: ", err) errorRes["error_description"] = err.Error() @@ -100,12 +107,12 @@ func VerifyEmailHandler() gin.HandlerFunc { params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token cookie.SetSession(c, authToken.FingerPrintHash) - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) if authToken.RefreshToken != nil { params = params + `&refresh_token=${refresh_token}` - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } if redirectURL == "" { diff --git a/server/memorystore/providers/inmemory/provider.go b/server/memorystore/providers/inmemory/provider.go index 0dec662..952092d 100644 --- a/server/memorystore/providers/inmemory/provider.go +++ b/server/memorystore/providers/inmemory/provider.go @@ -2,24 +2,23 @@ package inmemory import ( "sync" + + "github.com/authorizerdev/authorizer/server/memorystore/providers/inmemory/stores" ) type provider struct { mutex sync.Mutex - sessionStore map[string]map[string]string - stateStore map[string]string - envStore *EnvStore + sessionStore *stores.SessionStore + stateStore *stores.StateStore + envStore *stores.EnvStore } // NewInMemoryStore returns a new in-memory store. func NewInMemoryProvider() (*provider, error) { return &provider{ mutex: sync.Mutex{}, - sessionStore: map[string]map[string]string{}, - stateStore: map[string]string{}, - envStore: &EnvStore{ - mutex: sync.Mutex{}, - store: map[string]interface{}{}, - }, + envStore: stores.NewEnvStore(), + sessionStore: stores.NewSessionStore(), + stateStore: stores.NewStateStore(), }, nil } diff --git a/server/memorystore/providers/inmemory/store.go b/server/memorystore/providers/inmemory/store.go index 44de3b7..21046ce 100644 --- a/server/memorystore/providers/inmemory/store.go +++ b/server/memorystore/providers/inmemory/store.go @@ -3,46 +3,44 @@ package inmemory import ( "fmt" "os" - "strings" "github.com/authorizerdev/authorizer/server/constants" ) -// ClearStore clears the in-memory store. -func (c *provider) ClearStore() error { - if os.Getenv("ENV") != constants.TestEnv { - c.mutex.Lock() - defer c.mutex.Unlock() - } - c.sessionStore = map[string]map[string]string{} - +// SetUserSession sets the user session +func (c *provider) SetUserSession(userId, key, token string) error { + c.sessionStore.Set(userId, key, token) return nil } -// GetUserSessions returns all the user session token from the in-memory store. -func (c *provider) GetUserSessions(userId string) map[string]string { - res := map[string]string{} - for k, v := range c.stateStore { - split := strings.Split(v, "@") - if split[1] == userId { - res[k] = split[0] - } - } - - return res +// GetAllUserSessions returns all the user sessions token from the in-memory store. +func (c *provider) GetAllUserSessions(userId string) (map[string]string, error) { + data := c.sessionStore.GetAll(userId) + return data, nil } -// DeleteAllUserSession deletes all the user sessions from in-memory store. -func (c *provider) DeleteAllUserSession(userId string) error { +// GetUserSession returns value for given session token +func (c *provider) GetUserSession(userId, sessionToken string) (string, error) { + return c.sessionStore.Get(userId, sessionToken), nil +} + +// DeleteAllUserSessions deletes all the user sessions from in-memory store. +func (c *provider) DeleteAllUserSessions(userId string) error { if os.Getenv("ENV") != constants.TestEnv { c.mutex.Lock() defer c.mutex.Unlock() } - sessions := c.GetUserSessions(userId) - for k := range sessions { - c.RemoveState(k) - } + c.sessionStore.RemoveAll(userId) + return nil +} +// DeleteUserSession deletes the user session from the in-memory store. +func (c *provider) DeleteUserSession(userId, sessionToken string) error { + if os.Getenv("ENV") != constants.TestEnv { + c.mutex.Lock() + defer c.mutex.Unlock() + } + c.sessionStore.Remove(userId, sessionToken) return nil } @@ -52,29 +50,19 @@ func (c *provider) SetState(key, state string) error { c.mutex.Lock() defer c.mutex.Unlock() } - c.stateStore[key] = state + c.stateStore.Set(key, state) return nil } // GetState gets the state from the in-memory store. func (c *provider) GetState(key string) (string, error) { - state := "" - if stateVal, ok := c.stateStore[key]; ok { - state = stateVal - } - - return state, nil + return c.stateStore.Get(key), nil } // RemoveState removes the state from the in-memory store. func (c *provider) RemoveState(key string) error { - if os.Getenv("ENV") != constants.TestEnv { - c.mutex.Lock() - defer c.mutex.Unlock() - } - delete(c.stateStore, key) - + c.stateStore.Remove(key) return nil } diff --git a/server/memorystore/providers/inmemory/envstore.go b/server/memorystore/providers/inmemory/stores/env_store.go similarity index 84% rename from server/memorystore/providers/inmemory/envstore.go rename to server/memorystore/providers/inmemory/stores/env_store.go index 25d0712..d1a3feb 100644 --- a/server/memorystore/providers/inmemory/envstore.go +++ b/server/memorystore/providers/inmemory/stores/env_store.go @@ -1,4 +1,4 @@ -package inmemory +package stores import ( "os" @@ -13,6 +13,14 @@ type EnvStore struct { store map[string]interface{} } +// NewEnvStore create a new env store +func NewEnvStore() *EnvStore { + return &EnvStore{ + mutex: sync.Mutex{}, + store: make(map[string]interface{}), + } +} + // UpdateEnvStore to update the whole env store object func (e *EnvStore) UpdateStore(store map[string]interface{}) { if os.Getenv("ENV") != constants.TestEnv { diff --git a/server/memorystore/providers/inmemory/stores/session_store.go b/server/memorystore/providers/inmemory/stores/session_store.go new file mode 100644 index 0000000..c75a3d7 --- /dev/null +++ b/server/memorystore/providers/inmemory/stores/session_store.go @@ -0,0 +1,67 @@ +package stores + +import ( + "os" + "sync" + + "github.com/authorizerdev/authorizer/server/constants" +) + +// SessionStore struct to store the env variables +type SessionStore struct { + mutex sync.Mutex + store map[string]map[string]string +} + +// NewSessionStore create a new session store +func NewSessionStore() *SessionStore { + return &SessionStore{ + mutex: sync.Mutex{}, + store: make(map[string]map[string]string), + } +} + +// Get returns the value of the key in state store +func (s *SessionStore) Get(key, subKey string) string { + return s.store[key][subKey] +} + +// Set sets the value of the key in state store +func (s *SessionStore) Set(key string, subKey, value string) { + if os.Getenv("ENV") != constants.TestEnv { + s.mutex.Lock() + defer s.mutex.Unlock() + } + if _, ok := s.store[key]; !ok { + s.store[key] = make(map[string]string) + } + s.store[key][subKey] = value +} + +// RemoveAll all values for given key +func (s *SessionStore) RemoveAll(key string) { + if os.Getenv("ENV") != constants.TestEnv { + s.mutex.Lock() + defer s.mutex.Unlock() + } + delete(s.store, key) +} + +// Remove value for given key and subkey +func (s *SessionStore) Remove(key, subKey string) { + if os.Getenv("ENV") != constants.TestEnv { + s.mutex.Lock() + defer s.mutex.Unlock() + } + if _, ok := s.store[key]; ok { + delete(s.store[key], subKey) + } +} + +// Get all the values for given key +func (s *SessionStore) GetAll(key string) map[string]string { + if _, ok := s.store[key]; !ok { + s.store[key] = make(map[string]string) + } + return s.store[key] +} diff --git a/server/memorystore/providers/inmemory/stores/state_store.go b/server/memorystore/providers/inmemory/stores/state_store.go new file mode 100644 index 0000000..5e66b6e --- /dev/null +++ b/server/memorystore/providers/inmemory/stores/state_store.go @@ -0,0 +1,46 @@ +package stores + +import ( + "os" + "sync" + + "github.com/authorizerdev/authorizer/server/constants" +) + +// StateStore struct to store the env variables +type StateStore struct { + mutex sync.Mutex + store map[string]string +} + +// NewStateStore create a new state store +func NewStateStore() *StateStore { + return &StateStore{ + mutex: sync.Mutex{}, + store: make(map[string]string), + } +} + +// Get returns the value of the key in state store +func (s *StateStore) Get(key string) string { + return s.store[key] +} + +// Set sets the value of the key in state store +func (s *StateStore) Set(key string, value string) { + if os.Getenv("ENV") != constants.TestEnv { + s.mutex.Lock() + defer s.mutex.Unlock() + } + s.store[key] = value +} + +// Remove removes the key from state store +func (s *StateStore) Remove(key string) { + if os.Getenv("ENV") != constants.TestEnv { + s.mutex.Lock() + defer s.mutex.Unlock() + } + + delete(s.store, key) +} diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go index b9730f3..3c4d67d 100644 --- a/server/memorystore/providers/providers.go +++ b/server/memorystore/providers/providers.go @@ -2,12 +2,17 @@ package providers // Provider defines current memory store provider type Provider interface { + // SetUserSession sets the user session + SetUserSession(userId, key, token string) error + // GetAllUserSessions returns all the user sessions from the session store + GetAllUserSessions(userId string) (map[string]string, error) + // GetUserSession returns the session token for given token + GetUserSession(userId, token string) (string, error) + // DeleteUserSession deletes the user session + DeleteUserSession(userId, token string) error // DeleteAllSessions deletes all the sessions from the session store - DeleteAllUserSession(userId string) error - // GetUserSessions returns all the user sessions from the session store - GetUserSessions(userId string) map[string]string - // ClearStore clears the session store for authorizer tokens - ClearStore() error + DeleteAllUserSessions(userId string) error + // SetState sets the login state (key, value form) in the session store SetState(key, state string) error // GetState returns the state from the session store diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index 43dd761..8981294 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -2,67 +2,72 @@ package redis import ( "strconv" - "strings" "github.com/authorizerdev/authorizer/server/constants" log "github.com/sirupsen/logrus" ) var ( - // session store prefix - sessionStorePrefix = "authorizer_session:" + // state store prefix + stateStorePrefix = "authorizer_state:" // env store prefix envStorePrefix = "authorizer_env" ) -// ClearStore clears the redis store for authorizer related tokens -func (c *provider) ClearStore() error { - err := c.store.Del(c.ctx, sessionStorePrefix+"*").Err() +// SetUserSession sets the user session in redis store. +func (c *provider) SetUserSession(userId, key, token string) error { + err := c.store.HSet(c.ctx, userId, key, token).Err() if err != nil { - log.Debug("Error clearing redis store: ", err) + log.Debug("Error saving to redis: ", err) return err } - return nil } -// GetUserSessions returns all the user session token from the redis store. -func (c *provider) GetUserSessions(userID string) map[string]string { - data, err := c.store.HGetAll(c.ctx, "*").Result() +// GetAllUserSessions returns all the user session token from the redis store. +func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) { + data, err := c.store.HGetAll(c.ctx, userID).Result() if err != nil { - log.Debug("error getting token from redis store: ", err) + log.Debug("error getting all user sessions from redis store: ", err) + return nil, err } - res := map[string]string{} - for k, v := range data { - split := strings.Split(v, "@") - if split[1] == userID { - res[k] = split[0] - } - } - - return res + return data, nil } -// DeleteAllUserSession deletes all the user session from redis -func (c *provider) DeleteAllUserSession(userId string) error { - sessions := c.GetUserSessions(userId) - for k, v := range sessions { - if k == "token" { - err := c.store.Del(c.ctx, v).Err() - if err != nil { - log.Debug("Error deleting redis token: ", err) - return err - } - } +// GetUserSession returns the user session from redis store. +func (c *provider) GetUserSession(userId, key string) (string, error) { + var res string + err := c.store.HGet(c.ctx, userId, key).Scan(&res) + if err != nil { + return "", err } + return res, nil +} +// DeleteUserSession deletes the user session from redis store. +func (c *provider) DeleteUserSession(userId, key string) error { + err := c.store.HDel(c.ctx, userId, key).Err() + if err != nil { + log.Debug("Error deleting user session from redis: ", err) + return err + } + return nil +} + +// DeleteAllUserSessions deletes all the user session from redis +func (c *provider) DeleteAllUserSessions(userID string) error { + err := c.store.HDel(c.ctx, userID).Err() + if err != nil { + log.Debug("Error deleting all user sessions from redis: ", err) + return err + } return nil } // SetState sets the state in redis store. func (c *provider) SetState(key, value string) error { - err := c.store.Set(c.ctx, sessionStorePrefix+key, value, 0).Err() + err := c.store.Set(c.ctx, stateStorePrefix+key, value, 0).Err() if err != nil { log.Debug("Error saving redis token: ", err) return err @@ -74,7 +79,7 @@ func (c *provider) SetState(key, value string) error { // GetState gets the state from redis store. func (c *provider) GetState(key string) (string, error) { var res string - err := c.store.Get(c.ctx, sessionStorePrefix+key).Scan(&res) + err := c.store.Get(c.ctx, stateStorePrefix+key).Scan(&res) if err != nil { log.Debug("error getting token from redis store: ", err) } @@ -84,7 +89,7 @@ func (c *provider) GetState(key string) (string, error) { // RemoveState removes the state from redis store. func (c *provider) RemoveState(key string) error { - err := c.store.Del(c.ctx, sessionStorePrefix+key).Err() + err := c.store.Del(c.ctx, stateStorePrefix+key).Err() if err != nil { log.Fatalln("Error deleting redis token: ", err) return err diff --git a/server/resolvers/delete_user.go b/server/resolvers/delete_user.go index df64443..5166769 100644 --- a/server/resolvers/delete_user.go +++ b/server/resolvers/delete_user.go @@ -38,7 +38,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod return res, err } - go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) + go memorystore.Provider.DeleteAllUserSessions(user.ID) err = db.Provider.DeleteUser(user) if err != nil { diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 54ff030..4209b85 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -117,12 +117,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } cookie.SetSession(gc, authToken.FingerPrintHash) - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } go db.Provider.AddSession(models.Session{ diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 2c81f63..528a247 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -2,6 +2,7 @@ package resolvers import ( "context" + "encoding/json" log "github.com/sirupsen/logrus" @@ -9,38 +10,41 @@ import ( "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) // LogoutResolver is a resolver for logout mutation func LogoutResolver(ctx context.Context) (*model.Response, error) { - var res *model.Response - gc, err := utils.GinContextFromContext(ctx) if err != nil { log.Debug("Failed to get GinContext: ", err) - return res, err + return nil, err } // get fingerprint hash fingerprintHash, err := cookie.GetSession(gc) if err != nil { log.Debug("Failed to get fingerprint hash: ", err) - return res, err + return nil, err } decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash) if err != nil { log.Debug("Failed to decrypt fingerprint hash: ", err) - return res, err + return nil, err } - fingerPrint := string(decryptedFingerPrint) + var sessionData token.SessionData + err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData) + if err != nil { + return nil, err + } - memorystore.Provider.RemoveState(fingerPrint) + memorystore.Provider.DeleteUserSession(sessionData.Subject, fingerprintHash) cookie.DeleteSession(gc) - res = &model.Response{ + res := &model.Response{ Message: "Logged out successfully", } diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index 9defd06..a3a2892 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -57,12 +57,17 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) // verify if token exists in db hostname := parsers.GetHost(gc) - claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(params.Token) if err != nil { log.Debug("Failed to parse token: ", err) return res, fmt.Errorf(`invalid token`) } + if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil { + log.Debug("Failed to validate jwt claims: ", err) + return res, fmt.Errorf(`invalid token`) + } + email := claim["sub"].(string) log := log.WithFields(log.Fields{ "email": email, diff --git a/server/resolvers/revoke_access.go b/server/resolvers/revoke_access.go index 9b24c71..34ecaad 100644 --- a/server/resolvers/revoke_access.go +++ b/server/resolvers/revoke_access.go @@ -47,7 +47,7 @@ func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) ( return res, err } - go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) + go memorystore.Provider.DeleteAllUserSessions(user.ID) res = &model.Response{ Message: `user access revoked successfully`, diff --git a/server/resolvers/session.go b/server/resolvers/session.go index 89b7e11..c7c56ac 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -77,8 +77,8 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod // rollover the session for security memorystore.Provider.RemoveState(sessionToken) - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -96,7 +96,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) } return res, nil diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 32904f9..789cb4e 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -225,7 +225,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + } + cookie.SetSession(gc, authToken.FingerPrintHash) go db.Provider.AddSession(models.Session{ UserID: user.ID, diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index 9a00276..fdc3203 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -142,7 +142,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) return res, fmt.Errorf("user with this email address already exists") } - go memorystore.Provider.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSessions(user.ID) go cookie.DeleteSession(gc) user.Email = newEmail diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index b1b72b6..d4585b9 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -113,7 +113,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod } // TODO figure out how to do this - go memorystore.Provider.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSessions(user.ID) hostname := parsers.GetHost(gc) user.Email = newEmail @@ -182,7 +182,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod rolesToSave = strings.Join(inputRoles, ",") } - go memorystore.Provider.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSessions(user.ID) } if rolesToSave != "" { diff --git a/server/resolvers/validate_jwt_token.go b/server/resolvers/validate_jwt_token.go index 1efdad5..ced584e 100644 --- a/server/resolvers/validate_jwt_token.go +++ b/server/resolvers/validate_jwt_token.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "strings" "github.com/golang-jwt/jwt" log "github.com/sirupsen/logrus" @@ -35,43 +34,46 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken return nil, errors.New("invalid token type") } + var claimRoles []string + var claims jwt.MapClaims userID := "" nonce := "" // access_token and refresh_token should be validated from session store as well if tokenType == "access_token" || tokenType == "refresh_token" { - savedSession, err := memorystore.Provider.GetState(params.Token) - if savedSession == "" || err != nil { - return &model.ValidateJWTTokenResponse{ - IsValid: false, - }, nil + claims, err = token.ParseJWTToken(params.Token) + if err != nil { + log.Debug("Failed to parse JWT token: ", err) + return nil, err } - savedSessionSplit := strings.Split(savedSession, "@") - nonce = savedSessionSplit[0] - userID = savedSessionSplit[1] + userID = claims["sub"].(string) + nonce, err = memorystore.Provider.GetUserSession(userID, params.Token) + if err != nil || nonce == "" { + log.Debug("Failed to get user session: ", err) + return nil, errors.New("invalid token") + } + } else { + // for ID token just parse jwt + claims, err = token.ParseJWTToken(params.Token) + if err != nil { + log.Debug("Failed to parse JWT token: ", err) + return nil, err + } + userID = claims["sub"].(string) } hostname := parsers.GetHost(gc) - var claimRoles []string - var claims jwt.MapClaims // we cannot validate sub and nonce in case of id_token as that token is not persisted in session store if userID != "" && nonce != "" { - claims, err = token.ParseJWTToken(params.Token, hostname, nonce, userID) - if err != nil { + if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil { log.Debug("Failed to parse jwt token: ", err) - return &model.ValidateJWTTokenResponse{ - IsValid: false, - }, nil + return nil, errors.New("invalid claims") } } else { - claims, err = token.ParseJWTTokenWithoutNonce(params.Token, hostname) - if err != nil { + if ok, err := token.ValidateJWTTokenWithoutNonce(claims, hostname); !ok || err != nil { log.Debug("Failed to parse jwt token without nonce: ", err) - return &model.ValidateJWTTokenResponse{ - IsValid: false, - }, nil + return nil, errors.New("invalid claims") } - } claimRolesInterface := claims["roles"] diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 3c1d9d6..e7e4af4 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -36,12 +36,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m // verify if token exists in db hostname := parsers.GetHost(gc) - claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(params.Token) if err != nil { log.Debug("Failed to parse token: ", err) return res, fmt.Errorf(`invalid token: %s`, err.Error()) } + if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil { + log.Debug("Failed to validate jwt claims: ", err) + return res, fmt.Errorf(`invalid token: %s`, err.Error()) + } + email := claim["sub"].(string) log := log.WithFields(log.Fields{ "email": email, @@ -75,8 +80,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m return res, err } - memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + } + cookie.SetSession(gc, authToken.FingerPrintHash) go db.Provider.AddSession(models.Session{ UserID: user.ID, diff --git a/server/test/jwt_test.go b/server/test/jwt_test.go index 9695406..5082689 100644 --- a/server/test/jwt_test.go +++ b/server/test/jwt_test.go @@ -52,7 +52,7 @@ func TestJwt(t *testing.T) { } jwtToken, err := token.SignJWTToken(expiredClaims) assert.NoError(t, err) - _, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject) + _, err = token.ParseJWTToken(jwtToken) assert.Error(t, err, err.Error(), "Token is expired") }) t.Run("HMAC algorithms", func(t *testing.T) { @@ -62,27 +62,36 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("HS384", func(t *testing.T) { memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS384") jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("HS512", func(t *testing.T) { memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS512") jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) }) @@ -96,9 +105,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("RS384", func(t *testing.T) { _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID) @@ -109,9 +121,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("RS512", func(t *testing.T) { _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID) @@ -122,9 +137,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) }) @@ -138,9 +156,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("ES384", func(t *testing.T) { _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID) @@ -151,9 +172,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) t.Run("ES512", func(t *testing.T) { _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID) @@ -164,9 +188,12 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) + c, err := token.ParseJWTToken(jwtToken) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) + valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject) + assert.NoError(t, err) + assert.True(t, valid) }) }) diff --git a/server/test/logout_test.go b/server/test/logout_test.go index 3b38e3f..18ca45a 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -2,6 +2,7 @@ package test import ( "fmt" + "strings" "testing" "github.com/authorizerdev/authorizer/server/constants" @@ -28,14 +29,11 @@ func logoutTests(t *testing.T, s TestSetup) { }) token := *verifyRes.AccessToken - sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) - cookie := "" - // set all they keys in cookie one of them should be session cookie - for key := range sessions { - if key != token { - cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key) - } - } + session, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, token) + assert.NoError(t, err) + assert.NotEmpty(t, session) + cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", session) + cookie = strings.TrimSuffix(cookie, ";") req.Header.Set("Cookie", cookie) _, err = resolvers.LogoutResolver(ctx) diff --git a/server/test/session_test.go b/server/test/session_test.go index 2c4de19..affcde9 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -33,15 +33,11 @@ func sessionTests(t *testing.T, s TestSetup) { Token: verificationRequest.Token, }) - sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) - cookie := "" token := *verifyRes.AccessToken - // set all they keys in cookie one of them should be session cookie - for key := range sessions { - if key != token { - cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key) - } - } + session, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, token) + assert.NoError(t, err) + assert.NotEmpty(t, session) + cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", session) cookie = strings.TrimSuffix(cookie, ";") req.Header.Set("Cookie", cookie) diff --git a/server/test/validate_jwt_token_test.go b/server/test/validate_jwt_token_test.go index 4207ebc..a49a825 100644 --- a/server/test/validate_jwt_token_test.go +++ b/server/test/validate_jwt_token_test.go @@ -22,12 +22,14 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { TokenType: "access_token", Token: "", }) - assert.False(t, res.IsValid) + assert.Error(t, err) + assert.Nil(t, res) res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ TokenType: "access_token", Token: "invalid", }) - assert.False(t, res.IsValid) + assert.Error(t, err) + assert.Nil(t, res) _, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ TokenType: "access_token_invalid", Token: "invalid@invalid", @@ -48,8 +50,9 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { gc, err := utils.GinContextFromContext(ctx) assert.NoError(t, err) authToken, err := token.CreateAuthToken(gc, user, roles, scope) - memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) - memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) t.Run(`should validate the access token`, func(t *testing.T) { res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ @@ -57,7 +60,6 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { Token: authToken.AccessToken.Token, Roles: []string{"user"}, }) - assert.NoError(t, err) assert.True(t, res.IsValid) diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 65cb0d1..7702f1d 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -198,18 +198,19 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf return res, fmt.Errorf(`unauthorized`) } - savedSession, err := memorystore.Provider.GetState(accessToken) - if savedSession == "" || err != nil { + res, err := ParseJWTToken(accessToken) + if err != nil { + return res, err + } + + userID := res["sub"].(string) + nonce, err := memorystore.Provider.GetUserSession(userID, accessToken) + if nonce == "" || err != nil { return res, fmt.Errorf(`unauthorized`) } - savedSessionSplit := strings.Split(savedSession, "@") - nonce := savedSessionSplit[0] - userID := savedSessionSplit[1] - hostname := parsers.GetHost(gc) - res, err = ParseJWTToken(accessToken, hostname, nonce, userID) - if err != nil { + if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { return res, err } @@ -228,18 +229,19 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte return res, fmt.Errorf(`unauthorized`) } - savedSession, err := memorystore.Provider.GetState(refreshToken) - if savedSession == "" || err != nil { + res, err := ParseJWTToken(refreshToken) + if err != nil { + return res, err + } + + userID := res["sub"].(string) + nonce, err := memorystore.Provider.GetUserSession(userID, refreshToken) + if nonce == "" || err != nil { return res, fmt.Errorf(`unauthorized`) } - savedSessionSplit := strings.Split(savedSession, "@") - nonce := savedSessionSplit[0] - userID := savedSessionSplit[1] - hostname := parsers.GetHost(gc) - res, err = ParseJWTToken(refreshToken, hostname, nonce, userID) - if err != nil { + if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { return res, err } @@ -255,15 +257,6 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD return nil, fmt.Errorf(`unauthorized`) } - savedSession, err := memorystore.Provider.GetState(encryptedSession) - if savedSession == "" || err != nil { - return nil, fmt.Errorf(`unauthorized`) - } - - savedSessionSplit := strings.Split(savedSession, "@") - nonce := savedSessionSplit[0] - userID := savedSessionSplit[1] - decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession) if err != nil { return nil, err @@ -275,23 +268,20 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD return nil, err } - if res.Nonce != nonce { - return nil, fmt.Errorf(`unauthorized: invalid nonce`) + nonce, err := memorystore.Provider.GetUserSession(res.Subject, encryptedSession) + if nonce == "" || err != nil { + log.Debug("invalid browser session:", err) + return nil, fmt.Errorf(`unauthorized`) } - if res.Subject != userID { - return nil, fmt.Errorf(`unauthorized: invalid user id`) + if res.Nonce != nonce { + return nil, fmt.Errorf(`unauthorized: invalid nonce`) } if res.ExpiresAt < time.Now().Unix() { return nil, fmt.Errorf(`unauthorized: token expired`) } - // TODO validate scope - // if !reflect.DeepEqual(res.Roles, roles) { - // return res, "", fmt.Errorf(`unauthorized`) - // } - return &res, nil } diff --git a/server/token/jwt.go b/server/token/jwt.go index af7c020..d32487c 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -60,7 +60,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) { } // ParseJWTToken common util to parse jwt token -func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) { +func ParseJWTToken(token string) (jwt.MapClaims, error) { jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType) if err != nil { return nil, err @@ -116,98 +116,48 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error intIat := int64(claims["iat"].(float64)) claims["exp"] = intExp claims["iat"] = intIat + + return claims, nil +} + +// ValidateJWTClaims common util to validate claims +func ValidateJWTClaims(claims jwt.MapClaims, hostname, nonce, subject string) (bool, error) { clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) if err != nil { - return claims, err + return false, err } if claims["aud"] != clientID { - return claims, errors.New("invalid audience") + return false, errors.New("invalid audience") } if claims["nonce"] != nonce { - return claims, errors.New("invalid nonce") + return false, errors.New("invalid nonce") } if claims["iss"] != hostname { - return claims, errors.New("invalid issuer") + return false, errors.New("invalid issuer") } if claims["sub"] != subject { - return claims, errors.New("invalid subject") + return false, errors.New("invalid subject") } - return claims, nil + return true, nil } -// ParseJWTTokenWithoutNonce common util to parse jwt token without nonce -// used to validate ID token as it is not persisted in store -func ParseJWTTokenWithoutNonce(token, hostname string) (jwt.MapClaims, error) { - jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType) - if err != nil { - return nil, err - } - signingMethod := jwt.GetSigningMethod(jwtType) - - var claims jwt.MapClaims - - switch signingMethod { - case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512: - _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - jwtSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret) - if err != nil { - return nil, err - } - return []byte(jwtSecret), nil - }) - case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512: - _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey) - if err != nil { - return nil, err - } - key, err := crypto.ParseRsaPublicKeyFromPemStr(jwtPublicKey) - if err != nil { - return nil, err - } - return key, nil - }) - case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512: - _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey) - if err != nil { - return nil, err - } - key, err := crypto.ParseEcdsaPublicKeyFromPemStr(jwtPublicKey) - if err != nil { - return nil, err - } - return key, nil - }) - default: - err = errors.New("unsupported signing method") - } - if err != nil { - return claims, err - } - - // claim parses exp & iat into float 64 with e^10, - // but we expect it to be int64 - // hence we need to assert interface and convert to int64 - intExp := int64(claims["exp"].(float64)) - intIat := int64(claims["iat"].(float64)) - claims["exp"] = intExp - claims["iat"] = intIat +// ValidateJWTClaimsWithoutNonce common util to validate claims without nonce +func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname string) (bool, error) { clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) if err != nil { - return claims, err + return false, err } if claims["aud"] != clientID { - return claims, errors.New("invalid audience") + return false, errors.New("invalid audience") } if claims["iss"] != hostname { - return claims, errors.New("invalid issuer") + return false, errors.New("invalid issuer") } - return claims, nil + return true, nil }