fix: user session access

This commit is contained in:
Lakhan Samani 2022-06-12 00:27:21 +05:30
parent ac49b5bb70
commit 82a2a42f84
22 changed files with 172 additions and 130 deletions

View File

@ -7,4 +7,6 @@ const (
TokenTypeAccessToken = "access_token" TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type // TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token" TokenTypeIdentityToken = "id_token"
// TokenTypeSessionToken is the session_token type used for browser session
TokenTypeSessionToken = "session_token"
) )

View File

@ -38,7 +38,6 @@ func (user *User) AsAPIUser() *model.User {
email := user.Email email := user.Email
createdAt := user.CreatedAt createdAt := user.CreatedAt
updatedAt := user.UpdatedAt updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
return &model.User{ return &model.User{
ID: user.ID, ID: user.ID,
Email: user.Email, Email: user.Email,
@ -55,7 +54,7 @@ func (user *User) AsAPIUser() *model.User {
PhoneNumberVerified: &isPhoneVerified, PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture, Picture: user.Picture,
Roles: strings.Split(user.Roles, ","), Roles: strings.Split(user.Roles, ","),
RevokedTimestamp: revokedTimestamp, RevokedTimestamp: user.RevokedTimestamp,
CreatedAt: &createdAt, CreatedAt: &createdAt,
UpdatedAt: &updatedAt, UpdatedAt: &updatedAt,
} }

View File

@ -97,7 +97,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
func (p *provider) GetUserByEmail(email string) (models.User, error) { func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("email = ?", email).First(&user) result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }
@ -110,7 +109,6 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("id = ?", id).First(&user) result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }

View File

@ -222,10 +222,7 @@ func AuthorizeHandler() gin.HandlerFunc {
// based on the response type, generate the response // based on the response type, generate the response
if isResponseTypeCode { if isResponseTypeCode {
// rollover the session for security // rollover the session for security
err = memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
if err != nil {
log.Debug("Failed to remove state: ", err)
}
nonce := uuid.New().String() nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil { if err != nil {
@ -246,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return return
} }
memorystore.Provider.SetUserSession(user.ID, newSessionToken, newSessionTokenData.Nonce) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
cookie.SetSession(gc, newSessionToken) cookie.SetSession(gc, newSessionToken)
code := uuid.New().String() code := uuid.New().String()
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
@ -283,9 +280,9 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
return return
} }
memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@ -308,7 +305,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token res["refresh_token"] = authToken.RefreshToken.Token
params += "&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 { if isQuery {

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"encoding/json"
"net/http" "net/http"
"strings" "strings"
@ -10,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Handler to logout user // Handler to logout user
@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc {
return return
} }
fingerPrint := string(decryptedFingerPrint) var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
err = memorystore.Provider.RemoveState(fingerPrint)
if err != nil { 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) cookie.DeleteSession(gc)
if redirectURL != "" { if redirectURL != "" {

View File

@ -36,7 +36,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
log.Debug("Invalid oauth state: ", state) log.Debug("Invalid oauth state: ", state)
c.JSON(400, gin.H{"error": "invalid oauth state"}) c.JSON(400, gin.H{"error": "invalid oauth state"})
} }
memorystore.Provider.GetState(state)
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "___")
@ -46,6 +45,9 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return return
} }
// remove state from store
go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0] stateValue := sessionSplit[0]
redirectURL := sessionSplit[1] redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",") inputRoles := strings.Split(sessionSplit[2], ",")
@ -117,9 +119,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user) user, _ = db.Provider.AddUser(user)
} else { } else {
user = existingUser
if user.RevokedTimestamp != nil { if user.RevokedTimestamp != nil {
log.Debug("User access revoked at: ", user.RevokedTimestamp) log.Debug("User access revoked at: ", user.RevokedTimestamp)
c.JSON(400, gin.H{"error": "user access has been revoked"}) c.JSON(400, gin.H{"error": "user access has been revoked"})
return
} }
// user exists in db, check if method was google // user exists in db, check if method was google
@ -128,7 +132,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider signupMethod = signupMethod + "," + provider
} }
user = existingUser
user.SignupMethods = signupMethod user.SignupMethods = signupMethod
if user.EmailVerifiedAt == nil { 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 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) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token 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{ go db.Provider.AddSession(models.Session{

View File

@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Revoke handler to revoke refresh token // Revoke handler to revoke refresh token
@ -45,7 +46,17 @@ func RevokeHandler() gin.HandlerFunc {
return 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{ gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully", "message": "Token revoked successfully",

View File

@ -107,6 +107,7 @@ func TokenHandler() gin.HandlerFunc {
return return
} }
go memorystore.Provider.RemoveState(encryptedCode)
// split session data // split session data
// it contains code@sessiontoken // it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@") sessionDataSplit := strings.Split(sessionData, "@")
@ -130,11 +131,11 @@ func TokenHandler() gin.HandlerFunc {
}) })
return return
} }
// rollover the session for security
memorystore.Provider.RemoveState(sessionDataSplit[1])
userID = claims.Subject userID = claims.Subject
roles = claims.Roles roles = claims.Roles
scope = claims.Scope scope = claims.Scope
// rollover the session for security
go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
} else { } else {
// validate refresh token // validate refresh token
if refreshToken == "" { if refreshToken == "" {
@ -163,7 +164,7 @@ func TokenHandler() gin.HandlerFunc {
scope = append(scope, v.(string)) scope = append(scope, v.(string))
} }
// remove older refresh token and rotate it for security // 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) user, err := db.Provider.GetUserByID(userID)
@ -185,8 +186,8 @@ func TokenHandler() gin.HandlerFunc {
}) })
return return
} }
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@ -204,7 +205,7 @@ func TokenHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token 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) gc.JSON(http.StatusOK, res)

