diff --git a/.dockerignore b/.dockerignore index c51d2c2..5773072 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,4 @@ build data.db app/node_modules app/build +certs/ diff --git a/.gitignore b/.gitignore index 927a4df..8b70df4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ data.db *.tar.gz .vscode/ .yalc -yalc.lock \ No newline at end of file +yalc.lock +certs/ \ No newline at end of file diff --git a/server/constants/env.go b/server/constants/env.go index 4f391a9..b73048b 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -38,6 +38,12 @@ const ( EnvKeyDatabasePort = "DATABASE_PORT" // EnvKeyDatabaseHost key for env variable DATABASE_HOST EnvKeyDatabaseHost = "DATABASE_HOST" + // EnvKeyDatabaseCert key for env variable DATABASE_CERT + EnvKeyDatabaseCert = "DATABASE_CERT" + // EnvKeyDatabaseCertKey key for env variable DATABASE_KEY + EnvKeyDatabaseCertKey = "DATABASE_CERT_KEY" + // EnvKeyDatabaseCACert key for env variable DATABASE_CA_CERT + EnvKeyDatabaseCACert = "DATABASE_CA_CERT" // EnvKeySmtpHost key for env variable SMTP_HOST EnvKeySmtpHost = "SMTP_HOST" // EnvKeySmtpPort key for env variable SMTP_PORT diff --git a/server/db/providers/cassandradb/provider.go b/server/db/providers/cassandradb/provider.go index 4c41870..6e0dd64 100644 --- a/server/db/providers/cassandradb/provider.go +++ b/server/db/providers/cassandradb/provider.go @@ -1,13 +1,17 @@ package cassandradb import ( + "crypto/tls" + "crypto/x509" "fmt" "log" "strings" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" + "github.com/gocql/gocql" cansandraDriver "github.com/gocql/gocql" ) @@ -21,6 +25,13 @@ var KeySpace string // NewProvider to initialize arangodb connection func NewProvider() (*provider, error) { dbURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) + if dbURL == "" { + dbURL = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseHost) + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePort) != "" { + dbURL = fmt.Sprintf("%s:%s", dbURL, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePort)) + } + } + KeySpace = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) clusterURL := []string{} if strings.Contains(dbURL, ",") { @@ -36,10 +47,44 @@ func NewProvider() (*provider, error) { } } + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCert) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCACert) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCertKey) != "" { + certString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCert)) + if err != nil { + return nil, err + } + + keyString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCertKey)) + if err != nil { + return nil, err + } + + caString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCACert)) + if err != nil { + return nil, err + } + + cert, err := tls.X509KeyPair([]byte(certString), []byte(keyString)) + if err != nil { + return nil, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(caString)) + + cassandraClient.SslOpts = &cansandraDriver.SslOptions{ + Config: &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + InsecureSkipVerify: true, + }, + EnableHostVerification: false, + } + } + cassandraClient.RetryPolicy = &cansandraDriver.SimpleRetryPolicy{ NumRetries: 3, } - cassandraClient.Consistency = cansandraDriver.Quorum + cassandraClient.Consistency = gocql.LocalQuorum session, err := cassandraClient.CreateSession() if err != nil { @@ -47,12 +92,31 @@ func NewProvider() (*provider, error) { return nil, err } - keyspaceQuery := fmt.Sprintf("CREATE KEYSPACE IF NOT EXISTS %s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor':1}", - KeySpace) - err = session.Query(keyspaceQuery).Exec() - if err != nil { - log.Println("Unable to create keyspace:", err) - return nil, err + // Note for astra keyspaces can only be created from there console + // https://docs.datastax.com/en/astra/docs/datastax-astra-faq.html#_i_am_trying_to_create_a_keyspace_in_the_cql_shell_and_i_am_running_into_an_error_how_do_i_fix_this + getKeyspaceQuery := fmt.Sprintf("SELECT keyspace_name FROM system_schema.keyspaces;") + scanner := session.Query(getKeyspaceQuery).Iter().Scanner() + hasAuthorizerKeySpace := false + for scanner.Next() { + var keySpace string + err := scanner.Scan(&keySpace) + if err != nil { + log.Println("Error while getting keyspace information", err) + return nil, err + } + if keySpace == KeySpace { + hasAuthorizerKeySpace = true + break + } + } + + if !hasAuthorizerKeySpace { + createKeySpaceQuery := fmt.Sprintf("CREATE KEYSPACE %s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1};", KeySpace) + err = session.Query(createKeySpaceQuery).Exec() + if err != nil { + log.Println("Error while creating keyspace", err) + return nil, err + } } // make sure collections are present diff --git a/server/env/env.go b/server/env/env.go index aeb5c27..2972cf8 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -42,6 +42,9 @@ func InitRequiredEnv() error { 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 != "" { @@ -77,6 +80,10 @@ func InitRequiredEnv() error { 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 } diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 936e618..3fdf7f6 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -174,7 +174,11 @@ func OAuthCallbackHandler() gin.HandlerFunc { sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } - go utils.SaveSessionInDB(c, user.ID) + go db.Provider.AddSession(models.Session{ + UserID: user.ID, + UserAgent: utils.GetUserAgent(c.Request), + IP: utils.GetIP(c.Request), + }) if strings.Contains(redirectURL, "?") { redirectURL = redirectURL + "&" + params } else { diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 6333620..319537d 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -8,6 +8,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/token" "github.com/authorizerdev/authorizer/server/utils" @@ -109,7 +110,11 @@ func VerifyEmailHandler() gin.HandlerFunc { redirectURL = redirectURL + "?" + params } - go utils.SaveSessionInDB(c, user.ID) + go db.Provider.AddSession(models.Session{ + UserID: user.ID, + UserAgent: utils.GetUserAgent(c.Request), + IP: utils.GetIP(c.Request), + }) c.Redirect(http.StatusTemporaryRedirect, redirectURL) } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index a93ca8d..15acddd 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -10,6 +10,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/sessionstore" @@ -96,7 +97,11 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } - go utils.SaveSessionInDB(gc, user.ID) + go db.Provider.AddSession(models.Session{ + UserID: user.ID, + UserAgent: utils.GetUserAgent(gc.Request), + IP: utils.GetIP(gc.Request), + }) return res, nil } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 317416e..3ed0e5d 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -174,7 +174,11 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) - go utils.SaveSessionInDB(gc, user.ID) + go db.Provider.AddSession(models.Session{ + UserID: user.ID, + UserAgent: utils.GetUserAgent(gc.Request), + IP: utils.GetIP(gc.Request), + }) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 65e8494..95e19da 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -8,6 +8,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/graph/model" "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" @@ -62,7 +63,11 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) - go utils.SaveSessionInDB(gc, user.ID) + go db.Provider.AddSession(models.Session{ + UserID: user.ID, + UserAgent: utils.GetUserAgent(gc.Request), + IP: utils.GetIP(gc.Request), + }) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { diff --git a/server/utils/common.go b/server/utils/common.go index 6835806..badd7ea 100644 --- a/server/utils/common.go +++ b/server/utils/common.go @@ -1,12 +1,7 @@ package utils import ( - "log" "reflect" - - "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/db/models" - "github.com/gin-gonic/gin" ) // StringSliceContains checks if a string slice contains a particular string @@ -19,23 +14,6 @@ func StringSliceContains(s []string, e string) bool { return false } -// SaveSessionInDB saves sessions generated for a given user with meta information -// Do not store token here as that could be security breach -func SaveSessionInDB(c *gin.Context, userId string) { - sessionData := models.Session{ - UserID: userId, - UserAgent: GetUserAgent(c.Request), - IP: GetIP(c.Request), - } - - err := db.Provider.AddSession(sessionData) - if err != nil { - log.Println("=> error saving session in db:", err) - } else { - log.Println("=> session saved in db:", sessionData) - } -} - // RemoveDuplicateString removes duplicate strings from a string slice func RemoveDuplicateString(strSlice []string) []string { allKeys := make(map[string]bool) diff --git a/server/utils/file.go b/server/utils/file.go new file mode 100644 index 0000000..f1ab3f1 --- /dev/null +++ b/server/utils/file.go @@ -0,0 +1,48 @@ +package utils + +import ( + "errors" + "os" +) + +// CreateFolder creates a folder in Current working dir +func CreateFolder(dir string) (string, error) { + pwd, err := os.Getwd() + if err != nil { + return "", err + } + path := pwd + "/" + dir + err = os.Mkdir(path, 0o755) + if err == nil { + return path, nil + } + if os.IsExist(err) { + // check that the existing path is a directory + info, err := os.Stat(path) + if err != nil { + return "", err + } + if !info.IsDir() { + return "", errors.New("path exists but is not a directory") + } + return path, nil + } + return path, err +} + +// CreateFile creates a file on given path with given content +func CreateFile(filePath string, content string) error { + f, err := os.Create(filePath) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(content) + + if err != nil { + return err + } + return nil +}