From 82a2a42f848f790592bda78d388fbac7e683ae7d Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 12 Jun 2022 00:27:21 +0530 Subject: [PATCH] fix: user session access --- server/constants/token_types.go | 2 ++ server/db/models/user.go | 3 +- server/db/providers/sql/user.go | 2 -- server/handlers/authorize.go | 15 ++++---- server/handlers/logout.go | 15 +++++--- server/handlers/oauth_callback.go | 13 ++++--- server/handlers/revoke.go | 13 ++++++- server/handlers/token.go | 13 +++---- server/handlers/verify_email.go | 9 ++--- server/memorystore/providers/providers.go | 4 +-- server/memorystore/providers/redis/store.go | 34 ++++++++++-------- server/resolvers/login.go | 6 ++-- server/resolvers/logout.go | 2 +- server/resolvers/session.go | 15 ++++---- server/resolvers/signup.go | 18 +++++----- server/resolvers/validate_jwt_token.go | 38 +++++++++------------ server/resolvers/verify_email.go | 19 ++++++----- server/test/logout_test.go | 22 ++++++------ server/test/session_test.go | 22 ++++++------ server/test/validate_jwt_token_test.go | 10 ++++-- server/token/auth_token.go | 20 ++++++++--- server/token/jwt.go | 7 ++-- 22 files changed, 172 insertions(+), 130 deletions(-) diff --git a/server/constants/token_types.go b/server/constants/token_types.go index df671bf..023ce4e 100644 --- a/server/constants/token_types.go +++ b/server/constants/token_types.go @@ -7,4 +7,6 @@ const ( TokenTypeAccessToken = "access_token" // TokenTypeIdentityToken is the identity_token token type TokenTypeIdentityToken = "id_token" + // TokenTypeSessionToken is the session_token type used for browser session + TokenTypeSessionToken = "session_token" ) diff --git a/server/db/models/user.go b/server/db/models/user.go index e072650..12709d4 100644 --- a/server/db/models/user.go +++ b/server/db/models/user.go @@ -38,7 +38,6 @@ func (user *User) AsAPIUser() *model.User { email := user.Email createdAt := user.CreatedAt updatedAt := user.UpdatedAt - revokedTimestamp := user.RevokedTimestamp return &model.User{ ID: user.ID, Email: user.Email, @@ -55,7 +54,7 @@ func (user *User) AsAPIUser() *model.User { PhoneNumberVerified: &isPhoneVerified, Picture: user.Picture, Roles: strings.Split(user.Roles, ","), - RevokedTimestamp: revokedTimestamp, + RevokedTimestamp: user.RevokedTimestamp, CreatedAt: &createdAt, UpdatedAt: &updatedAt, } diff --git a/server/db/providers/sql/user.go b/server/db/providers/sql/user.go index e7e999e..6d4d889 100644 --- a/server/db/providers/sql/user.go +++ b/server/db/providers/sql/user.go @@ -97,7 +97,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) func (p *provider) GetUserByEmail(email string) (models.User, error) { var user models.User result := p.db.Where("email = ?", email).First(&user) - if result.Error != nil { return user, result.Error } @@ -110,7 +109,6 @@ func (p *provider) GetUserByID(id string) (models.User, error) { var user models.User result := p.db.Where("id = ?", id).First(&user) - if result.Error != nil { return user, result.Error } diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 35015dc..3a8b6ab 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -222,10 +222,7 @@ func AuthorizeHandler() gin.HandlerFunc { // based on the response type, generate the response if isResponseTypeCode { // rollover the session for security - err = memorystore.Provider.RemoveState(sessionToken) - if err != nil { - log.Debug("Failed to remove state: ", err) - } + go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce) nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) if err != nil { @@ -246,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc { return } - memorystore.Provider.SetUserSession(user.ID, newSessionToken, newSessionTokenData.Nonce) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken) cookie.SetSession(gc, newSessionToken) code := uuid.New().String() memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) @@ -283,9 +280,9 @@ func AuthorizeHandler() gin.HandlerFunc { } return } - memorystore.Provider.RemoveState(sessionToken) - memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -308,7 +305,7 @@ func AuthorizeHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } if isQuery { diff --git a/server/handlers/logout.go b/server/handlers/logout.go index e207b87..bf1d69e 100644 --- a/server/handlers/logout.go +++ b/server/handlers/logout.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/json" "net/http" "strings" @@ -10,6 +11,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/token" ) // Handler to logout user @@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc { return } - fingerPrint := string(decryptedFingerPrint) - - err = memorystore.Provider.RemoveState(fingerPrint) + var sessionData token.SessionData + err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData) if err != nil { - log.Debug("Failed to remove state: ", err) + log.Debug("Failed to decrypt fingerprint: ", err) + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return } + + memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce) cookie.DeleteSession(gc) if redirectURL != "" { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 419de67..0ccc113 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -36,7 +36,6 @@ func OAuthCallbackHandler() gin.HandlerFunc { log.Debug("Invalid oauth state: ", state) c.JSON(400, gin.H{"error": "invalid oauth state"}) } - memorystore.Provider.GetState(state) // contains random token, redirect url, role sessionSplit := strings.Split(state, "___") @@ -46,6 +45,9 @@ func OAuthCallbackHandler() gin.HandlerFunc { return } + // remove state from store + go memorystore.Provider.RemoveState(state) + stateValue := sessionSplit[0] redirectURL := sessionSplit[1] inputRoles := strings.Split(sessionSplit[2], ",") @@ -117,9 +119,11 @@ func OAuthCallbackHandler() gin.HandlerFunc { user.EmailVerifiedAt = &now user, _ = db.Provider.AddUser(user) } else { + user = existingUser if user.RevokedTimestamp != nil { log.Debug("User access revoked at: ", user.RevokedTimestamp) c.JSON(400, gin.H{"error": "user access has been revoked"}) + return } // user exists in db, check if method was google @@ -128,7 +132,6 @@ func OAuthCallbackHandler() gin.HandlerFunc { if !strings.Contains(signupMethod, provider) { signupMethod = signupMethod + "," + provider } - user = existingUser user.SignupMethods = signupMethod if user.EmailVerifiedAt == nil { @@ -200,12 +203,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.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) if authToken.RefreshToken != nil { params = params + `&refresh_token=` + authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } go db.Provider.AddSession(models.Session{ diff --git a/server/handlers/revoke.go b/server/handlers/revoke.go index 9cc5b07..6e71ee3 100644 --- a/server/handlers/revoke.go +++ b/server/handlers/revoke.go @@ -9,6 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/token" ) // Revoke handler to revoke refresh token @@ -45,7 +46,17 @@ func RevokeHandler() gin.HandlerFunc { return } - memorystore.Provider.RemoveState(refreshToken) + claims, err := token.ParseJWTToken(refreshToken) + if err != nil { + log.Debug("Client ID is invalid: ", clientID) + gc.JSON(http.StatusBadRequest, gin.H{ + "error": err.Error(), + "error_description": "Failed to parse jwt", + }) + return + } + + memorystore.Provider.DeleteUserSession(claims["sub"].(string), claims["nonce"].(string)) gc.JSON(http.StatusOK, gin.H{ "message": "Token revoked successfully", diff --git a/server/handlers/token.go b/server/handlers/token.go index 0f9573a..4740466 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -107,6 +107,7 @@ func TokenHandler() gin.HandlerFunc { return } + go memorystore.Provider.RemoveState(encryptedCode) // split session data // it contains code@sessiontoken sessionDataSplit := strings.Split(sessionData, "@") @@ -130,11 +131,11 @@ func TokenHandler() gin.HandlerFunc { }) return } - // rollover the session for security - memorystore.Provider.RemoveState(sessionDataSplit[1]) userID = claims.Subject roles = claims.Roles scope = claims.Scope + // rollover the session for security + go memorystore.Provider.DeleteUserSession(userID, claims.Nonce) } else { // validate refresh token if refreshToken == "" { @@ -163,7 +164,7 @@ func TokenHandler() gin.HandlerFunc { scope = append(scope, v.(string)) } // remove older refresh token and rotate it for security - memorystore.Provider.RemoveState(refreshToken) + go memorystore.Provider.DeleteUserSession(userID, claims["nonce"].(string)) } user, err := db.Provider.GetUserByID(userID) @@ -185,8 +186,8 @@ func TokenHandler() gin.HandlerFunc { }) return } - memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -204,7 +205,7 @@ func TokenHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } gc.JSON(http.StatusOK, res) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 1488254..3eb7324 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" @@ -107,12 +108,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.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) if authToken.RefreshToken != nil { - params = params + `&refresh_token=${refresh_token}` - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + params = params + `&refresh_token=` + authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } if redirectURL == "" { diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go index 3c4d67d..f3b2471 100644 --- a/server/memorystore/providers/providers.go +++ b/server/memorystore/providers/providers.go @@ -7,9 +7,9 @@ type Provider interface { // 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) + GetUserSession(userId, key string) (string, error) // DeleteUserSession deletes the user session - DeleteUserSession(userId, token string) error + DeleteUserSession(userId, key string) error // DeleteAllSessions deletes all the sessions from the session store DeleteAllUserSessions(userId string) error diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index 8981294..53a087a 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -37,18 +37,24 @@ func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) // 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) + data, err := c.store.HGet(c.ctx, userId, key).Result() if err != nil { return "", err } - return res, nil + return data, 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 { + if err := c.store.HDel(c.ctx, userId, constants.TokenTypeSessionToken+"_"+key).Err(); err != nil { + log.Debug("Error deleting user session from redis: ", err) + return err + } + if err := c.store.HDel(c.ctx, userId, constants.TokenTypeAccessToken+"_"+key).Err(); err != nil { + log.Debug("Error deleting user session from redis: ", err) + return err + } + if err := c.store.HDel(c.ctx, userId, constants.TokenTypeRefreshToken+"_"+key).Err(); err != nil { log.Debug("Error deleting user session from redis: ", err) return err } @@ -57,7 +63,7 @@ func (c *provider) DeleteUserSession(userId, key string) error { // DeleteAllUserSessions deletes all the user session from redis func (c *provider) DeleteAllUserSessions(userID string) error { - err := c.store.HDel(c.ctx, userID).Err() + err := c.store.Del(c.ctx, userID).Err() if err != nil { log.Debug("Error deleting all user sessions from redis: ", err) return err @@ -78,13 +84,13 @@ 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, stateStorePrefix+key).Scan(&res) + data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result() if err != nil { log.Debug("error getting token from redis store: ", err) + return "", err } - return res, err + return data, err } // RemoveState removes the state from redis store. @@ -142,22 +148,20 @@ func (c *provider) UpdateEnvVariable(key string, value interface{}) error { // GetStringStoreEnvVariable to get the string env variable from env store func (c *provider) GetStringStoreEnvVariable(key string) (string, error) { - var res string - err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res) + data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result() if err != nil { return "", nil } - return res, nil + return data, nil } // GetBoolStoreEnvVariable to get the bool env variable from env store func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) { - var res bool - err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res) + data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result() if err != nil { return false, nil } - return res, nil + return data == "1", nil } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 4209b85..993ffce 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.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } go db.Provider.AddSession(models.Session{ diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 528a247..4dd8269 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -41,7 +41,7 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) { return nil, err } - memorystore.Provider.DeleteUserSession(sessionData.Subject, fingerprintHash) + memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce) cookie.DeleteSession(gc) res := &model.Response{ diff --git a/server/resolvers/session.go b/server/resolvers/session.go index c7c56ac..e2f5061 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" @@ -29,7 +30,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod sessionToken, err := cookie.GetSession(gc) if err != nil { - log.Debug("Failed to get session token", err) + log.Debug("Failed to get session token: ", err) return res, errors.New("unauthorized") } @@ -76,10 +77,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod } // rollover the session for security - memorystore.Provider.RemoveState(sessionToken) - memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) - memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) - cookie.SetSession(gc, authToken.FingerPrintHash) + go memorystore.Provider.DeleteUserSession(userID, claims.Nonce) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { @@ -94,10 +92,13 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod User: user.AsAPIUser(), } + cookie.SetSession(gc, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } - return res, nil } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 789cb4e..f0a9007 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -225,15 +225,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } - 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, UserAgent: utils.GetUserAgent(gc.Request), @@ -251,6 +242,15 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR ExpiresIn: &expiresIn, User: userToReturn, } + + cookie.SetSession(gc, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) + } } return res, nil diff --git a/server/resolvers/validate_jwt_token.go b/server/resolvers/validate_jwt_token.go index ced584e..4139826 100644 --- a/server/resolvers/validate_jwt_token.go +++ b/server/resolvers/validate_jwt_token.go @@ -8,6 +8,7 @@ import ( "github.com/golang-jwt/jwt" log "github.com/sirupsen/logrus" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/parsers" @@ -29,7 +30,7 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken } tokenType := params.TokenType - if tokenType != "access_token" && tokenType != "refresh_token" && tokenType != "id_token" { + if tokenType != constants.TokenTypeAccessToken && tokenType != constants.TokenTypeRefreshToken && tokenType != constants.TokenTypeIdentityToken { log.Debug("Invalid token type: ", tokenType) return nil, errors.New("invalid token type") } @@ -38,39 +39,34 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken var claims jwt.MapClaims userID := "" nonce := "" + + claims, err = token.ParseJWTToken(params.Token) + if err != nil { + log.Debug("Failed to parse JWT token: ", err) + return nil, err + } + userID = claims["sub"].(string) + // access_token and refresh_token should be validated from session store as well - if tokenType == "access_token" || tokenType == "refresh_token" { - claims, err = token.ParseJWTToken(params.Token) - if err != nil { - log.Debug("Failed to parse JWT token: ", err) - return nil, err - } - userID = claims["sub"].(string) - nonce, err = memorystore.Provider.GetUserSession(userID, params.Token) - if err != nil || nonce == "" { + if tokenType == constants.TokenTypeAccessToken || tokenType == constants.TokenTypeRefreshToken { + nonce = claims["nonce"].(string) + token, err := memorystore.Provider.GetUserSession(userID, tokenType+"_"+claims["nonce"].(string)) + if err != nil || token == "" { 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) - // we cannot validate sub and nonce in case of id_token as that token is not persisted in session store - if userID != "" && nonce != "" { + // we cannot validate nonce in case of id_token as that token is not persisted in session store + if nonce != "" { if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil { log.Debug("Failed to parse jwt token: ", err) return nil, errors.New("invalid claims") } } else { - if ok, err := token.ValidateJWTTokenWithoutNonce(claims, hostname); !ok || err != nil { + if ok, err := token.ValidateJWTTokenWithoutNonce(claims, hostname, userID); !ok || err != nil { log.Debug("Failed to parse jwt token without nonce: ", err) return nil, errors.New("invalid claims") } diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index e7e4af4..e1d5827 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" @@ -80,15 +81,6 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m return res, err } - 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, UserAgent: utils.GetUserAgent(gc.Request), @@ -107,5 +99,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m ExpiresIn: &expiresIn, User: user.AsAPIUser(), } + + cookie.SetSession(gc, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) + } return res, nil } diff --git a/server/test/logout_test.go b/server/test/logout_test.go index 736bd98..d0d6228 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -10,6 +10,7 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/token" "github.com/stretchr/testify/assert" ) @@ -28,17 +29,18 @@ func logoutTests(t *testing.T, s TestSetup) { Token: verificationRequest.Token, }) - token := *verifyRes.AccessToken - sessions, err := memorystore.Provider.GetAllUserSessions(verifyRes.User.ID) + accessToken := *verifyRes.AccessToken + assert.NotEmpty(t, accessToken) + + claims, err := token.ParseJWTToken(accessToken) assert.NoError(t, err) - assert.NotEmpty(t, sessions) - 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) - } - } + assert.NotEmpty(t, claims) + + sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string)) + assert.NoError(t, err) + assert.NotEmpty(t, sessionToken) + + cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken) cookie = strings.TrimSuffix(cookie, ";") req.Header.Set("Cookie", cookie) diff --git a/server/test/session_test.go b/server/test/session_test.go index 66e2c89..474be37 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -10,6 +10,7 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/token" "github.com/stretchr/testify/assert" ) @@ -33,17 +34,18 @@ func sessionTests(t *testing.T, s TestSetup) { Token: verificationRequest.Token, }) - token := *verifyRes.AccessToken - sessions, err := memorystore.Provider.GetAllUserSessions(verifyRes.User.ID) + accessToken := *verifyRes.AccessToken + assert.NotEmpty(t, accessToken) + + claims, err := token.ParseJWTToken(accessToken) assert.NoError(t, err) - assert.NotEmpty(t, sessions) - 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) - } - } + assert.NotEmpty(t, claims) + + sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string)) + assert.NoError(t, err) + assert.NotEmpty(t, sessionToken) + + cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken) 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 a49a825..c353e6e 100644 --- a/server/test/validate_jwt_token_test.go +++ b/server/test/validate_jwt_token_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" @@ -50,9 +51,12 @@ 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.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) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + + if authToken.RefreshToken != nil { + memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) + } t.Run(`should validate the access token`, func(t *testing.T) { res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 7702f1d..0076ff9 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -204,11 +204,16 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf } userID := res["sub"].(string) - nonce, err := memorystore.Provider.GetUserSession(userID, accessToken) + nonce := res["nonce"].(string) + token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeAccessToken+"_"+nonce) if nonce == "" || err != nil { return res, fmt.Errorf(`unauthorized`) } + if token != accessToken { + return res, fmt.Errorf(`unauthorized`) + } + hostname := parsers.GetHost(gc) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { return res, err @@ -235,11 +240,16 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte } userID := res["sub"].(string) - nonce, err := memorystore.Provider.GetUserSession(userID, refreshToken) + nonce := res["nonce"].(string) + token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeRefreshToken+"_"+nonce) if nonce == "" || err != nil { return res, fmt.Errorf(`unauthorized`) } + if token != refreshToken { + return res, fmt.Errorf(`unauthorized`) + } + hostname := parsers.GetHost(gc) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { return res, err @@ -268,13 +278,13 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD return nil, err } - nonce, err := memorystore.Provider.GetUserSession(res.Subject, encryptedSession) - if nonce == "" || err != nil { + token, err := memorystore.Provider.GetUserSession(res.Subject, constants.TokenTypeSessionToken+"_"+res.Nonce) + if token == "" || err != nil { log.Debug("invalid browser session:", err) return nil, fmt.Errorf(`unauthorized`) } - if res.Nonce != nonce { + if encryptedSession != token { return nil, fmt.Errorf(`unauthorized: invalid nonce`) } diff --git a/server/token/jwt.go b/server/token/jwt.go index d32487c..4e5f0ed 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -145,8 +145,8 @@ func ValidateJWTClaims(claims jwt.MapClaims, hostname, nonce, subject string) (b return true, nil } -// ValidateJWTClaimsWithoutNonce common util to validate claims without nonce -func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname string) (bool, error) { +// ValidateJWTTokenWithoutNonce common util to validate claims without nonce +func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname, subject string) (bool, error) { clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) if err != nil { return false, err @@ -159,5 +159,8 @@ func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname string) (bool, return false, errors.New("invalid issuer") } + if claims["sub"] != subject { + return false, errors.New("invalid subject") + } return true, nil }