View File

@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "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 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) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}` 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)
} }
if redirectURL == "" { if redirectURL == "" {

View File

@ -7,9 +7,9 @@ type Provider interface {
// GetAllUserSessions returns all the user sessions from the session store // GetAllUserSessions returns all the user sessions from the session store
GetAllUserSessions(userId string) (map[string]string, error) GetAllUserSessions(userId string) (map[string]string, error)
// GetUserSession returns the session token for given token // 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 deletes the user session
DeleteUserSession(userId, token string) error DeleteUserSession(userId, key string) error
// DeleteAllSessions deletes all the sessions from the session store // DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSessions(userId string) error DeleteAllUserSessions(userId string) error

View File

@ -37,18 +37,24 @@ func (c *provider) GetAllUserSessions(userID string) (map[string]string, error)
// GetUserSession returns the user session from redis store. // GetUserSession returns the user session from redis store.
func (c *provider) GetUserSession(userId, key string) (string, error) { func (c *provider) GetUserSession(userId, key string) (string, error) {
var res string data, err := c.store.HGet(c.ctx, userId, key).Result()
err := c.store.HGet(c.ctx, userId, key).Scan(&res)
if err != nil { if err != nil {
return "", err return "", err
} }
return res, nil return data, nil
} }
// DeleteUserSession deletes the user session from redis store. // DeleteUserSession deletes the user session from redis store.
func (c *provider) DeleteUserSession(userId, key string) error { func (c *provider) DeleteUserSession(userId, key string) error {
err := c.store.HDel(c.ctx, userId, key).Err() if err := c.store.HDel(c.ctx, userId, constants.TokenTypeSessionToken+"_"+key).Err(); err != nil {
if 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) log.Debug("Error deleting user session from redis: ", err)
return err return err
} }
@ -57,7 +63,7 @@ func (c *provider) DeleteUserSession(userId, key string) error {
// DeleteAllUserSessions deletes all the user session from redis // DeleteAllUserSessions deletes all the user session from redis
func (c *provider) DeleteAllUserSessions(userID string) error { 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 { if err != nil {
log.Debug("Error deleting all user sessions from redis: ", err) log.Debug("Error deleting all user sessions from redis: ", err)
return err return err
@ -78,13 +84,13 @@ func (c *provider) SetState(key, value string) error {
// GetState gets the state from redis store. // GetState gets the state from redis store.
func (c *provider) GetState(key string) (string, error) { func (c *provider) GetState(key string) (string, error) {
var res string data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result()
err := c.store.Get(c.ctx, stateStorePrefix+key).Scan(&res)
if err != nil { if err != nil {
log.Debug("error getting token from redis store: ", err) log.Debug("error getting token from redis store: ", err)
return "", err
} }
return res, err return data, err
} }
// RemoveState removes the state from redis store. // 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 // GetStringStoreEnvVariable to get the string env variable from env store
func (c *provider) GetStringStoreEnvVariable(key string) (string, error) { func (c *provider) GetStringStoreEnvVariable(key string) (string, error) {
var res string data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res)
if err != nil { if err != nil {
return "", nil return "", nil
} }
return res, nil return data, nil
} }
// GetBoolStoreEnvVariable to get the bool env variable from env store // GetBoolStoreEnvVariable to get the bool env variable from env store
func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) { func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) {
var res bool data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res)
if err != nil { if err != nil {
return false, nil return false, nil
} }
return res, nil return data == "1", nil
} }

View File

@ -117,12 +117,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
} }
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token 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{ go db.Provider.AddSession(models.Session{

View File

@ -41,7 +41,7 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
return nil, err return nil, err
} }
memorystore.Provider.DeleteUserSession(sessionData.Subject, fingerprintHash) memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc) cookie.DeleteSession(gc)
res := &model.Response{ res := &model.Response{

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "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) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("Failed to get session token", err) log.Debug("Failed to get session token: ", err)
return res, errors.New("unauthorized") return res, errors.New("unauthorized")
} }
@ -76,10 +77,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
} }
// rollover the session for security // rollover the session for security
memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
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() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
@ -94,10 +92,13 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
User: user.AsAPIUser(), 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 { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token 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 return res, nil
} }

