From 1941cf42990071fb05a7bbde3a380b77dbd3feda Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 27 May 2022 23:20:38 +0530 Subject: [PATCH] fix: move sessionstore -> memstore --- server/db/db.go | 14 +- server/env/env.go | 76 --------- server/envstore/store.go | 11 -- server/handlers/authorize.go | 19 ++- server/handlers/logout.go | 7 +- server/handlers/oauth_callback.go | 12 +- server/handlers/oauth_login.go | 8 +- server/handlers/revoke.go | 4 +- server/handlers/token.go | 14 +- server/handlers/verify_email.go | 8 +- server/main.go | 37 +++-- server/memorystore/memory_store.go | 36 ++++ .../providers/inmemory/inmemory.go} | 38 ++--- .../providers/inmemory/provider.go | 18 ++ server/memorystore/providers/providers.go | 17 ++ .../memorystore/providers/redis/provider.go | 75 +++++++++ .../providers/redis/reids.go} | 36 ++-- server/memorystore/required_env_store.go | 136 +++++++++++++++ server/resolvers/delete_user.go | 4 +- server/resolvers/login.go | 8 +- server/resolvers/logout.go | 4 +- server/resolvers/revoke.go | 4 +- server/resolvers/revoke_access.go | 4 +- server/resolvers/session.go | 10 +- server/resolvers/signup.go | 4 +- server/resolvers/update_env.go | 13 +- server/resolvers/update_profile.go | 4 +- server/resolvers/update_user.go | 6 +- server/resolvers/validate_jwt_token.go | 4 +- server/resolvers/verify_email.go | 6 +- server/sessionstore/redis_client.go | 18 -- server/sessionstore/session.go | 156 ------------------ server/test/logout_test.go | 4 +- server/test/session_test.go | 4 +- server/test/test.go | 6 +- server/test/validate_jwt_token_test.go | 6 +- server/token/auth_token.go | 8 +- server/utils/cli.go | 12 ++ 38 files changed, 451 insertions(+), 400 deletions(-) create mode 100644 server/memorystore/memory_store.go rename server/{sessionstore/in_memory_session.go => memorystore/providers/inmemory/inmemory.go} (62%) create mode 100644 server/memorystore/providers/inmemory/provider.go create mode 100644 server/memorystore/providers/providers.go create mode 100644 server/memorystore/providers/redis/provider.go rename server/{sessionstore/redis_store.go => memorystore/providers/redis/reids.go} (72%) create mode 100644 server/memorystore/required_env_store.go delete mode 100644 server/sessionstore/redis_client.go delete mode 100644 server/sessionstore/session.go create mode 100644 server/utils/cli.go diff --git a/server/db/db.go b/server/db/db.go index a93cc01..d41469f 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -9,7 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/db/providers/cassandradb" "github.com/authorizerdev/authorizer/server/db/providers/mongodb" "github.com/authorizerdev/authorizer/server/db/providers/sql" - "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/memorystore" ) // Provider returns the current database provider @@ -18,13 +18,15 @@ var Provider providers.Provider func InitDB() error { var err error - isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeCassandraDB - isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb - isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb - isCassandra := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeCassandraDB + envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv() + + isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB + isArangoDB := envs.DatabaseType == constants.DbTypeArangodb + isMongoDB := envs.DatabaseType == constants.DbTypeMongodb + isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB if isSQL { - log.Info("Initializing SQL Driver for: ", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType)) + log.Info("Initializing SQL Driver for: ", envs.DatabaseType) Provider, err = sql.NewProvider() if err != nil { log.Fatal("Failed to initialize SQL driver: ", err) diff --git a/server/env/env.go b/server/env/env.go index abf9b53..8a29ec9 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/google/uuid" - "github.com/joho/godotenv" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -15,81 +14,6 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -// InitRequiredEnv to initialize EnvData and through error if required env are not present -func InitRequiredEnv() error { - envPath := os.Getenv(constants.EnvKeyEnvPath) - - if envPath == "" { - envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath) - if envPath == "" { - envPath = `.env` - } - } - - if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" { - envPath = *envstore.ARG_ENV_FILE - } - log.Info("env path: ", envPath) - - err := godotenv.Load(envPath) - if err != nil { - log.Info("using OS env instead of %s file", envPath) - } - - dbURL := os.Getenv(constants.EnvKeyDatabaseURL) - dbType := os.Getenv(constants.EnvKeyDatabaseType) - dbName := os.Getenv(constants.EnvKeyDatabaseName) - dbPort := os.Getenv(constants.EnvKeyDatabasePort) - dbHost := os.Getenv(constants.EnvKeyDatabaseHost) - dbUsername := os.Getenv(constants.EnvKeyDatabaseUsername) - dbPassword := os.Getenv(constants.EnvKeyDatabasePassword) - dbCert := os.Getenv(constants.EnvKeyDatabaseCert) - dbCertKey := os.Getenv(constants.EnvKeyDatabaseCertKey) - dbCACert := os.Getenv(constants.EnvKeyDatabaseCACert) - - if strings.TrimSpace(dbType) == "" { - if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" { - dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE) - } - - if dbType == "" { - log.Debug("DATABASE_TYPE is not set") - return errors.New("invalid database type. DATABASE_TYPE is empty") - } - } - - if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" { - if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" { - dbURL = strings.TrimSpace(*envstore.ARG_DB_URL) - } - - if dbURL == "" && dbPort == "" && dbHost == "" && dbUsername == "" && dbPassword == "" { - log.Debug("DATABASE_URL is not set") - return errors.New("invalid database url. DATABASE_URL is required") - } - } - - if dbName == "" { - if dbName == "" { - dbName = "authorizer" - } - } - - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseHost, dbHost) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabasePort, dbPort) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseUsername, dbUsername) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabasePassword, dbPassword) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCert, dbCert) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCertKey, dbCertKey) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCACert, dbCACert) - - return nil -} - // InitEnv to initialize EnvData and through error if required env are not present func InitAllEnv() error { envData, err := GetEnvData() diff --git a/server/envstore/store.go b/server/envstore/store.go index d2f5487..1140087 100644 --- a/server/envstore/store.go +++ b/server/envstore/store.go @@ -6,17 +6,6 @@ import ( "github.com/authorizerdev/authorizer/server/constants" ) -var ( - // ARG_DB_URL is the cli arg variable for the database url - ARG_DB_URL *string - // ARG_DB_TYPE is the cli arg variable for the database type - ARG_DB_TYPE *string - // ARG_ENV_FILE is the cli arg variable for the env file - ARG_ENV_FILE *string - // ARG_LOG_LEVEL is the cli arg variable for the log level - ARG_LOG_LEVEL *string -) - // Store data structure type Store struct { StringEnv map[string]string `json:"string_env"` diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index d8d6016..2ff450a 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -14,7 +14,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" ) @@ -223,7 +223,10 @@ func AuthorizeHandler() gin.HandlerFunc { // based on the response type, generate the response if isResponseTypeCode { // rollover the session for security - sessionstore.RemoveState(sessionToken) + err = memorystore.Provider.RemoveState(sessionToken) + if err != nil { + log.Debug("Failed to remove state: ", err) + } nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) if err != nil { @@ -244,10 +247,10 @@ func AuthorizeHandler() gin.HandlerFunc { return } - sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) + memorystore.Provider.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) cookie.SetSession(gc, newSessionToken) code := uuid.New().String() - sessionstore.SetState(codeChallenge, code+"@"+newSessionToken) + memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) gc.HTML(http.StatusOK, template, gin.H{ "target_origin": redirectURI, "authorization_response": map[string]interface{}{ @@ -281,9 +284,9 @@ func AuthorizeHandler() gin.HandlerFunc { } return } - sessionstore.RemoveState(sessionToken) - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.RemoveState(sessionToken) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -306,7 +309,7 @@ func AuthorizeHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } if isQuery { diff --git a/server/handlers/logout.go b/server/handlers/logout.go index 66bc498..e207b87 100644 --- a/server/handlers/logout.go +++ b/server/handlers/logout.go @@ -9,7 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/crypto" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" ) // Handler to logout user @@ -37,7 +37,10 @@ func LogoutHandler() gin.HandlerFunc { fingerPrint := string(decryptedFingerPrint) - sessionstore.RemoveState(fingerPrint) + err = memorystore.Provider.RemoveState(fingerPrint) + if err != nil { + log.Debug("Failed to remove state: ", err) + } cookie.DeleteSession(gc) if redirectURL != "" { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 07347c7..6898331 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -20,8 +20,8 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/oauth" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -32,12 +32,12 @@ func OAuthCallbackHandler() gin.HandlerFunc { provider := c.Param("oauth_provider") state := c.Request.FormValue("state") - sessionState := sessionstore.GetState(state) + sessionState := memorystore.Provider.GetState(state) if sessionState == "" { log.Debug("Invalid oauth state: ", state) c.JSON(400, gin.H{"error": "invalid oauth state"}) } - sessionstore.GetState(state) + memorystore.Provider.GetState(state) // contains random token, redirect url, role sessionSplit := strings.Split(state, "___") @@ -178,12 +178,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) - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) if authToken.RefreshToken != nil { params = params + `&refresh_token=` + authToken.RefreshToken.Token - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } go db.Provider.AddSession(models.Session{ diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index 3dc3351..2b5948e 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -9,8 +9,8 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/oauth" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/utils" ) @@ -78,7 +78,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetState(oauthStateString, constants.SignupMethodGoogle) + memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGoogle) // during the init of OAuthProvider authorizer url might be empty oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google" url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) @@ -89,7 +89,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetState(oauthStateString, constants.SignupMethodGithub) + memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGithub) oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github" url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) @@ -99,7 +99,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetState(oauthStateString, constants.SignupMethodFacebook) + memorystore.Provider.SetState(oauthStateString, constants.SignupMethodFacebook) oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook" url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) diff --git a/server/handlers/revoke.go b/server/handlers/revoke.go index f6d2bfc..a63457c 100644 --- a/server/handlers/revoke.go +++ b/server/handlers/revoke.go @@ -9,7 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" ) // Revoke handler to revoke refresh token @@ -46,7 +46,7 @@ func RevokeHandler() gin.HandlerFunc { return } - sessionstore.RemoveState(refreshToken) + memorystore.Provider.RemoveState(refreshToken) gc.JSON(http.StatusOK, gin.H{ "message": "Token revoked successfully", diff --git a/server/handlers/token.go b/server/handlers/token.go index 895a672..6fc2275 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -14,7 +14,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" ) @@ -98,7 +98,7 @@ func TokenHandler() gin.HandlerFunc { encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-") encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") - sessionData := sessionstore.GetState(encryptedCode) + sessionData := memorystore.Provider.GetState(encryptedCode) if sessionData == "" { log.Debug("Session data is empty") gc.JSON(http.StatusBadRequest, gin.H{ @@ -132,7 +132,7 @@ func TokenHandler() gin.HandlerFunc { return } // rollover the session for security - sessionstore.RemoveState(sessionDataSplit[1]) + memorystore.Provider.RemoveState(sessionDataSplit[1]) userID = claims.Subject roles = claims.Roles scope = claims.Scope @@ -164,7 +164,7 @@ func TokenHandler() gin.HandlerFunc { scope = append(scope, v.(string)) } // remove older refresh token and rotate it for security - sessionstore.RemoveState(refreshToken) + memorystore.Provider.RemoveState(refreshToken) } user, err := db.Provider.GetUserByID(userID) @@ -186,8 +186,8 @@ func TokenHandler() gin.HandlerFunc { }) return } - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -205,7 +205,7 @@ func TokenHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } gc.JSON(http.StatusOK, res) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 0d34b7d..d9abe50 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -12,7 +12,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -99,12 +99,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) - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) if authToken.RefreshToken != nil { params = params + `&refresh_token=${refresh_token}` - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } if redirectURL == "" { diff --git a/server/main.go b/server/main.go index 347dbae..a5c18da 100644 --- a/server/main.go +++ b/server/main.go @@ -10,9 +10,10 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/env" "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/routes" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/utils" ) var VERSION string @@ -27,23 +28,21 @@ func (u LogUTCFormatter) Format(e *log.Entry) ([]byte, error) { } func main() { - envstore.ARG_DB_URL = flag.String("database_url", "", "Database connection string") - envstore.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite") - envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") - envstore.ARG_LOG_LEVEL = flag.String("log_level", "info", "Log level, possible values are debug,info,warn,error,fatal,panic") + utils.ARG_DB_URL = flag.String("database_url", "", "Database connection string") + utils.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite") + utils.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") + utils.ARG_LOG_LEVEL = flag.String("log_level", "info", "Log level, possible values are debug,info,warn,error,fatal,panic") flag.Parse() // global log level logrus.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}}) - logrus.SetReportCaller(true) // log instance for gin server log := logrus.New() log.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}}) - log.SetReportCaller(true) var logLevel logrus.Level - switch *envstore.ARG_LOG_LEVEL { + switch *utils.ARG_LOG_LEVEL { case "debug": logLevel = logrus.DebugLevel case "info": @@ -62,14 +61,26 @@ func main() { logrus.SetLevel(logLevel) log.SetLevel(logLevel) + // show file path in log for debug or other log levels. + if logLevel != logrus.InfoLevel { + logrus.SetReportCaller(true) + log.SetReportCaller(true) + } + constants.VERSION = VERSION - // initialize required envs (mainly db & env file path) - err := env.InitRequiredEnv() + // initialize required envs (mainly db, env file path and redis) + err := memorystore.InitRequiredEnv() if err != nil { log.Fatal("Error while initializing required envs: ", err) } + // initialize memory store + err = memorystore.InitMemStore() + if err != nil { + log.Fatal("Error while initializing memory store: ", err) + } + // initialize db provider err = db.InitDB() if err != nil { @@ -89,12 +100,6 @@ func main() { log.Fatalln("Error while persisting env: ", err) } - // initialize session store (redis or in-memory based on env) - err = sessionstore.InitSession() - if err != nil { - log.Fatalln("Error while initializing session store: ", err) - } - // initialize oauth providers based on env err = oauth.InitOAuth() if err != nil { diff --git a/server/memorystore/memory_store.go b/server/memorystore/memory_store.go new file mode 100644 index 0000000..ad66b8b --- /dev/null +++ b/server/memorystore/memory_store.go @@ -0,0 +1,36 @@ +package memorystore + +import ( + log "github.com/sirupsen/logrus" + + "github.com/authorizerdev/authorizer/server/memorystore/providers" + "github.com/authorizerdev/authorizer/server/memorystore/providers/inmemory" + "github.com/authorizerdev/authorizer/server/memorystore/providers/redis" +) + +// Provider returns the current database provider +var Provider providers.Provider + +// InitMemStore initializes the memory store +func InitMemStore() error { + var err error + + redisURL := RequiredEnvStoreObj.GetRequiredEnv().RedisURL + if redisURL != "" { + log.Info("Initializing Redis memory store") + Provider, err = redis.NewRedisProvider(redisURL) + if err != nil { + return err + } + + return nil + } + + log.Info("using in memory store to save sessions") + // if redis url is not set use in memory store + Provider, err = inmemory.NewInMemoryProvider() + if err != nil { + return err + } + return nil +} diff --git a/server/sessionstore/in_memory_session.go b/server/memorystore/providers/inmemory/inmemory.go similarity index 62% rename from server/sessionstore/in_memory_session.go rename to server/memorystore/providers/inmemory/inmemory.go index edc6924..72c7d59 100644 --- a/server/sessionstore/in_memory_session.go +++ b/server/memorystore/providers/inmemory/inmemory.go @@ -1,26 +1,18 @@ -package sessionstore +package inmemory -import ( - "strings" - "sync" -) - -// InMemoryStore is a simple in-memory store for sessions. -type InMemoryStore struct { - mutex sync.Mutex - sessionStore map[string]map[string]string - stateStore map[string]string -} +import "strings" // ClearStore clears the in-memory store. -func (c *InMemoryStore) ClearStore() { +func (c *provider) ClearStore() error { c.mutex.Lock() defer c.mutex.Unlock() c.sessionStore = map[string]map[string]string{} + + return nil } // GetUserSessions returns all the user session token from the in-memory store. -func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { +func (c *provider) GetUserSessions(userId string) map[string]string { // c.mutex.Lock() // defer c.mutex.Unlock() res := map[string]string{} @@ -35,25 +27,29 @@ func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { } // DeleteAllUserSession deletes all the user sessions from in-memory store. -func (c *InMemoryStore) DeleteAllUserSession(userId string) { +func (c *provider) DeleteAllUserSession(userId string) error { // c.mutex.Lock() // defer c.mutex.Unlock() - sessions := GetUserSessions(userId) + sessions := c.GetUserSessions(userId) for k := range sessions { - RemoveState(k) + c.RemoveState(k) } + + return nil } // SetState sets the state in the in-memory store. -func (c *InMemoryStore) SetState(key, state string) { +func (c *provider) SetState(key, state string) error { c.mutex.Lock() defer c.mutex.Unlock() c.stateStore[key] = state + + return nil } // GetState gets the state from the in-memory store. -func (c *InMemoryStore) GetState(key string) string { +func (c *provider) GetState(key string) string { c.mutex.Lock() defer c.mutex.Unlock() @@ -66,9 +62,11 @@ func (c *InMemoryStore) GetState(key string) string { } // RemoveState removes the state from the in-memory store. -func (c *InMemoryStore) RemoveState(key string) { +func (c *provider) RemoveState(key string) error { c.mutex.Lock() defer c.mutex.Unlock() delete(c.stateStore, key) + + return nil } diff --git a/server/memorystore/providers/inmemory/provider.go b/server/memorystore/providers/inmemory/provider.go new file mode 100644 index 0000000..767e2fa --- /dev/null +++ b/server/memorystore/providers/inmemory/provider.go @@ -0,0 +1,18 @@ +package inmemory + +import "sync" + +type provider struct { + mutex sync.Mutex + sessionStore map[string]map[string]string + stateStore map[string]string +} + +// 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{}, + }, nil +} diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go new file mode 100644 index 0000000..3270bb4 --- /dev/null +++ b/server/memorystore/providers/providers.go @@ -0,0 +1,17 @@ +package providers + +// Provider defines current memory store provider +type Provider interface { + // 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 + // 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 + GetState(key string) string + // RemoveState removes the social login state from the session store + RemoveState(key string) error +} diff --git a/server/memorystore/providers/redis/provider.go b/server/memorystore/providers/redis/provider.go new file mode 100644 index 0000000..ce9840d --- /dev/null +++ b/server/memorystore/providers/redis/provider.go @@ -0,0 +1,75 @@ +package redis + +import ( + "context" + "strings" + "time" + + "github.com/go-redis/redis/v8" + log "github.com/sirupsen/logrus" +) + +// RedisClient is the interface for redis client & redis cluster client +type RedisClient interface { + HMSet(ctx context.Context, key string, values ...interface{}) *redis.BoolCmd + Del(ctx context.Context, keys ...string) *redis.IntCmd + HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd + HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd + HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd + Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd + Get(ctx context.Context, key string) *redis.StringCmd +} + +type provider struct { + ctx context.Context + store RedisClient +} + +// NewRedisProvider returns a new redis provider +func NewRedisProvider(redisURL string) (*provider, error) { + redisURLHostPortsList := strings.Split(redisURL, ",") + + if len(redisURLHostPortsList) > 1 { + opt, err := redis.ParseURL(redisURLHostPortsList[0]) + if err != nil { + log.Debug("error parsing redis url: ", err) + return nil, err + } + urls := []string{opt.Addr} + urlList := redisURLHostPortsList[1:] + urls = append(urls, urlList...) + clusterOpt := &redis.ClusterOptions{Addrs: urls} + + rdb := redis.NewClusterClient(clusterOpt) + ctx := context.Background() + _, err = rdb.Ping(ctx).Result() + if err != nil { + log.Debug("error connecting to redis: ", err) + return nil, err + } + + return &provider{ + ctx: ctx, + store: rdb, + }, nil + } + + opt, err := redis.ParseURL(redisURL) + if err != nil { + log.Debug("error parsing redis url: ", err) + return nil, err + } + + rdb := redis.NewClient(opt) + ctx := context.Background() + _, err = rdb.Ping(ctx).Result() + if err != nil { + log.Debug("error connecting to redis: ", err) + return nil, err + } + + return &provider{ + ctx: ctx, + store: rdb, + }, nil +} diff --git a/server/sessionstore/redis_store.go b/server/memorystore/providers/redis/reids.go similarity index 72% rename from server/sessionstore/redis_store.go rename to server/memorystore/providers/redis/reids.go index 6ade0fa..427ae9d 100644 --- a/server/sessionstore/redis_store.go +++ b/server/memorystore/providers/redis/reids.go @@ -1,27 +1,24 @@ -package sessionstore +package redis import ( - "context" "strings" log "github.com/sirupsen/logrus" ) -type RedisStore struct { - ctx context.Context - store RedisSessionClient -} - // ClearStore clears the redis store for authorizer related tokens -func (c *RedisStore) ClearStore() { +func (c *provider) ClearStore() error { err := c.store.Del(c.ctx, "authorizer_*").Err() if err != nil { log.Debug("Error clearing redis store: ", err) + return err } + + return nil } // GetUserSessions returns all the user session token from the redis store. -func (c *RedisStore) GetUserSessions(userID string) map[string]string { +func (c *provider) GetUserSessions(userID string) map[string]string { data, err := c.store.HGetAll(c.ctx, "*").Result() if err != nil { log.Debug("error getting token from redis store: ", err) @@ -39,28 +36,34 @@ func (c *RedisStore) GetUserSessions(userID string) map[string]string { } // DeleteAllUserSession deletes all the user session from redis -func (c *RedisStore) DeleteAllUserSession(userId string) { - sessions := GetUserSessions(userId) +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 := c.store.Del(c.ctx, v).Err() if err != nil { log.Debug("Error deleting redis token: ", err) + return err } } } + + return nil } // SetState sets the state in redis store. -func (c *RedisStore) SetState(key, value string) { +func (c *provider) SetState(key, value string) error { err := c.store.Set(c.ctx, "authorizer_"+key, value, 0).Err() if err != nil { log.Debug("Error saving redis token: ", err) + return err } + + return nil } // GetState gets the state from redis store. -func (c *RedisStore) GetState(key string) string { +func (c *provider) GetState(key string) string { state := "" state, err := c.store.Get(c.ctx, "authorizer_"+key).Result() if err != nil { @@ -71,9 +74,12 @@ func (c *RedisStore) GetState(key string) string { } // RemoveState removes the state from redis store. -func (c *RedisStore) RemoveState(key string) { +func (c *provider) RemoveState(key string) error { err := c.store.Del(c.ctx, "authorizer_"+key).Err() if err != nil { log.Fatalln("Error deleting redis token: ", err) + return err } + + return nil } diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go new file mode 100644 index 0000000..d2780f7 --- /dev/null +++ b/server/memorystore/required_env_store.go @@ -0,0 +1,136 @@ +package memorystore + +import ( + "errors" + "os" + "strings" + "sync" + + "github.com/joho/godotenv" + log "github.com/sirupsen/logrus" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/utils" +) + +// RequiredEnv holds information about required envs +type RequiredEnv struct { + EnvPath string + DatabaseURL string + DatabaseType string + DatabaseName string + DatabaseHost string + DatabasePort string + DatabaseUsername string + DatabasePassword string + DatabaseCert string + DatabaseCertKey string + DatabaseCACert string + RedisURL string +} + +// RequiredEnvObj is a simple in-memory store for sessions. +type RequiredEnvStore struct { + mutex sync.Mutex + requiredEnv RequiredEnv +} + +// GetRequiredEnv to get required env +func (r *RequiredEnvStore) GetRequiredEnv() RequiredEnv { + r.mutex.Lock() + defer r.mutex.Unlock() + + return r.requiredEnv +} + +// SetRequiredEnv to set required env +func (r *RequiredEnvStore) SetRequiredEnv(requiredEnv RequiredEnv) { + r.mutex.Lock() + defer r.mutex.Unlock() + r.requiredEnv = requiredEnv +} + +var RequiredEnvStoreObj *RequiredEnvStore + +// InitRequiredEnv to initialize EnvData and through error if required env are not present +func InitRequiredEnv() error { + envPath := os.Getenv(constants.EnvKeyEnvPath) + + if envPath == "" { + if envPath == "" { + envPath = `.env` + } + } + + if utils.ARG_ENV_FILE != nil && *utils.ARG_ENV_FILE != "" { + envPath = *utils.ARG_ENV_FILE + } + log.Info("env path: ", envPath) + + err := godotenv.Load(envPath) + if err != nil { + log.Info("using OS env instead of %s file", envPath) + } + + dbURL := os.Getenv(constants.EnvKeyDatabaseURL) + dbType := os.Getenv(constants.EnvKeyDatabaseType) + dbName := os.Getenv(constants.EnvKeyDatabaseName) + dbPort := os.Getenv(constants.EnvKeyDatabasePort) + dbHost := os.Getenv(constants.EnvKeyDatabaseHost) + dbUsername := os.Getenv(constants.EnvKeyDatabaseUsername) + dbPassword := os.Getenv(constants.EnvKeyDatabasePassword) + dbCert := os.Getenv(constants.EnvKeyDatabaseCert) + dbCertKey := os.Getenv(constants.EnvKeyDatabaseCertKey) + dbCACert := os.Getenv(constants.EnvKeyDatabaseCACert) + redisURL := os.Getenv(constants.EnvKeyRedisURL) + + if strings.TrimSpace(dbType) == "" { + if utils.ARG_DB_TYPE != nil && *utils.ARG_DB_TYPE != "" { + dbType = strings.TrimSpace(*utils.ARG_DB_TYPE) + } + + if dbType == "" { + log.Debug("DATABASE_TYPE is not set") + return errors.New("invalid database type. DATABASE_TYPE is empty") + } + } + + if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" { + if utils.ARG_DB_URL != nil && *utils.ARG_DB_URL != "" { + dbURL = strings.TrimSpace(*utils.ARG_DB_URL) + } + + if dbURL == "" && dbPort == "" && dbHost == "" && dbUsername == "" && dbPassword == "" { + log.Debug("DATABASE_URL is not set") + return errors.New("invalid database url. DATABASE_URL is required") + } + } + + if dbName == "" { + if dbName == "" { + dbName = "authorizer" + } + } + + requiredEnv := RequiredEnv{ + EnvPath: envPath, + DatabaseURL: dbURL, + DatabaseType: dbType, + DatabaseName: dbName, + DatabaseHost: dbHost, + DatabasePort: dbPort, + DatabaseUsername: dbUsername, + DatabasePassword: dbPassword, + DatabaseCert: dbCert, + DatabaseCertKey: dbCertKey, + DatabaseCACert: dbCACert, + RedisURL: redisURL, + } + + RequiredEnvStoreObj = &RequiredEnvStore{ + requiredEnv: requiredEnv, + } + + return nil +} diff --git a/server/resolvers/delete_user.go b/server/resolvers/delete_user.go index 4fadbfe..df64443 100644 --- a/server/resolvers/delete_user.go +++ b/server/resolvers/delete_user.go @@ -8,7 +8,7 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -38,7 +38,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod return res, err } - go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) + go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) err = db.Provider.DeleteUser(user) if err != nil { diff --git a/server/resolvers/login.go b/server/resolvers/login.go index eda8c9d..a0672ef 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -15,7 +15,7 @@ import ( "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -102,12 +102,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } cookie.SetSession(gc, authToken.FingerPrintHash) - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } go db.Provider.AddSession(models.Session{ diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 9683237..2c81f63 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -8,7 +8,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/utils" ) @@ -37,7 +37,7 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) { fingerPrint := string(decryptedFingerPrint) - sessionstore.RemoveState(fingerPrint) + memorystore.Provider.RemoveState(fingerPrint) cookie.DeleteSession(gc) res = &model.Response{ diff --git a/server/resolvers/revoke.go b/server/resolvers/revoke.go index 1ab1cb9..694e36b 100644 --- a/server/resolvers/revoke.go +++ b/server/resolvers/revoke.go @@ -4,12 +4,12 @@ import ( "context" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" ) // RevokeResolver resolver to revoke refresh token func RevokeResolver(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) { - sessionstore.RemoveState(params.RefreshToken) + memorystore.Provider.RemoveState(params.RefreshToken) return &model.Response{ Message: "Token revoked", }, nil diff --git a/server/resolvers/revoke_access.go b/server/resolvers/revoke_access.go index a7b6ab0..9b24c71 100644 --- a/server/resolvers/revoke_access.go +++ b/server/resolvers/revoke_access.go @@ -9,7 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -47,7 +47,7 @@ func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) ( return res, err } - go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) + go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) res = &model.Response{ Message: `user access revoked successfully`, diff --git a/server/resolvers/session.go b/server/resolvers/session.go index 0698b64..89b7e11 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -11,7 +11,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -76,9 +76,9 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod } // rollover the session for security - sessionstore.RemoveState(sessionToken) - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.RemoveState(sessionToken) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) 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 - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } return res, nil diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index b8cffce..3848ebe 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -16,7 +16,7 @@ import ( "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -194,7 +194,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) go db.Provider.AddSession(models.Session{ UserID: user.ID, diff --git a/server/resolvers/update_env.go b/server/resolvers/update_env.go index be298f9..e7d958d 100644 --- a/server/resolvers/update_env.go +++ b/server/resolvers/update_env.go @@ -16,7 +16,6 @@ import ( "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/oauth" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -214,11 +213,13 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model } // updating jwk envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) - err = sessionstore.InitSession() - if err != nil { - log.Debug("Failed to init session store: ", err) - return res, err - } + + // TODO check how to update session store based on env change. + // err = sessionstore.InitSession() + // if err != nil { + // log.Debug("Failed to init session store: ", err) + // return res, err + // } err = oauth.InitOAuth() if err != nil { return res, err diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index a7ddc49..27e7382 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -16,7 +16,7 @@ import ( "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "golang.org/x/crypto/bcrypt" @@ -141,7 +141,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) return res, fmt.Errorf("user with this email address already exists") } - go sessionstore.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSession(user.ID) go cookie.DeleteSession(gc) user.Email = newEmail diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index 3628ba4..99dee9c 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -14,7 +14,7 @@ import ( "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -112,7 +112,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod } // TODO figure out how to do this - go sessionstore.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSession(user.ID) hostname := utils.GetHost(gc) user.Email = newEmail @@ -164,7 +164,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod rolesToSave = strings.Join(inputRoles, ",") } - go sessionstore.DeleteAllUserSession(user.ID) + go memorystore.Provider.DeleteAllUserSession(user.ID) } if rolesToSave != "" { diff --git a/server/resolvers/validate_jwt_token.go b/server/resolvers/validate_jwt_token.go index 4733eb2..0fba3b9 100644 --- a/server/resolvers/validate_jwt_token.go +++ b/server/resolvers/validate_jwt_token.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -38,7 +38,7 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken nonce := "" // access_token and refresh_token should be validated from session store as well if tokenType == "access_token" || tokenType == "refresh_token" { - savedSession := sessionstore.GetState(params.Token) + savedSession := memorystore.Provider.GetState(params.Token) if savedSession == "" { return &model.ValidateJWTTokenResponse{ IsValid: false, diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index c19fdcc..e44b7dd 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -12,7 +12,7 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -74,8 +74,8 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m return res, err } - sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) go db.Provider.AddSession(models.Session{ UserID: user.ID, diff --git a/server/sessionstore/redis_client.go b/server/sessionstore/redis_client.go deleted file mode 100644 index e73cd74..0000000 --- a/server/sessionstore/redis_client.go +++ /dev/null @@ -1,18 +0,0 @@ -package sessionstore - -import ( - "context" - "time" - - "github.com/go-redis/redis/v8" -) - -type RedisSessionClient interface { - HMSet(ctx context.Context, key string, values ...interface{}) *redis.BoolCmd - Del(ctx context.Context, keys ...string) *redis.IntCmd - HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd - HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd - HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd - Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd - Get(ctx context.Context, key string) *redis.StringCmd -} diff --git a/server/sessionstore/session.go b/server/sessionstore/session.go deleted file mode 100644 index 7626e8f..0000000 --- a/server/sessionstore/session.go +++ /dev/null @@ -1,156 +0,0 @@ -package sessionstore - -import ( - "context" - "strings" - - log "github.com/sirupsen/logrus" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/envstore" - "github.com/go-redis/redis/v8" -) - -// SessionStore is a struct that defines available session stores -// If redis store is available, higher preference is given to that store. -// Else in memory store is used. -type SessionStore struct { - InMemoryStoreObj *InMemoryStore - RedisMemoryStoreObj *RedisStore -} - -// SessionStoreObj is a global variable that holds the -// reference to various session store instances -var SessionStoreObj SessionStore - -// DeleteAllSessions deletes all the sessions from the session store -func DeleteAllUserSession(userId string) { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.DeleteAllUserSession(userId) - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.DeleteAllUserSession(userId) - } -} - -// GetUserSessions returns all the user sessions from the session store -func GetUserSessions(userId string) map[string]string { - if SessionStoreObj.RedisMemoryStoreObj != nil { - return SessionStoreObj.RedisMemoryStoreObj.GetUserSessions(userId) - } - if SessionStoreObj.InMemoryStoreObj != nil { - return SessionStoreObj.InMemoryStoreObj.GetUserSessions(userId) - } - - return nil -} - -// ClearStore clears the session store for authorizer tokens -func ClearStore() { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.ClearStore() - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.ClearStore() - } -} - -// SetState sets the login state (key, value form) in the session store -func SetState(key, state string) { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.SetState(key, state) - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.SetState(key, state) - } -} - -// GetState returns the state from the session store -func GetState(key string) string { - if SessionStoreObj.RedisMemoryStoreObj != nil { - return SessionStoreObj.RedisMemoryStoreObj.GetState(key) - } - if SessionStoreObj.InMemoryStoreObj != nil { - return SessionStoreObj.InMemoryStoreObj.GetState(key) - } - - return "" -} - -// RemoveState removes the social login state from the session store -func RemoveState(key string) { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.RemoveState(key) - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.RemoveState(key) - } -} - -// InitializeSessionStore initializes the SessionStoreObj based on environment variables -func InitSession() error { - if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" { - log.Info("using redis store to save sessions") - - redisURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) - redisURLHostPortsList := strings.Split(redisURL, ",") - - if len(redisURLHostPortsList) > 1 { - opt, err := redis.ParseURL(redisURLHostPortsList[0]) - if err != nil { - log.Debug("error parsing redis url: ", err) - return err - } - urls := []string{opt.Addr} - urlList := redisURLHostPortsList[1:] - urls = append(urls, urlList...) - clusterOpt := &redis.ClusterOptions{Addrs: urls} - - rdb := redis.NewClusterClient(clusterOpt) - ctx := context.Background() - _, err = rdb.Ping(ctx).Result() - if err != nil { - log.Debug("error connecting to redis: ", err) - return err - } - SessionStoreObj.RedisMemoryStoreObj = &RedisStore{ - ctx: ctx, - store: rdb, - } - - // return on successful initialization - return nil - } - - opt, err := redis.ParseURL(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) - if err != nil { - log.Debug("error parsing redis url: ", err) - return err - } - - rdb := redis.NewClient(opt) - ctx := context.Background() - _, err = rdb.Ping(ctx).Result() - if err != nil { - log.Debug("error connecting to redis: ", err) - return err - } - - SessionStoreObj.RedisMemoryStoreObj = &RedisStore{ - ctx: ctx, - store: rdb, - } - - // return on successful initialization - return nil - } - - log.Info("using in memory store to save sessions") - // if redis url is not set use in memory store - SessionStoreObj.InMemoryStoreObj = &InMemoryStore{ - sessionStore: map[string]map[string]string{}, - stateStore: map[string]string{}, - } - - return nil -} diff --git a/server/test/logout_test.go b/server/test/logout_test.go index 8956b31..67b2402 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -8,8 +8,8 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/stretchr/testify/assert" ) @@ -29,7 +29,7 @@ func logoutTests(t *testing.T, s TestSetup) { }) token := *verifyRes.AccessToken - sessions := sessionstore.GetUserSessions(verifyRes.User.ID) + 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 { diff --git a/server/test/session_test.go b/server/test/session_test.go index 65ce57e..bf6d32c 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -9,8 +9,8 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/stretchr/testify/assert" ) @@ -34,7 +34,7 @@ func sessionTests(t *testing.T, s TestSetup) { Token: verificationRequest.Token, }) - sessions := sessionstore.GetUserSessions(verifyRes.User.ID) + sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) cookie := "" token := *verifyRes.AccessToken // set all they keys in cookie one of them should be session cookie diff --git a/server/test/test.go b/server/test/test.go index c4cb14f..2c3b1c8 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -12,8 +12,8 @@ import ( "github.com/authorizerdev/authorizer/server/env" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/handlers" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/middlewares" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/gin-gonic/gin" ) @@ -77,16 +77,16 @@ func testSetup() TestSetup { } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") - env.InitRequiredEnv() + memorystore.InitMemStore() envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"}) + memorystore.InitMemStore() db.InitDB() env.InitAllEnv() - sessionstore.InitSession() w := httptest.NewRecorder() c, r := gin.CreateTestContext(w) diff --git a/server/test/validate_jwt_token_test.go b/server/test/validate_jwt_token_test.go index 5bb4268..4207ebc 100644 --- a/server/test/validate_jwt_token_test.go +++ b/server/test/validate_jwt_token_test.go @@ -6,8 +6,8 @@ import ( "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/google/uuid" @@ -48,8 +48,8 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { gc, err := utils.GinContextFromContext(ctx) assert.NoError(t, err) authToken, err := token.CreateAuthToken(gc, user, roles, scope) - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) - sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) 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 6f8930f..1ef6700 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -17,7 +17,7 @@ import ( "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/utils" ) @@ -186,7 +186,7 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf return res, fmt.Errorf(`unauthorized`) } - savedSession := sessionstore.GetState(accessToken) + savedSession := memorystore.Provider.GetState(accessToken) if savedSession == "" { return res, fmt.Errorf(`unauthorized`) } @@ -216,7 +216,7 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte return res, fmt.Errorf(`unauthorized`) } - savedSession := sessionstore.GetState(refreshToken) + savedSession := memorystore.Provider.GetState(refreshToken) if savedSession == "" { return res, fmt.Errorf(`unauthorized`) } @@ -243,7 +243,7 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD return nil, fmt.Errorf(`unauthorized`) } - savedSession := sessionstore.GetState(encryptedSession) + savedSession := memorystore.Provider.GetState(encryptedSession) if savedSession == "" { return nil, fmt.Errorf(`unauthorized`) } diff --git a/server/utils/cli.go b/server/utils/cli.go new file mode 100644 index 0000000..650df39 --- /dev/null +++ b/server/utils/cli.go @@ -0,0 +1,12 @@ +package utils + +var ( + // ARG_DB_URL is the cli arg variable for the database url + ARG_DB_URL *string + // ARG_DB_TYPE is the cli arg variable for the database type + ARG_DB_TYPE *string + // ARG_ENV_FILE is the cli arg variable for the env file + ARG_ENV_FILE *string + // ARG_LOG_LEVEL is the cli arg variable for the log level + ARG_LOG_LEVEL *string +)