View File

@ -225,15 +225,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err 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{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@ -251,6 +242,15 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: userToReturn, 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 return res, nil

View File

@ -8,6 +8,7 @@ import (
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/parsers"
@ -29,7 +30,7 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
} }
tokenType := params.TokenType 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) log.Debug("Invalid token type: ", tokenType)
return nil, errors.New("invalid token type") return nil, errors.New("invalid token type")
} }
@ -38,39 +39,34 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
var claims jwt.MapClaims var claims jwt.MapClaims
userID := "" userID := ""
nonce := "" nonce := ""
// 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) claims, err = token.ParseJWTToken(params.Token)
if err != nil { if err != nil {
log.Debug("Failed to parse JWT token: ", err) log.Debug("Failed to parse JWT token: ", err)
return nil, err return nil, err
} }
userID = claims["sub"].(string) userID = claims["sub"].(string)
nonce, err = memorystore.Provider.GetUserSession(userID, params.Token)
if err != nil || nonce == "" { // access_token and refresh_token should be validated from session store as well
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) log.Debug("Failed to get user session: ", err)
return nil, errors.New("invalid token") 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) hostname := parsers.GetHost(gc)
// we cannot validate sub and nonce in case of id_token as that token is not persisted in session store // we cannot validate nonce in case of id_token as that token is not persisted in session store
if userID != "" && nonce != "" { if nonce != "" {
if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil { if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil {
log.Debug("Failed to parse jwt token: ", err) log.Debug("Failed to parse jwt token: ", err)
return nil, errors.New("invalid claims") return nil, errors.New("invalid claims")
} }
} else { } 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) log.Debug("Failed to parse jwt token without nonce: ", err)
return nil, errors.New("invalid claims") return nil, errors.New("invalid claims")
} }

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -80,15 +81,6 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
return res, err 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{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@ -107,5 +99,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: user.AsAPIUser(), 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 return res, nil
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -28,17 +29,18 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
token := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
sessions, err := memorystore.Provider.GetAllUserSessions(verifyRes.User.ID) assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, sessions) assert.NotEmpty(t, claims)
cookie := ""
// set all they keys in cookie one of them should be session cookie sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, sessionToken)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
}
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)

View File

@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -33,17 +34,18 @@ func sessionTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
token := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
sessions, err := memorystore.Provider.GetAllUserSessions(verifyRes.User.ID) assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, sessions) assert.NotEmpty(t, claims)
cookie := ""
// set all they keys in cookie one of them should be session cookie sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, sessionToken)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
}
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
@ -50,9 +51,12 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
assert.NoError(t, err) assert.NoError(t, err)
authToken, err := token.CreateAuthToken(gc, user, roles, scope) authToken, err := token.CreateAuthToken(gc, user, roles, scope)
memorystore.Provider.SetUserSession(user.ID, authToken.FingerPrintHash, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, authToken.AccessToken.Token, authToken.FingerPrint) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
memorystore.Provider.SetUserSession(user.ID, authToken.RefreshToken.Token, authToken.FingerPrint)
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) { t.Run(`should validate the access token`, func(t *testing.T) {
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{

View File

@ -204,11 +204,16 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
} }
userID := res["sub"].(string) 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 { if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
if token != accessToken {
return res, fmt.Errorf(`unauthorized`)
}
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
return res, err return res, err
@ -235,11 +240,16 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte
} }
userID := res["sub"].(string) 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 { if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
if token != refreshToken {
return res, fmt.Errorf(`unauthorized`)
}
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil { if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
return res, err return res, err
@ -268,13 +278,13 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, err return nil, err
} }
nonce, err := memorystore.Provider.GetUserSession(res.Subject, encryptedSession) token, err := memorystore.Provider.GetUserSession(res.Subject, constants.TokenTypeSessionToken+"_"+res.Nonce)
if nonce == "" || err != nil { if token == "" || err != nil {
log.Debug("invalid browser session:", err) log.Debug("invalid browser session:", err)
return nil, fmt.Errorf(`unauthorized`) return nil, fmt.Errorf(`unauthorized`)
} }
if res.Nonce != nonce { if encryptedSession != token {
return nil, fmt.Errorf(`unauthorized: invalid nonce`) return nil, fmt.Errorf(`unauthorized: invalid nonce`)
} }

View File

@ -145,8 +145,8 @@ func ValidateJWTClaims(claims jwt.MapClaims, hostname, nonce, subject string) (b
return true, nil return true, nil
} }
// ValidateJWTClaimsWithoutNonce common util to validate claims without nonce // ValidateJWTTokenWithoutNonce common util to validate claims without nonce
func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname string) (bool, error) { func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname, subject string) (bool, error) {
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil { if err != nil {
return false, err return false, err
@ -159,5 +159,8 @@ func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname string) (bool,
return false, errors.New("invalid issuer") return false, errors.New("invalid issuer")
} }
if claims["sub"] != subject {
return false, errors.New("invalid subject")
}
return true, nil return true, nil
} }