From dfa96f09a0fa7b8b84c5cdb4089371b49c9bbbe4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 22 Feb 2022 11:06:47 +0530 Subject: [PATCH 01/41] feat: add required jwt claims --- server/token/auth_token.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 1d8c988..f5a2e44 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -67,6 +67,9 @@ func CreateRefreshToken(user models.User, roles []string) (string, int64, error) expiresAt := time.Now().Add(expiryBound).Unix() customClaims := jwt.MapClaims{ + "iss": "", + "aud": "", + "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), "token_type": constants.TokenTypeRefreshToken, @@ -94,6 +97,10 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error) claimKey := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) customClaims := jwt.MapClaims{ + "iss": "", + "aud": "", + "nonce": "", + "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), "token_type": constants.TokenTypeAccessToken, From 332269ecf9de59d1f6e1d777402266b046cdd5b4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 23 Feb 2022 11:24:52 +0530 Subject: [PATCH 02/41] feat: add well-known config endpoint --- server/handlers/openid_config.go | 37 ++++++++++++++++++++++++++++++++ server/routes/routes.go | 2 ++ 2 files changed, 39 insertions(+) create mode 100644 server/handlers/openid_config.go diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go new file mode 100644 index 0000000..9c22e39 --- /dev/null +++ b/server/handlers/openid_config.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "strings" + + "github.com/gin-gonic/gin" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/utils" +) + +// OpenIDConfigurationHandler handler for open-id configurations +func OpenIDConfigurationHandler() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.Contains(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType), "HS") { + c.JSON(400, gin.H{"error": "openid not supported for HSA algorithm"}) + return + } + + issuer := utils.GetHost(c) + jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + + c.JSON(200, gin.H{ + "issuer": issuer, + "authorization_endpoint": issuer + "/authorize", + "token_endpoint": issuer + "/oauth/token", + "userinfo_endpoint": issuer + "/userinfo", + "jwks_uri": issuer + "/jwks.json", + "response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"}, + "scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"}, + "response_modes_supported": []string{"query", "fragment", "form_post"}, + "id_token_signing_alg_values_supported": []string{jwtType}, + "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "gender", "birthdate", "phone_number", "phone_number_verified"}, + }) + } +} diff --git a/server/routes/routes.go b/server/routes/routes.go index d957f86..23ee36e 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -20,6 +20,8 @@ func InitRouter() *gin.Engine { router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler()) router.GET("/verify_email", handlers.VerifyEmailHandler()) + // OPEN ID routes + router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler()) router.LoadHTMLGlob("templates/*") // login page app related routes. From 4e19f73845fe70634f14075dd7592a79b5e9886e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 26 Feb 2022 09:44:55 +0530 Subject: [PATCH 03/41] fix: segregate env setup --- server/constants/env.go | 2 + server/crypto/ecdsa.go | 137 +++++++++++++++++++++++ server/crypto/hmac.go | 19 ++++ server/crypto/rsa.go | 127 ++++++++++++++++++++++ server/env/env.go | 205 +++++++++++++++++++++++------------ server/env/persist_env.go | 54 +++++++-- server/main.go | 14 ++- server/test/env_file_test.go | 2 +- server/test/test.go | 2 +- 9 files changed, 478 insertions(+), 84 deletions(-) create mode 100644 server/crypto/ecdsa.go create mode 100644 server/crypto/hmac.go create mode 100644 server/crypto/rsa.go diff --git a/server/constants/env.go b/server/constants/env.go index e36c5e3..213a125 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -20,6 +20,8 @@ const ( EnvKeyAuthorizerURL = "AUTHORIZER_URL" // EnvKeyPort key for env variable PORT EnvKeyPort = "PORT" + // EnvKeyClientID key for env variable CLIENT_ID + EnvKeyClientID = "CLIENT_ID" // EnvKeyAdminSecret key for env variable ADMIN_SECRET EnvKeyAdminSecret = "ADMIN_SECRET" diff --git a/server/crypto/ecdsa.go b/server/crypto/ecdsa.go new file mode 100644 index 0000000..b76813b --- /dev/null +++ b/server/crypto/ecdsa.go @@ -0,0 +1,137 @@ +package crypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" +) + +// NewECDSAKey to generate new ECDSA Key if env is not set +func NewECDSAKey() (*ecdsa.PrivateKey, string, string, error) { + key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, "", "", err + } + + privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey) + if err != nil { + return nil, "", "", err + } + + return key, privateKey, publicKey, err +} + +// IsECDSA checks if given string is valid ECDSA algo +func IsECDSA(algo string) bool { + switch algo { + case "ES256", "ES384", "ES512": + return true + default: + return false + } +} + +// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string +func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) { + privkeyBytes, err := x509.MarshalECPrivateKey(privkey) + if err != nil { + return "", err + } + privkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "ECDSA PRIVATE KEY", + Bytes: privkeyBytes, + }, + ) + return string(privkeyPem), nil +} + +// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string +func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) { + pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + return "", err + } + pubkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "ECDSA PUBLIC KEY", + Bytes: pubkeyBytes, + }, + ) + + return string(pubkeyPem), nil +} + +// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string +func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(privPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + priv, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return priv, nil +} + +// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string +func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) { + block, _ := pem.Decode([]byte(pubPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := pub.(type) { + case *ecdsa.PublicKey: + return pub, nil + default: + break // fall through + } + return nil, errors.New("Key type is not ECDSA") +} + +// AsECDSAStr returns private, public key string or error +func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) { + // Export the keys to pem string + privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey) + if err != nil { + return "", "", err + } + pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey) + if err != nil { + return "", "", err + } + + // Import the keys from pem string + privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem) + if err != nil { + return "", "", err + } + pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem) + if err != nil { + return "", "", err + } + + // Export the newly imported keys + privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed) + if err != nil { + return "", "", err + } + pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed) + if err != nil { + return "", "", err + } + + return privParsedPem, pubParsedPem, nil +} diff --git a/server/crypto/hmac.go b/server/crypto/hmac.go new file mode 100644 index 0000000..cb349b5 --- /dev/null +++ b/server/crypto/hmac.go @@ -0,0 +1,19 @@ +package crypto + +import "github.com/google/uuid" + +// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo +func NewHMACKey() string { + key := uuid.New().String() + return key +} + +// IsHMACValid checks if given string is valid HMCA algo +func IsHMACA(algo string) bool { + switch algo { + case "HS256", "HS384", "HS512": + return true + default: + return false + } +} diff --git a/server/crypto/rsa.go b/server/crypto/rsa.go new file mode 100644 index 0000000..34edad5 --- /dev/null +++ b/server/crypto/rsa.go @@ -0,0 +1,127 @@ +package crypto + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// NewRSAKey to generate new RSA Key if env is not set +func NewRSAKey() (*rsa.PrivateKey, string, string, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, "", "", err + } + + privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey) + if err != nil { + return nil, "", "", err + } + + return key, privateKey, publicKey, err +} + +// IsRSA checks if given string is valid RSA algo +func IsRSA(algo string) bool { + switch algo { + case "RS256", "RS384", "RS512": + return true + default: + return false + } +} + +// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string +func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string { + privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey) + privkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privkeyBytes, + }, + ) + return string(privkeyPem) +} + +// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string +func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) { + pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + return "", err + } + pubkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pubkeyBytes, + }, + ) + + return string(pubkeyPem), nil +} + +// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string +func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(privPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return priv, nil +} + +// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string +func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) { + block, _ := pem.Decode([]byte(pubPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + default: + break // fall through + } + return nil, errors.New("Key type is not RSA") +} + +// AsRSAStr returns private, public key string or error +func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) { + // Export the keys to pem string + privPem := ExportRsaPrivateKeyAsPemStr(privateKey) + pubPem, err := ExportRsaPublicKeyAsPemStr(publickKey) + if err != nil { + return "", "", err + } + + // Import the keys from pem string + privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem) + if err != nil { + return "", "", err + } + pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem) + if err != nil { + return "", "", err + } + + // Export the newly imported keys + privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed) + pubParsedPem, err := ExportRsaPublicKeyAsPemStr(pubParsed) + if err != nil { + return "", "", err + } + + return privParsedPem, pubParsedPem, nil +} diff --git a/server/env/env.go b/server/env/env.go index 11f213d..03ad1de 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -1,22 +1,80 @@ package env import ( + "fmt" "log" "os" "strings" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/joho/godotenv" ) +// InitRequiredEnv to initialize EnvData and through error if required env are not present +func InitRequiredEnv() { + envPath := os.Getenv(constants.EnvKeyEnvPath) + + if envPath == "" { + envPath = `.env` + } + + if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" { + envPath = *envstore.ARG_ENV_FILE + } + + err := godotenv.Load(envPath) + if err != nil { + log.Printf("using OS env instead of %s file", envPath) + } + + dbURL := os.Getenv(constants.EnvKeyDatabaseURL) + dbType := os.Getenv(constants.EnvKeyDatabaseType) + dbName := os.Getenv(constants.EnvKeyDatabaseName) + + if dbType == "" { + if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" { + dbType = *envstore.ARG_DB_TYPE + } + + if dbType == "" { + panic("DATABASE_TYPE is required") + } + } + + if dbURL == "" { + if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" { + dbURL = *envstore.ARG_DB_URL + } + + if dbURL == "" { + panic("DATABASE_URL is required") + } + } + + if dbName == "" { + if dbName == "" { + dbName = "authorizer" + } + } + + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath) + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName) +} + // InitEnv to initialize EnvData and through error if required env are not present -func InitEnv() { - // get clone of current store - envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() +func InitAllEnv() { + envData, err := GetEnvData() + if err != nil { + log.Println("No env data found in db, using local clone of env data") + // get clone of current store + envData = envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + } if envData.StringEnv[constants.EnvKeyEnv] == "" { envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv) @@ -36,19 +94,6 @@ func InitEnv() { envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL) } - if envData.StringEnv[constants.EnvKeyEnvPath] == "" { - envData.StringEnv[constants.EnvKeyEnvPath] = `.env` - } - - if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" { - envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE - } - - err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath]) - if err != nil { - log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath]) - } - if envData.StringEnv[constants.EnvKeyPort] == "" { envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort) if envData.StringEnv[constants.EnvKeyPort] == "" { @@ -60,37 +105,6 @@ func InitEnv() { envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret) } - if envData.StringEnv[constants.EnvKeyDatabaseType] == "" { - envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv(constants.EnvKeyDatabaseType) - - if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" { - envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE - } - - if envData.StringEnv[constants.EnvKeyDatabaseType] == "" { - panic("DATABASE_TYPE is required") - } - } - - if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" { - envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv(constants.EnvKeyDatabaseURL) - - if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" { - envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL - } - - if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" { - panic("DATABASE_URL is required") - } - } - - if envData.StringEnv[constants.EnvKeyDatabaseName] == "" { - envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv(constants.EnvKeyDatabaseName) - if envData.StringEnv[constants.EnvKeyDatabaseName] == "" { - envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer" - } - } - if envData.StringEnv[constants.EnvKeySmtpHost] == "" { envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost) } @@ -111,32 +125,83 @@ func InitEnv() { envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail) } - if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { - envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret) - if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { - envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String() - } - } - - if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" { - envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript) - } - - if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" { - envData.StringEnv[constants.EnvKeyJwtPrivateKey] = os.Getenv(constants.EnvKeyJwtPrivateKey) - } - - if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" { - envData.StringEnv[constants.EnvKeyJwtPublicKey] = os.Getenv(constants.EnvKeyJwtPublicKey) - } - + algo := "" if envData.StringEnv[constants.EnvKeyJwtType] == "" { envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType) if envData.StringEnv[constants.EnvKeyJwtType] == "" { - envData.StringEnv[constants.EnvKeyJwtType] = "HS256" + envData.StringEnv[constants.EnvKeyJwtType] = "RS256" + algo = envData.StringEnv[constants.EnvKeyJwtType] + } else { + algo = envData.StringEnv[constants.EnvKeyJwtType] + if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) { + panic("JWT_TYPE is invalid") + } } } + if envData.StringEnv[constants.EnvKeyJwtSecret] == "" && crypto.IsHMACA(algo) { + envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret) + if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { + envData.StringEnv[constants.EnvKeyJwtSecret] = crypto.NewHMACKey() + } + } + + if crypto.IsRSA(algo) || crypto.IsECDSA(algo) { + privateKey, publicKey := "", "" + + if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" { + privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey) + } + + if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" { + publicKey = os.Getenv(constants.EnvKeyJwtPublicKey) + } + + // if algo is RSA / ECDSA, then we need to have both private and public key + // if either of them is not present generate new keys + if privateKey == "" || publicKey == "" { + if crypto.IsRSA(algo) { + _, privateKey, publicKey, err = crypto.NewRSAKey() + if err != nil { + panic(err) + } + } else if crypto.IsECDSA(algo) { + _, privateKey, publicKey, err = crypto.NewECDSAKey() + if err != nil { + panic(err) + } + } + } else { + // parse keys to make sure they are valid + if crypto.IsRSA(algo) { + _, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey) + if err != nil { + panic(err) + } + + _, err = crypto.ParseRsaPublicKeyFromPemStr(publicKey) + if err != nil { + panic(err) + } + } else if crypto.IsECDSA(algo) { + _, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey) + if err != nil { + panic(err) + } + + _, err = crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) + if err != nil { + panic(err) + } + } + fmt.Println("=> keys parsed successfully") + } + fmt.Println(privateKey) + fmt.Println(publicKey) + envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey + envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey + } + if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" { envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv(constants.EnvKeyJwtRoleClaim) @@ -145,6 +210,10 @@ func InitEnv() { } } + if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" { + envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript) + } + if envData.StringEnv[constants.EnvKeyRedisURL] == "" { envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL) } diff --git a/server/env/persist_env.go b/server/env/persist_env.go index de2fc49..cd44e95 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -15,6 +15,40 @@ import ( "github.com/google/uuid" ) +// GetEnvData returns the env data from database +func GetEnvData() (envstore.Store, error) { + var result envstore.Store + env, err := db.Provider.GetEnv() + // config not found in db + if err != nil { + return result, err + } + + encryptionKey := env.Hash + decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey) + if err != nil { + return result, err + } + + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) + b64DecryptedConfig, err := utils.DecryptB64(env.EnvData) + if err != nil { + return result, err + } + + decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig)) + if err != nil { + return result, err + } + + err = json.Unmarshal(decryptedConfigs, &result) + if err != nil { + return result, err + } + + return result, err +} + // PersistEnv persists the environment variables to the database func PersistEnv() error { env, err := db.Provider.GetEnv() @@ -29,22 +63,16 @@ func PersistEnv() error { if err != nil { return err } - // configData, err := json.Marshal() - // if err != nil { - // return err - // } - - // encryptedConfig, err := utils.EncryptAES(configData) - // if err != nil { - // return err - // } env = models.Env{ Hash: encodedHash, EnvData: encryptedConfig, } - db.Provider.AddEnv(env) + env, err = db.Provider.AddEnv(env) + if err != nil { + return err + } } else { // decrypt the config data from db // decryption can be done using the hash stored in db @@ -134,6 +162,7 @@ func PersistEnv() error { } envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData) + if hasChanged { encryptedConfig, err := utils.EncryptEnvData(storeData) if err != nil { @@ -147,8 +176,11 @@ func PersistEnv() error { return err } } - } + // ID of env is used to identify the config and declared as client id + // this client id can be used in `aud` section of JWT token + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyClientID, env.ID) + return nil } diff --git a/server/main.go b/server/main.go index 377f454..f41ccd3 100644 --- a/server/main.go +++ b/server/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "log" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" @@ -22,13 +23,20 @@ func main() { envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION) - env.InitEnv() + // initialize required envs (mainly db env & env file path) + env.InitRequiredEnv() + // initialize db provider db.InitDB() - env.PersistEnv() + // initialize all envs + env.InitAllEnv() + // persist all envs + err := env.PersistEnv() + if err != nil { + log.Println("Error persisting env:", err) + } sessionstore.InitSession() oauth.InitOAuth() - router := routes.InitRouter() router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort)) diff --git a/server/test/env_file_test.go b/server/test/env_file_test.go index 8a6a838..50f8e74 100644 --- a/server/test/env_file_test.go +++ b/server/test/env_file_test.go @@ -11,7 +11,7 @@ import ( func TestEnvs(t *testing.T) { envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") - env.InitEnv() + env.InitAllEnv() store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production") diff --git a/server/test/test.go b/server/test/test.go index e0451a9..e0ff189 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -78,7 +78,7 @@ func testSetup() TestSetup { envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test") envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com") envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"}) - env.InitEnv() + env.InitAllEnv() sessionstore.InitSession() w := httptest.NewRecorder() From ad462101121426eb12778fbf44d39c3148932c8f Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 26 Feb 2022 10:06:26 +0530 Subject: [PATCH 04/41] fix: report error on initialization --- server/db/db.go | 12 +++---- server/env/env.go | 40 +++++++++++------------ server/main.go | 46 ++++++++++++++++++++------- server/oauth/oauth.go | 7 ++-- server/sessionstore/session.go | 58 ++++++++++++++++++---------------- 5 files changed, 94 insertions(+), 69 deletions(-) diff --git a/server/db/db.go b/server/db/db.go index ca14e16..9890878 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -1,8 +1,6 @@ package db import ( - "log" - "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db/providers" "github.com/authorizerdev/authorizer/server/db/providers/arangodb" @@ -14,7 +12,7 @@ import ( // Provider returns the current database provider var Provider providers.Provider -func InitDB() { +func InitDB() error { var err error isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb @@ -24,21 +22,23 @@ func InitDB() { if isSQL { Provider, err = sql.NewProvider() if err != nil { - log.Fatal("=> error setting sql provider:", err) + return err } } if isArangoDB { Provider, err = arangodb.NewProvider() if err != nil { - log.Fatal("=> error setting arangodb provider:", err) + return err } } if isMongoDB { Provider, err = mongodb.NewProvider() if err != nil { - log.Fatal("=> error setting arangodb provider:", err) + return err } } + + return nil } diff --git a/server/env/env.go b/server/env/env.go index 03ad1de..576f438 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -1,7 +1,7 @@ package env import ( - "fmt" + "errors" "log" "os" "strings" @@ -15,7 +15,7 @@ import ( ) // InitRequiredEnv to initialize EnvData and through error if required env are not present -func InitRequiredEnv() { +func InitRequiredEnv() error { envPath := os.Getenv(constants.EnvKeyEnvPath) if envPath == "" { @@ -35,23 +35,23 @@ func InitRequiredEnv() { dbType := os.Getenv(constants.EnvKeyDatabaseType) dbName := os.Getenv(constants.EnvKeyDatabaseName) - if dbType == "" { + if strings.TrimSpace(dbType) == "" { if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" { - dbType = *envstore.ARG_DB_TYPE + dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE) } if dbType == "" { - panic("DATABASE_TYPE is required") + return errors.New("invalid database type. DATABASE_TYPE is empty") } } - if dbURL == "" { + if strings.TrimSpace(dbURL) == "" { if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" { - dbURL = *envstore.ARG_DB_URL + dbURL = strings.TrimSpace(*envstore.ARG_DB_URL) } if dbURL == "" { - panic("DATABASE_URL is required") + return errors.New("invalid database url. DATABASE_URL is required") } } @@ -65,10 +65,11 @@ func InitRequiredEnv() { envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName) + return nil } // InitEnv to initialize EnvData and through error if required env are not present -func InitAllEnv() { +func InitAllEnv() error { envData, err := GetEnvData() if err != nil { log.Println("No env data found in db, using local clone of env data") @@ -134,7 +135,7 @@ func InitAllEnv() { } else { algo = envData.StringEnv[constants.EnvKeyJwtType] if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) { - panic("JWT_TYPE is invalid") + return errors.New("invalid JWT_TYPE") } } } @@ -163,12 +164,12 @@ func InitAllEnv() { if crypto.IsRSA(algo) { _, privateKey, publicKey, err = crypto.NewRSAKey() if err != nil { - panic(err) + return err } } else if crypto.IsECDSA(algo) { _, privateKey, publicKey, err = crypto.NewECDSAKey() if err != nil { - panic(err) + return err } } } else { @@ -176,28 +177,26 @@ func InitAllEnv() { if crypto.IsRSA(algo) { _, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey) if err != nil { - panic(err) + return err } _, err = crypto.ParseRsaPublicKeyFromPemStr(publicKey) if err != nil { - panic(err) + return err } } else if crypto.IsECDSA(algo) { _, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey) if err != nil { - panic(err) + return err } _, err = crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) if err != nil { - panic(err) + return err } } - fmt.Println("=> keys parsed successfully") } - fmt.Println(privateKey) - fmt.Println(publicKey) + envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey } @@ -333,7 +332,7 @@ func InitAllEnv() { } if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 { - panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) + return errors.New(`invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) } envData.SliceEnv[constants.EnvKeyRoles] = roles @@ -349,4 +348,5 @@ func InitAllEnv() { } envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData) + return nil } diff --git a/server/main.go b/server/main.go index f41ccd3..89b8fc0 100644 --- a/server/main.go +++ b/server/main.go @@ -23,21 +23,43 @@ func main() { envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION) - // initialize required envs (mainly db env & env file path) - env.InitRequiredEnv() - // initialize db provider - db.InitDB() - // initialize all envs - env.InitAllEnv() - // persist all envs - err := env.PersistEnv() + // initialize required envs (mainly db & env file path) + err := env.InitRequiredEnv() if err != nil { - log.Println("Error persisting env:", err) + log.Fatal("Error while initializing required envs:", err) } - sessionstore.InitSession() - oauth.InitOAuth() - router := routes.InitRouter() + // initialize db provider + err = db.InitDB() + if err != nil { + log.Fatalln("Error while initializing db:", err) + } + // initialize all envs + // (get if present from db else construct from os env + defaults) + err = env.InitAllEnv() + if err != nil { + log.Fatalln("Error while initializing env: ", err) + } + + // persist all envs + err = env.PersistEnv() + if err != nil { + 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 { + log.Fatalln("Error while initializing oauth:", err) + } + + router := routes.InitRouter() router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort)) } diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index c0ff694..5d79125 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -2,7 +2,6 @@ package oauth import ( "context" - "log" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" @@ -32,12 +31,12 @@ var ( ) // InitOAuth initializes the OAuth providers based on EnvData -func InitOAuth() { +func InitOAuth() error { ctx := context.Background() if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" { p, err := oidc.NewProvider(ctx, "https://accounts.google.com") if err != nil { - log.Fatalln("error creating oidc provider for google:", err) + return err } OIDCProviders.GoogleOIDC = p OAuthProviders.GoogleConfig = &oauth2.Config{ @@ -65,4 +64,6 @@ func InitOAuth() { Scopes: []string{"public_profile", "email"}, } } + + return nil } diff --git a/server/sessionstore/session.go b/server/sessionstore/session.go index 523dee6..0287a46 100644 --- a/server/sessionstore/session.go +++ b/server/sessionstore/session.go @@ -119,62 +119,64 @@ func RemoveSocialLoginState(key string) { } // InitializeSessionStore initializes the SessionStoreObj based on environment variables -func InitSession() { +func InitSession() error { if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" { log.Println("using redis store to save sessions") - if isCluster(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) { - clusterOpt, err := getClusterOptions(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) + + redisURL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) + redisURLHostPortsList := strings.Split(redisURL, ",") + + if len(redisURLHostPortsList) > 1 { + opt, err := redis.ParseURL(redisURLHostPortsList[0]) if err != nil { - log.Fatalln("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.Fatalln("Error connecting to redis cluster server", err) + return err } SessionStoreObj.RedisMemoryStoreObj = &RedisStore{ ctx: ctx, store: rdb, } - return + + // return on successful initialization + return nil } + opt, err := redis.ParseURL(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) if err != nil { - log.Fatalln("Error parsing redis url:", err) + return err } + rdb := redis.NewClient(opt) ctx := context.Background() _, err = rdb.Ping(ctx).Result() - if err != nil { - log.Fatalln("Error connecting to redis server", err) + return err } + SessionStoreObj.RedisMemoryStoreObj = &RedisStore{ ctx: ctx, store: rdb, } - } else { - SessionStoreObj.InMemoryStoreObj = &InMemoryStore{ - store: map[string]map[string]string{}, - socialLoginState: map[string]string{}, - } + // return on successful initialization + return nil } -} -func isCluster(url string) bool { - return len(strings.Split(url, ",")) > 1 -} - -func getClusterOptions(url string) (*redis.ClusterOptions, error) { - hostPortsList := strings.Split(url, ",") - opt, err := redis.ParseURL(hostPortsList[0]) - if err != nil { - return nil, err + // if redis url is not set use in memory store + SessionStoreObj.InMemoryStoreObj = &InMemoryStore{ + store: map[string]map[string]string{}, + socialLoginState: map[string]string{}, } - urls := []string{opt.Addr} - urlList := hostPortsList[1:] - urls = append(urls, urlList...) - return &redis.ClusterOptions{Addrs: urls}, nil + + return nil } From 145091dce1a8e5ea639fd71a1e5b1856ece85e27 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 26 Feb 2022 18:14:43 +0530 Subject: [PATCH 05/41] feat: add well-known jwks.json endpoint --- server/constants/env.go | 12 ++++++--- server/crypto/common.go | 29 ++++++++++++++++++++ server/crypto/ecdsa.go | 14 +++++++--- server/crypto/hmac.go | 13 ++++++--- server/crypto/rsa.go | 43 ++++++++++++------------------ server/env/env.go | 45 ++++++++++++++++++++++++++------ server/env/persist_env.go | 4 --- server/go.mod | 1 + server/go.sum | 20 ++++++++++++++ server/handlers/jwks.go | 21 +++++++++++++++ server/handlers/openid_config.go | 9 +------ server/routes/routes.go | 1 + server/token/auth_token.go | 2 +- 13 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 server/crypto/common.go create mode 100644 server/handlers/jwks.go diff --git a/server/constants/env.go b/server/constants/env.go index 213a125..abf604c 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -20,8 +20,6 @@ const ( EnvKeyAuthorizerURL = "AUTHORIZER_URL" // EnvKeyPort key for env variable PORT EnvKeyPort = "PORT" - // EnvKeyClientID key for env variable CLIENT_ID - EnvKeyClientID = "CLIENT_ID" // EnvKeyAdminSecret key for env variable ADMIN_SECRET EnvKeyAdminSecret = "ADMIN_SECRET" @@ -95,8 +93,14 @@ const ( EnvKeyOrganizationName = "ORGANIZATION_NAME" // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO EnvKeyOrganizationLogo = "ORGANIZATION_LOGO" - // EnvKeyIsProd key for env variable IS_PROD - EnvKeyIsProd = "IS_PROD" // EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT" + + // Not Exposed Keys + // EnvKeyClientID key for env variable CLIENT_ID + EnvKeyClientID = "CLIENT_ID" + // EnvKeyJWK key for env variable JWK + EnvKeyJWK = "JWK" + // EnvKeyIsProd key for env variable IS_PROD + EnvKeyIsProd = "IS_PROD" ) diff --git a/server/crypto/common.go b/server/crypto/common.go new file mode 100644 index 0000000..22b354e --- /dev/null +++ b/server/crypto/common.go @@ -0,0 +1,29 @@ +package crypto + +import ( + "crypto/x509" + + "gopkg.in/square/go-jose.v2" +) + +// GetPubJWK returns JWK for given keys +func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) { + jwk := &jose.JSONWebKeySet{ + Keys: []jose.JSONWebKey{ + { + Algorithm: algo, + Key: publicKey, + Use: "sig", + KeyID: keyID, + Certificates: []*x509.Certificate{}, + CertificateThumbprintSHA1: []uint8{}, + CertificateThumbprintSHA256: []uint8{}, + }, + }, + } + jwkPublicKey, err := jwk.Keys[0].MarshalJSON() + if err != nil { + return "", err + } + return string(jwkPublicKey), nil +} diff --git a/server/crypto/ecdsa.go b/server/crypto/ecdsa.go index b76813b..3b8e4b6 100644 --- a/server/crypto/ecdsa.go +++ b/server/crypto/ecdsa.go @@ -10,18 +10,24 @@ import ( ) // NewECDSAKey to generate new ECDSA Key if env is not set -func NewECDSAKey() (*ecdsa.PrivateKey, string, string, error) { +// returns key instance, private key string, public key string, jwk string, error +func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) { key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { - return nil, "", "", err + return nil, "", "", "", err } privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey) if err != nil { - return nil, "", "", err + return nil, "", "", "", err } - return key, privateKey, publicKey, err + jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey) + if err != nil { + return nil, "", "", "", err + } + + return key, privateKey, publicKey, string(jwkPublicKey), err } // IsECDSA checks if given string is valid ECDSA algo diff --git a/server/crypto/hmac.go b/server/crypto/hmac.go index cb349b5..a70916b 100644 --- a/server/crypto/hmac.go +++ b/server/crypto/hmac.go @@ -1,11 +1,18 @@ package crypto -import "github.com/google/uuid" +import ( + "github.com/google/uuid" +) // NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo -func NewHMACKey() string { +// returns key, string, error +func NewHMACKey(algo, keyID string) (string, string, error) { key := uuid.New().String() - return key + jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key)) + if err != nil { + return "", "", err + } + return key, string(jwkPublicKey), nil } // IsHMACValid checks if given string is valid HMCA algo diff --git a/server/crypto/rsa.go b/server/crypto/rsa.go index 34edad5..35bebd3 100644 --- a/server/crypto/rsa.go +++ b/server/crypto/rsa.go @@ -9,18 +9,24 @@ import ( ) // NewRSAKey to generate new RSA Key if env is not set -func NewRSAKey() (*rsa.PrivateKey, string, string, error) { +// returns key instance, private key string, public key string, jwk string, error +func NewRSAKey(algo, keyID string) (*rsa.PrivateKey, string, string, string, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return nil, "", "", err + return nil, "", "", "", err } privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey) if err != nil { - return nil, "", "", err + return nil, "", "", "", err } - return key, privateKey, publicKey, err + jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey) + if err != nil { + return nil, "", "", "", err + } + + return key, privateKey, publicKey, string(jwkPublicKey), err } // IsRSA checks if given string is valid RSA algo @@ -46,11 +52,8 @@ func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string { } // ExportRsaPublicKeyAsPemStr to get RSA public key as pem string -func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) { - pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) - if err != nil { - return "", err - } +func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) string { + pubkeyBytes := x509.MarshalPKCS1PublicKey(pubkey) pubkeyPem := pem.EncodeToMemory( &pem.Block{ Type: "RSA PUBLIC KEY", @@ -58,7 +61,7 @@ func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) { }, ) - return string(pubkeyPem), nil + return string(pubkeyPem) } // ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string @@ -83,28 +86,19 @@ func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) { return nil, errors.New("failed to parse PEM block containing the key") } - pub, err := x509.ParsePKIXPublicKey(block.Bytes) + pub, err := x509.ParsePKCS1PublicKey(block.Bytes) if err != nil { return nil, err } - switch pub := pub.(type) { - case *rsa.PublicKey: - return pub, nil - default: - break // fall through - } - return nil, errors.New("Key type is not RSA") + return pub, nil } // AsRSAStr returns private, public key string or error func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) { // Export the keys to pem string privPem := ExportRsaPrivateKeyAsPemStr(privateKey) - pubPem, err := ExportRsaPublicKeyAsPemStr(publickKey) - if err != nil { - return "", "", err - } + pubPem := ExportRsaPublicKeyAsPemStr(publickKey) // Import the keys from pem string privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem) @@ -118,10 +112,7 @@ func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, st // Export the newly imported keys privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed) - pubParsedPem, err := ExportRsaPublicKeyAsPemStr(pubParsed) - if err != nil { - return "", "", err - } + pubParsedPem := ExportRsaPublicKeyAsPemStr(pubParsed) return privParsedPem, pubParsedPem, nil } diff --git a/server/env/env.go b/server/env/env.go index 576f438..8c56638 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -11,6 +11,7 @@ import ( "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" + "github.com/google/uuid" "github.com/joho/godotenv" ) @@ -77,6 +78,13 @@ func InitAllEnv() error { envData = envstore.EnvInMemoryStoreObj.GetEnvStoreClone() } + clientID := envData.StringEnv[constants.EnvKeyClientID] + // unique client id for each instance + if clientID == "" { + clientID = uuid.New().String() + envData.StringEnv[constants.EnvKeyClientID] = clientID + } + if envData.StringEnv[constants.EnvKeyEnv] == "" { envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv) if envData.StringEnv[constants.EnvKeyEnv] == "" { @@ -126,8 +134,8 @@ func InitAllEnv() error { envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail) } - algo := "" - if envData.StringEnv[constants.EnvKeyJwtType] == "" { + algo := envData.StringEnv[constants.EnvKeyJwtType] + if algo == "" { envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType) if envData.StringEnv[constants.EnvKeyJwtType] == "" { envData.StringEnv[constants.EnvKeyJwtType] = "RS256" @@ -143,12 +151,21 @@ func InitAllEnv() error { if envData.StringEnv[constants.EnvKeyJwtSecret] == "" && crypto.IsHMACA(algo) { envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret) if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { - envData.StringEnv[constants.EnvKeyJwtSecret] = crypto.NewHMACKey() + envData.StringEnv[constants.EnvKeyJwtSecret], envData.StringEnv[constants.EnvKeyJWK], err = crypto.NewHMACKey(algo, clientID) + if err != nil { + return err + } + } else { + envData.StringEnv[constants.EnvKeyJWK], err = crypto.GetPubJWK(algo, clientID, []byte(envData.StringEnv[constants.EnvKeyJwtSecret])) + if err != nil { + return err + } + } } if crypto.IsRSA(algo) || crypto.IsECDSA(algo) { - privateKey, publicKey := "", "" + privateKey, publicKey, jwk := "", "", "" if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" { privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey) @@ -162,12 +179,12 @@ func InitAllEnv() error { // if either of them is not present generate new keys if privateKey == "" || publicKey == "" { if crypto.IsRSA(algo) { - _, privateKey, publicKey, err = crypto.NewRSAKey() + _, privateKey, publicKey, jwk, err = crypto.NewRSAKey(algo, clientID) if err != nil { return err } } else if crypto.IsECDSA(algo) { - _, privateKey, publicKey, err = crypto.NewECDSAKey() + _, privateKey, publicKey, jwk, err = crypto.NewECDSAKey(algo, clientID) if err != nil { return err } @@ -180,7 +197,12 @@ func InitAllEnv() error { return err } - _, err = crypto.ParseRsaPublicKeyFromPemStr(publicKey) + publicKeyInstance, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey) + if err != nil { + return err + } + + jwk, err = crypto.GetPubJWK(algo, clientID, publicKeyInstance) if err != nil { return err } @@ -190,15 +212,22 @@ func InitAllEnv() error { return err } - _, err = crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) + publicKeyInstance, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) + if err != nil { + return err + } + + jwk, err = crypto.GetPubJWK(algo, clientID, publicKeyInstance) if err != nil { return err } } } + envData.StringEnv[constants.EnvKeyJWK] = jwk envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey + } if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" { diff --git a/server/env/persist_env.go b/server/env/persist_env.go index cd44e95..eae32c7 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -178,9 +178,5 @@ func PersistEnv() error { } } - // ID of env is used to identify the config and declared as client id - // this client id can be used in `aud` section of JWT token - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyClientID, env.ID) - return nil } diff --git a/server/go.mod b/server/go.mod index e470a65..447ef5b 100644 --- a/server/go.mod +++ b/server/go.mod @@ -30,6 +30,7 @@ require ( google.golang.org/protobuf v1.27.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 + gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/mysql v1.2.1 gorm.io/driver/postgres v1.2.3 diff --git a/server/go.sum b/server/go.sum index bcf90b7..904c6e6 100644 --- a/server/go.sum +++ b/server/go.sum @@ -69,6 +69,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -106,6 +109,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -256,6 +261,18 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.19 h1:qxxLmAXNwZpTTvjc4PH21nT7I4wPK6lVv3lVNcZPnUk= +github.com/lestrrat-go/jwx v1.2.19/go.mod h1:bWTBO7IHHVMtNunM8so9MT8wD+euEY1PzGEyCnuI2qM= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -386,6 +403,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= @@ -684,6 +702,8 @@ gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/server/handlers/jwks.go b/server/handlers/jwks.go new file mode 100644 index 0000000..526c319 --- /dev/null +++ b/server/handlers/jwks.go @@ -0,0 +1,21 @@ +package handlers + +import ( + "encoding/json" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/gin-gonic/gin" +) + +func JWKsHandler() gin.HandlerFunc { + var data map[string]string + json.Unmarshal([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK)), &data) + return func(c *gin.Context) { + c.JSON(200, gin.H{ + "keys": []map[string]string{ + data, + }, + }) + } +} diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index 9c22e39..eac91ca 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -1,8 +1,6 @@ package handlers import ( - "strings" - "github.com/gin-gonic/gin" "github.com/authorizerdev/authorizer/server/constants" @@ -13,11 +11,6 @@ import ( // OpenIDConfigurationHandler handler for open-id configurations func OpenIDConfigurationHandler() gin.HandlerFunc { return func(c *gin.Context) { - if strings.Contains(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType), "HS") { - c.JSON(400, gin.H{"error": "openid not supported for HSA algorithm"}) - return - } - issuer := utils.GetHost(c) jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) @@ -26,7 +19,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { "authorization_endpoint": issuer + "/authorize", "token_endpoint": issuer + "/oauth/token", "userinfo_endpoint": issuer + "/userinfo", - "jwks_uri": issuer + "/jwks.json", + "jwks_uri": issuer + "/.well-known/jwks.json", "response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"}, "scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"}, "response_modes_supported": []string{"query", "fragment", "form_post"}, diff --git a/server/routes/routes.go b/server/routes/routes.go index 23ee36e..0544431 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -22,6 +22,7 @@ func InitRouter() *gin.Engine { router.GET("/verify_email", handlers.VerifyEmailHandler()) // OPEN ID routes router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler()) + router.GET("/.well-known/jwks.json", handlers.JWKsHandler()) router.LoadHTMLGlob("templates/*") // login page app related routes. diff --git a/server/token/auth_token.go b/server/token/auth_token.go index f5a2e44..5906e2d 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -98,7 +98,7 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error) claimKey := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) customClaims := jwt.MapClaims{ "iss": "", - "aud": "", + "aud": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), "nonce": "", "sub": user.ID, "exp": expiresAt, From b68d9ce661f6676a8e092d837f09196063f31a94 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 26 Feb 2022 20:36:22 +0530 Subject: [PATCH 06/41] fix: update_env resolver --- server/constants/env.go | 4 +- server/crypto/common.go | 44 +++++++++ server/env/env.go | 38 +++----- server/env/persist_env.go | 14 ++- server/go.mod | 2 +- server/go.sum | 19 ---- server/graph/generated/generated.go | 134 ++++++++++++++++++++++++++-- server/graph/model/models_gen.go | 8 +- server/graph/schema.graphqls | 8 +- server/handlers/jwks.go | 13 ++- server/resolvers/env.go | 8 +- server/resolvers/update_env.go | 78 +++++++++++++++- server/test/resolvers_test.go | 1 + server/utils/meta.go | 1 + 14 files changed, 298 insertions(+), 74 deletions(-) diff --git a/server/constants/env.go b/server/constants/env.go index abf604c..c14f232 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -59,8 +59,6 @@ const ( EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME" // EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL EnvKeyResetPasswordURL = "RESET_PASSWORD_URL" - // EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY - EnvKeyEncryptionKey = "ENCRYPTION_KEY" // EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION" // EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH @@ -99,6 +97,8 @@ const ( // Not Exposed Keys // EnvKeyClientID key for env variable CLIENT_ID EnvKeyClientID = "CLIENT_ID" + // EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY + EnvKeyEncryptionKey = "ENCRYPTION_KEY" // EnvKeyJWK key for env variable JWK EnvKeyJWK = "JWK" // EnvKeyIsProd key for env variable IS_PROD diff --git a/server/crypto/common.go b/server/crypto/common.go index 22b354e..69ab530 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -3,6 +3,8 @@ package crypto import ( "crypto/x509" + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "gopkg.in/square/go-jose.v2" ) @@ -27,3 +29,45 @@ func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) { } return string(jwkPublicKey), nil } + +// GenerateJWKBasedOnEnv generates JWK based on env +func GenerateJWKBasedOnEnv() (string, error) { + jwk := "" + algo := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + clientID := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) + + var err error + // check if jwt secret is provided + if IsHMACA(algo) { + jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret))) + if err != nil { + return "", err + } + } + + if IsRSA(algo) { + publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) + if err != nil { + return "", err + } + + jwk, err = GetPubJWK(algo, clientID, publicKeyInstance) + if err != nil { + return "", err + } + } + + if IsECDSA(algo) { + publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) + if err != nil { + return "", err + } + + jwk, err = GetPubJWK(algo, clientID, publicKeyInstance) + if err != nil { + return "", err + } + } + + return jwk, nil +} diff --git a/server/env/env.go b/server/env/env.go index 8c56638..05e72d5 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -148,24 +148,20 @@ func InitAllEnv() error { } } - if envData.StringEnv[constants.EnvKeyJwtSecret] == "" && crypto.IsHMACA(algo) { - envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret) + if crypto.IsHMACA(algo) { if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { - envData.StringEnv[constants.EnvKeyJwtSecret], envData.StringEnv[constants.EnvKeyJWK], err = crypto.NewHMACKey(algo, clientID) - if err != nil { - return err + envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret) + if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { + envData.StringEnv[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID) + if err != nil { + return err + } } - } else { - envData.StringEnv[constants.EnvKeyJWK], err = crypto.GetPubJWK(algo, clientID, []byte(envData.StringEnv[constants.EnvKeyJwtSecret])) - if err != nil { - return err - } - } } if crypto.IsRSA(algo) || crypto.IsECDSA(algo) { - privateKey, publicKey, jwk := "", "", "" + privateKey, publicKey := "", "" if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" { privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey) @@ -179,12 +175,12 @@ func InitAllEnv() error { // if either of them is not present generate new keys if privateKey == "" || publicKey == "" { if crypto.IsRSA(algo) { - _, privateKey, publicKey, jwk, err = crypto.NewRSAKey(algo, clientID) + _, privateKey, publicKey, _, err = crypto.NewRSAKey(algo, clientID) if err != nil { return err } } else if crypto.IsECDSA(algo) { - _, privateKey, publicKey, jwk, err = crypto.NewECDSAKey(algo, clientID) + _, privateKey, publicKey, _, err = crypto.NewECDSAKey(algo, clientID) if err != nil { return err } @@ -197,34 +193,24 @@ func InitAllEnv() error { return err } - publicKeyInstance, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey) + _, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey) if err != nil { return err } - jwk, err = crypto.GetPubJWK(algo, clientID, publicKeyInstance) - if err != nil { - return err - } } else if crypto.IsECDSA(algo) { _, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey) if err != nil { return err } - publicKeyInstance, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) - if err != nil { - return err - } - - jwk, err = crypto.GetPubJWK(algo, clientID, publicKeyInstance) + _, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey) if err != nil { return err } } } - envData.StringEnv[constants.EnvKeyJWK] = jwk envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey diff --git a/server/env/persist_env.go b/server/env/persist_env.go index eae32c7..caf3346 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -7,12 +7,14 @@ import ( "strconv" "strings" + "github.com/google/uuid" + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" - "github.com/google/uuid" ) // GetEnvData returns the env data from database @@ -107,7 +109,8 @@ func PersistEnv() error { hasChanged := false for key, value := range storeData.StringEnv { - if key != constants.EnvKeyEncryptionKey { + // don't override unexposed envs + if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyJWK { // check only for derivative keys // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data // as we have removed it from json @@ -160,8 +163,13 @@ func PersistEnv() error { hasChanged = true } } - envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData) + jwk, err := crypto.GenerateJWKBasedOnEnv() + if err != nil { + return err + } + // updating jwk + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) if hasChanged { encryptedConfig, err := utils.EncryptEnvData(storeData) diff --git a/server/go.mod b/server/go.mod index 447ef5b..92c9882 100644 --- a/server/go.mod +++ b/server/go.mod @@ -30,7 +30,7 @@ require ( google.golang.org/protobuf v1.27.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 - gopkg.in/square/go-jose.v2 v2.6.0 // indirect + gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/mysql v1.2.1 gorm.io/driver/postgres v1.2.3 diff --git a/server/go.sum b/server/go.sum index 904c6e6..1b2daa1 100644 --- a/server/go.sum +++ b/server/go.sum @@ -69,9 +69,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -109,8 +106,6 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= -github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -261,18 +256,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= -github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= -github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= -github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= -github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= -github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.2.19 h1:qxxLmAXNwZpTTvjc4PH21nT7I4wPK6lVv3lVNcZPnUk= -github.com/lestrrat-go/jwx v1.2.19/go.mod h1:bWTBO7IHHVMtNunM8so9MT8wD+euEY1PzGEyCnuI2qM= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -403,7 +386,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= @@ -700,7 +682,6 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index a93dbe4..8ccae86 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -54,6 +54,7 @@ type ComplexityRoot struct { AdminSecret func(childComplexity int) int AllowedOrigins func(childComplexity int) int AppURL func(childComplexity int) int + ClientID func(childComplexity int) int CookieName func(childComplexity int) int CustomAccessTokenScript func(childComplexity int) int DatabaseName func(childComplexity int) int @@ -94,6 +95,7 @@ type ComplexityRoot struct { } Meta struct { + ClientID func(childComplexity int) int IsBasicAuthenticationEnabled func(childComplexity int) int IsEmailVerificationEnabled func(childComplexity int) int IsFacebookLoginEnabled func(childComplexity int) int @@ -281,6 +283,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Env.AppURL(childComplexity), true + case "Env.CLIENT_ID": + if e.complexity.Env.ClientID == nil { + break + } + + return e.complexity.Env.ClientID(childComplexity), true + case "Env.COOKIE_NAME": if e.complexity.Env.CookieName == nil { break @@ -519,6 +528,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Error.Reason(childComplexity), true + case "Meta.client_id": + if e.complexity.Meta.ClientID == nil { + break + } + + return e.complexity.Meta.ClientID(childComplexity), true + case "Meta.is_basic_authentication_enabled": if e.complexity.Meta.IsBasicAuthenticationEnabled == nil { break @@ -1139,6 +1155,7 @@ type Pagination { type Meta { version: String! + client_id: String! is_google_login_enabled: Boolean! is_facebook_login_enabled: Boolean! is_github_login_enabled: Boolean! @@ -1211,9 +1228,10 @@ type ValidJWTResponse { type Env { ADMIN_SECRET: String - DATABASE_NAME: String - DATABASE_URL: String - DATABASE_TYPE: String + DATABASE_NAME: String! + DATABASE_URL: String! + DATABASE_TYPE: String! + CLIENT_ID: String! CUSTOM_ACCESS_TOKEN_SCRIPT: String SMTP_HOST: String SMTP_PORT: String @@ -1922,11 +1940,14 @@ func (ec *executionContext) _Env_DATABASE_NAME(ctx context.Context, field graphq return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { @@ -1954,11 +1975,14 @@ func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { @@ -1986,11 +2010,49 @@ func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphq return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Env_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Env", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ClientID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { @@ -3090,6 +3152,41 @@ func (ec *executionContext) _Meta_version(ctx context.Context, field graphql.Col return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _Meta_client_id(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Meta", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ClientID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Meta_is_google_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -7619,10 +7716,24 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj out.Values[i] = ec._Env_ADMIN_SECRET(ctx, field, obj) case "DATABASE_NAME": out.Values[i] = ec._Env_DATABASE_NAME(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "DATABASE_URL": out.Values[i] = ec._Env_DATABASE_URL(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "DATABASE_TYPE": out.Values[i] = ec._Env_DATABASE_TYPE(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "CLIENT_ID": + out.Values[i] = ec._Env_CLIENT_ID(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "CUSTOM_ACCESS_TOKEN_SCRIPT": out.Values[i] = ec._Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx, field, obj) case "SMTP_HOST": @@ -7744,6 +7855,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { invalids++ } + case "client_id": + out.Values[i] = ec._Meta_client_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "is_google_login_enabled": out.Values[i] = ec._Meta_is_google_login_enabled(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 0d5a4cc..5db3db7 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -23,9 +23,10 @@ type DeleteUserInput struct { type Env struct { AdminSecret *string `json:"ADMIN_SECRET"` - DatabaseName *string `json:"DATABASE_NAME"` - DatabaseURL *string `json:"DATABASE_URL"` - DatabaseType *string `json:"DATABASE_TYPE"` + DatabaseName string `json:"DATABASE_NAME"` + DatabaseURL string `json:"DATABASE_URL"` + DatabaseType string `json:"DATABASE_TYPE"` + ClientID string `json:"CLIENT_ID"` CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"` SMTPHost *string `json:"SMTP_HOST"` SMTPPort *string `json:"SMTP_PORT"` @@ -86,6 +87,7 @@ type MagicLinkLoginInput struct { type Meta struct { Version string `json:"version"` + ClientID string `json:"client_id"` IsGoogleLoginEnabled bool `json:"is_google_login_enabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 0b88775..7d92c92 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -14,6 +14,7 @@ type Pagination { type Meta { version: String! + client_id: String! is_google_login_enabled: Boolean! is_facebook_login_enabled: Boolean! is_github_login_enabled: Boolean! @@ -86,9 +87,10 @@ type ValidJWTResponse { type Env { ADMIN_SECRET: String - DATABASE_NAME: String - DATABASE_URL: String - DATABASE_TYPE: String + DATABASE_NAME: String! + DATABASE_URL: String! + DATABASE_TYPE: String! + CLIENT_ID: String! CUSTOM_ACCESS_TOKEN_SCRIPT: String SMTP_HOST: String SMTP_PORT: String diff --git a/server/handlers/jwks.go b/server/handlers/jwks.go index 526c319..b74c6fa 100644 --- a/server/handlers/jwks.go +++ b/server/handlers/jwks.go @@ -9,9 +9,18 @@ import ( ) func JWKsHandler() gin.HandlerFunc { - var data map[string]string - json.Unmarshal([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK)), &data) return func(c *gin.Context) { + var data map[string]string + jwk := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK) + err := json.Unmarshal([]byte(jwk), &data) + if err != nil { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + + return + } + c.JSON(200, gin.H{ "keys": []map[string]string{ data, diff --git a/server/resolvers/env.go b/server/resolvers/env.go index ab4769f..917bee6 100644 --- a/server/resolvers/env.go +++ b/server/resolvers/env.go @@ -28,6 +28,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) { // get clone of store store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() adminSecret := store.StringEnv[constants.EnvKeyAdminSecret] + clientID := store.StringEnv[constants.EnvKeyClientID] databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL] databaseName := store.StringEnv[constants.EnvKeyDatabaseName] databaseType := store.StringEnv[constants.EnvKeyDatabaseType] @@ -65,9 +66,10 @@ func EnvResolver(ctx context.Context) (*model.Env, error) { res = &model.Env{ AdminSecret: &adminSecret, - DatabaseName: &databaseName, - DatabaseURL: &databaseURL, - DatabaseType: &databaseType, + DatabaseName: databaseName, + DatabaseURL: databaseURL, + DatabaseType: databaseType, + ClientID: clientID, CustomAccessTokenScript: &customAccessTokenScript, SMTPHost: &smtpHost, SMTPPort: &smtpPort, diff --git a/server/resolvers/update_env.go b/server/resolvers/update_env.go index 9791314..b4afc20 100644 --- a/server/resolvers/update_env.go +++ b/server/resolvers/update_env.go @@ -10,6 +10,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" @@ -33,6 +34,66 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model return res, fmt.Errorf("unauthorized") } + updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + + isJWTUpdated := false + algo := updatedData.StringEnv[constants.EnvKeyJwtType] + if params.JwtType != nil { + algo = *params.JwtType + if !crypto.IsHMACA(algo) && !crypto.IsECDSA(algo) && !crypto.IsRSA(algo) { + return res, fmt.Errorf("invalid jwt type") + } + + updatedData.StringEnv[constants.EnvKeyJwtType] = algo + isJWTUpdated = true + } + + if params.JwtSecret != nil || params.JwtPublicKey != nil || params.JwtPrivateKey != nil { + isJWTUpdated = true + } + + if isJWTUpdated { + // check if jwt secret is provided + if crypto.IsHMACA(algo) { + if params.JwtSecret == nil { + return res, fmt.Errorf("jwt secret is required for HMAC algorithm") + } + } + + if crypto.IsRSA(algo) { + if params.JwtPrivateKey == nil || params.JwtPublicKey == nil { + return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm") + } + + _, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey) + if err != nil { + return res, err + } + + _, err := crypto.ParseRsaPublicKeyFromPemStr(*params.JwtPublicKey) + if err != nil { + return res, err + } + } + + if crypto.IsECDSA(algo) { + if params.JwtPrivateKey == nil || params.JwtPublicKey == nil { + return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm") + } + + _, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey) + if err != nil { + return res, err + } + + _, err := crypto.ParseEcdsaPublicKeyFromPemStr(*params.JwtPublicKey) + if err != nil { + return res, err + } + } + + } + var data map[string]interface{} byteData, err := json.Marshal(params) if err != nil { @@ -61,7 +122,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model } - updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() for key, value := range data { if value != nil { fieldType := reflect.TypeOf(value).String() @@ -117,8 +177,20 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model // Update local store envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData) - sessionstore.InitSession() - oauth.InitOAuth() + jwk, err := crypto.GenerateJWKBasedOnEnv() + if err != nil { + return res, err + } + // updating jwk + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) + err = sessionstore.InitSession() + if err != nil { + return res, err + } + err = oauth.InitOAuth() + if err != nil { + return res, err + } // Fetch the current db store and update it env, err := db.Provider.GetEnv() diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index a1d7e3c..70879dd 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -33,6 +33,7 @@ func TestResolvers(t *testing.T) { envData.EnvData = "" db.Provider.UpdateEnv(envData) } + env.InitAllEnv() env.PersistEnv() t.Run("should pass tests for "+dbType, func(t *testing.T) { diff --git a/server/utils/meta.go b/server/utils/meta.go index c94f0ff..c9a82aa 100644 --- a/server/utils/meta.go +++ b/server/utils/meta.go @@ -10,6 +10,7 @@ import ( func GetMetaInfo() model.Meta { return model.Meta{ Version: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyVersion), + ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), IsGoogleLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "", IsGithubLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "", IsFacebookLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret) != "", From df1c56bb1ca41ebb6e2d6972083583621269cc59 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 28 Feb 2022 07:55:01 +0530 Subject: [PATCH 07/41] fix: tests --- server/cookie/admin_cookie.go | 6 +- server/cookie/cookie.go | 24 ++++---- server/crypto/common.go | 12 ++-- server/crypto/ecdsa.go | 13 +++- server/db/db.go | 6 +- server/db/providers/arangodb/arangodb.go | 10 ++-- server/db/providers/arangodb/session.go | 1 - server/db/providers/arangodb/user.go | 2 +- server/db/providers/mongodb/mongodb.go | 4 +- server/db/providers/mongodb/user.go | 2 +- server/db/providers/sql/sql.go | 10 ++-- server/db/providers/sql/user.go | 2 +- server/email/email.go | 8 +-- server/email/forgot_password_email.go | 8 +-- server/email/verification_email.go | 4 +- server/env/env.go | 19 +++--- server/env/persist_env.go | 12 ++-- server/envstore/store.go | 28 +++++---- server/handlers/app.go | 6 +- server/handlers/dashboard.go | 2 +- server/handlers/jwks.go | 2 +- server/handlers/oauth_callback.go | 4 +- server/handlers/oauth_login.go | 4 +- server/handlers/openid_config.go | 2 +- server/main.go | 4 +- server/oauth/oauth.go | 18 +++--- server/resolvers/admin_login.go | 2 +- server/resolvers/admin_session.go | 2 +- server/resolvers/admin_signup.go | 6 +- server/resolvers/env.go | 2 +- server/resolvers/forgot_password.go | 2 +- server/resolvers/is_valid_jwt.go | 2 +- server/resolvers/login.go | 4 +- server/resolvers/magic_link_login.go | 10 ++-- server/resolvers/reset_password.go | 2 +- server/resolvers/signup.go | 10 ++-- server/resolvers/update_env.go | 10 ++-- server/resolvers/update_user.go | 2 +- server/resolvers/verify_email.go | 4 +- server/sessionstore/session.go | 6 +- server/test/admin_login_test.go | 2 +- server/test/admin_logout_test.go | 4 +- server/test/admin_session_test.go | 4 +- server/test/admin_signup_test.go | 2 +- server/test/delete_user_test.go | 4 +- server/test/env_file_test.go | 6 +- server/test/env_test.go | 6 +- server/test/jwt_test.go | 73 +++++++++++++++-------- server/test/login_test.go | 5 +- server/test/logout_test.go | 2 +- server/test/magic_link_login_test.go | 2 +- server/test/profile_test.go | 2 +- server/test/resolvers_test.go | 21 +++---- server/test/session_test.go | 2 +- server/test/test.go | 16 ++--- server/test/update_env_test.go | 12 ++-- server/test/update_profile_test.go | 2 +- server/test/update_user_test.go | 4 +- server/test/users_test.go | 6 +- server/test/validator_test.go | 4 +- server/test/verification_requests_test.go | 4 +- server/token/admin_token.go | 6 +- server/token/auth_token.go | 4 +- server/token/jwt.go | 18 +++--- server/token/verification_token.go | 2 +- server/utils/crypto.go | 10 ++-- server/utils/meta.go | 16 ++--- server/utils/urls.go | 2 - server/utils/validator.go | 2 +- 69 files changed, 284 insertions(+), 236 deletions(-) diff --git a/server/cookie/admin_cookie.go b/server/cookie/admin_cookie.go index 4c6bc26..58f2c56 100644 --- a/server/cookie/admin_cookie.go +++ b/server/cookie/admin_cookie.go @@ -16,12 +16,12 @@ func SetAdminCookie(gc *gin.Context, token string) { hostname := utils.GetHost(gc) host, _ := utils.GetHostParts(hostname) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly) } // GetAdminCookie gets the admin cookie from the request func GetAdminCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName)) + cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName)) if err != nil { return "", err } @@ -42,5 +42,5 @@ func DeleteAdminCookie(gc *gin.Context) { hostname := utils.GetHost(gc) host, _ := utils.GetHostParts(hostname) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly) } diff --git a/server/cookie/cookie.go b/server/cookie/cookie.go index 7a59bae..ad9fca8 100644 --- a/server/cookie/cookie.go +++ b/server/cookie/cookie.go @@ -31,23 +31,23 @@ func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash strin gc.SetSameSite(http.SameSiteNoneMode) // set cookie for host - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly) // in case of subdomain, set cookie for domain - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly) // set finger print cookie (this should be accessed via cookie only) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly) // set refresh token cookie (this should be accessed via cookie only) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly) } // GetAccessTokenCookie to get access token cookie from the request func GetAccessTokenCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token") + cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token") if err != nil { - cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain") + cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain") if err != nil { return "", err } @@ -58,7 +58,7 @@ func GetAccessTokenCookie(gc *gin.Context) (string, error) { // GetRefreshTokenCookie to get refresh token cookie func GetRefreshTokenCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token") + cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token") if err != nil { return "", err } @@ -68,7 +68,7 @@ func GetRefreshTokenCookie(gc *gin.Context) (string, error) { // GetFingerPrintCookie to get fingerprint cookie func GetFingerPrintCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint") + cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint") if err != nil { return "", err } @@ -95,8 +95,8 @@ func DeleteCookie(gc *gin.Context) { } gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly) } diff --git a/server/crypto/common.go b/server/crypto/common.go index 69ab530..698917a 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -31,22 +31,24 @@ func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) { } // GenerateJWKBasedOnEnv generates JWK based on env +// make sure clientID, jwtType, jwtSecret / public & private key pair is set +// this is called while initializing app / when env is updated func GenerateJWKBasedOnEnv() (string, error) { jwk := "" - algo := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) - clientID := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) + algo := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) var err error // check if jwt secret is provided if IsHMACA(algo) { - jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret))) + jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret))) if err != nil { return "", err } } if IsRSA(algo) { - publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) + publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) if err != nil { return "", err } @@ -58,7 +60,7 @@ func GenerateJWKBasedOnEnv() (string, error) { } if IsECDSA(algo) { - publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) + publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) if err != nil { return "", err } diff --git a/server/crypto/ecdsa.go b/server/crypto/ecdsa.go index 3b8e4b6..dbd8d89 100644 --- a/server/crypto/ecdsa.go +++ b/server/crypto/ecdsa.go @@ -12,7 +12,18 @@ import ( // NewECDSAKey to generate new ECDSA Key if env is not set // returns key instance, private key string, public key string, jwk string, error func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) { - key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + var curve elliptic.Curve + switch algo { + case "ES256": + curve = elliptic.P256() + case "ES384": + curve = elliptic.P384() + case "ES512": + curve = elliptic.P521() + default: + return nil, "", "", "", errors.New("Invalid algo") + } + key, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { return nil, "", "", "", err } diff --git a/server/db/db.go b/server/db/db.go index 9890878..17fdbec 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -15,9 +15,9 @@ var Provider providers.Provider func InitDB() error { var err error - isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb - isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb - isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb + isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb + isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb + isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb if isSQL { Provider, err = sql.NewProvider() diff --git a/server/db/providers/arangodb/arangodb.go b/server/db/providers/arangodb/arangodb.go index 9c9bff8..9866ed2 100644 --- a/server/db/providers/arangodb/arangodb.go +++ b/server/db/providers/arangodb/arangodb.go @@ -24,7 +24,7 @@ type provider struct { func NewProvider() (*provider, error) { ctx := context.Background() conn, err := http.NewConnection(http.ConnectionConfig{ - Endpoints: []string{envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)}, + Endpoints: []string{envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)}, }) if err != nil { return nil, err @@ -39,16 +39,16 @@ func NewProvider() (*provider, error) { var arangodb driver.Database - arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) + arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) if arangodb_exists { - log.Println(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already") - arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) + log.Println(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already") + arangodb, err = arangoClient.Database(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) if err != nil { return nil, err } } else { - arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil) + arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil) if err != nil { return nil, err } diff --git a/server/db/providers/arangodb/session.go b/server/db/providers/arangodb/session.go index 9cfc8be..2623a1a 100644 --- a/server/db/providers/arangodb/session.go +++ b/server/db/providers/arangodb/session.go @@ -34,7 +34,6 @@ func (p *provider) DeleteSession(userId string) error { } cursor, err := p.db.Query(nil, query, bindVars) if err != nil { - log.Println("=> error deleting arangodb session:", err) return err } defer cursor.Close() diff --git a/server/db/providers/arangodb/user.go b/server/db/providers/arangodb/user.go index 6cc4cb6..fb011b3 100644 --- a/server/db/providers/arangodb/user.go +++ b/server/db/providers/arangodb/user.go @@ -23,7 +23,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) { } if user.Roles == "" { - user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") + user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } user.CreatedAt = time.Now().Unix() diff --git a/server/db/providers/mongodb/mongodb.go b/server/db/providers/mongodb/mongodb.go index b53b276..d29fca1 100644 --- a/server/db/providers/mongodb/mongodb.go +++ b/server/db/providers/mongodb/mongodb.go @@ -19,7 +19,7 @@ type provider struct { // NewProvider to initialize mongodb connection func NewProvider() (*provider, error) { - mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)) + mongodbOptions := options.Client().ApplyURI(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)) maxWait := time.Duration(5 * time.Second) mongodbOptions.ConnectTimeout = &maxWait mongoClient, err := mongo.NewClient(mongodbOptions) @@ -37,7 +37,7 @@ func NewProvider() (*provider, error) { return nil, err } - mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database()) + mongodb := mongoClient.Database(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database()) mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection()) userCollection := mongodb.Collection(models.Collections.User, options.Collection()) diff --git a/server/db/providers/mongodb/user.go b/server/db/providers/mongodb/user.go index 93c38d0..28d7d44 100644 --- a/server/db/providers/mongodb/user.go +++ b/server/db/providers/mongodb/user.go @@ -21,7 +21,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) { } if user.Roles == "" { - user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") + user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } user.CreatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix() diff --git a/server/db/providers/sql/sql.go b/server/db/providers/sql/sql.go index 580c7b3..28af527 100644 --- a/server/db/providers/sql/sql.go +++ b/server/db/providers/sql/sql.go @@ -41,15 +41,15 @@ func NewProvider() (*provider, error) { TablePrefix: models.Prefix, }, } - switch envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) { + switch envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) { case constants.DbTypePostgres, constants.DbTypeYugabyte: - sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) + sqlDB, err = gorm.Open(postgres.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) case constants.DbTypeSqlite: - sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) + sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) case constants.DbTypeMysql, constants.DbTypeMariaDB: - sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) + sqlDB, err = gorm.Open(mysql.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) case constants.DbTypeSqlserver: - sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) + sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) } if err != nil { diff --git a/server/db/providers/sql/user.go b/server/db/providers/sql/user.go index 0a881dd..7d0049a 100644 --- a/server/db/providers/sql/user.go +++ b/server/db/providers/sql/user.go @@ -20,7 +20,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) { } if user.Roles == "" { - user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") + user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } user.CreatedAt = time.Now().Unix() diff --git a/server/email/email.go b/server/email/email.go index 5a76a43..2c57c3c 100644 --- a/server/email/email.go +++ b/server/email/email.go @@ -32,13 +32,13 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s // SendMail function to send mail func SendMail(to []string, Subject, bodyMessage string) error { m := gomail.NewMessage() - m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)) + m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)) m.SetHeader("To", to...) m.SetHeader("Subject", Subject) m.SetBody("text/html", bodyMessage) - port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort)) - d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword)) - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" { + port, _ := strconv.Atoi(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort)) + d := gomail.NewDialer(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword)) + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" { d.TLSConfig = &tls.Config{InsecureSkipVerify: true} } if err := d.DialAndSend(m); err != nil { diff --git a/server/email/forgot_password_email.go b/server/email/forgot_password_email.go index ddc3c12..1e06437 100644 --- a/server/email/forgot_password_email.go +++ b/server/email/forgot_password_email.go @@ -7,9 +7,9 @@ import ( // SendForgotPasswordMail to send forgot password email func SendForgotPasswordMail(toEmail, token, hostname string) error { - resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) + resetPasswordUrl := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) if resetPasswordUrl == "" { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password") } // The receiver needs to be in slice as the receive supports multiple receiver @@ -103,8 +103,8 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error { ` data := make(map[string]interface{}, 3) - data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) - data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) + data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) + data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) data["verification_url"] = resetPasswordUrl + "?token=" + token message = addEmailTemplate(message, data, "reset_password_email.tmpl") diff --git a/server/email/verification_email.go b/server/email/verification_email.go index 27fff46..cc6bb54 100644 --- a/server/email/verification_email.go +++ b/server/email/verification_email.go @@ -97,8 +97,8 @@ func SendVerificationMail(toEmail, token, hostname string) error { ` data := make(map[string]interface{}, 3) - data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) - data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) + data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) + data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) data["verification_url"] = hostname + "/verify_email?token=" + token message = addEmailTemplate(message, data, "verify_email.tmpl") // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) diff --git a/server/env/env.go b/server/env/env.go index 05e72d5..173daa7 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -20,7 +20,10 @@ func InitRequiredEnv() error { envPath := os.Getenv(constants.EnvKeyEnvPath) if envPath == "" { - envPath = `.env` + envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath) + if envPath == "" { + envPath = `.env` + } } if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" { @@ -46,7 +49,7 @@ func InitRequiredEnv() error { } } - if strings.TrimSpace(dbURL) == "" { + 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) } @@ -62,10 +65,10 @@ func InitRequiredEnv() error { } } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName) + 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) return nil } @@ -75,7 +78,7 @@ func InitAllEnv() error { if err != nil { log.Println("No env data found in db, using local clone of env data") // get clone of current store - envData = envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + envData = envstore.EnvStoreObj.GetEnvStoreClone() } clientID := envData.StringEnv[constants.EnvKeyClientID] @@ -362,6 +365,6 @@ func InitAllEnv() error { envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo) } - envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData) + envstore.EnvStoreObj.UpdateEnvStore(envData) return nil } diff --git a/server/env/persist_env.go b/server/env/persist_env.go index caf3346..f00ea04 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -32,7 +32,7 @@ func GetEnvData() (envstore.Store, error) { return result, err } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) b64DecryptedConfig, err := utils.DecryptB64(env.EnvData) if err != nil { return result, err @@ -58,10 +58,10 @@ func PersistEnv() error { if err != nil { // AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid hash := uuid.New().String()[:36-4] - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash) encodedHash := utils.EncryptB64(hash) - encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) + encryptedConfig, err := utils.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone()) if err != nil { return err } @@ -84,7 +84,7 @@ func PersistEnv() error { return err } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) b64DecryptedConfig, err := utils.DecryptB64(env.EnvData) if err != nil { return err @@ -163,13 +163,13 @@ func PersistEnv() error { hasChanged = true } } - envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData) + envstore.EnvStoreObj.UpdateEnvStore(storeData) jwk, err := crypto.GenerateJWKBasedOnEnv() if err != nil { return err } // updating jwk - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) if hasChanged { encryptedConfig, err := utils.EncryptEnvData(storeData) diff --git a/server/envstore/store.go b/server/envstore/store.go index 908b448..c7cb122 100644 --- a/server/envstore/store.go +++ b/server/envstore/store.go @@ -22,14 +22,13 @@ type Store struct { SliceEnv map[string][]string `json:"slice_env"` } -// EnvInMemoryStore struct -type EnvInMemoryStore struct { +// EnvStore struct +type EnvStore struct { mutex sync.Mutex store *Store } -// EnvInMemoryStoreObj global variable for EnvInMemoryStore -var EnvInMemoryStoreObj = &EnvInMemoryStore{ +var defaultStore = &EnvStore{ store: &Store{ StringEnv: map[string]string{ constants.EnvKeyAdminCookieName: "authorizer-admin", @@ -47,8 +46,11 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{ }, } +// EnvStoreObj.GetBoolStoreEnvVariable global variable for EnvStore +var EnvStoreObj = defaultStore + // UpdateEnvStore to update the whole env store object -func (e *EnvInMemoryStore) UpdateEnvStore(store Store) { +func (e *EnvStore) UpdateEnvStore(store Store) { e.mutex.Lock() defer e.mutex.Unlock() // just override the keys + new keys @@ -67,7 +69,7 @@ func (e *EnvInMemoryStore) UpdateEnvStore(store Store) { } // UpdateEnvVariable to update the particular env variable -func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) { +func (e *EnvStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) { e.mutex.Lock() defer e.mutex.Unlock() switch storeIdentifier { @@ -81,31 +83,37 @@ func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value } // GetStringStoreEnvVariable to get the env variable from string store object -func (e *EnvInMemoryStore) GetStringStoreEnvVariable(key string) string { +func (e *EnvStore) GetStringStoreEnvVariable(key string) string { // e.mutex.Lock() // defer e.mutex.Unlock() return e.store.StringEnv[key] } // GetBoolStoreEnvVariable to get the env variable from bool store object -func (e *EnvInMemoryStore) GetBoolStoreEnvVariable(key string) bool { +func (e *EnvStore) GetBoolStoreEnvVariable(key string) bool { // e.mutex.Lock() // defer e.mutex.Unlock() return e.store.BoolEnv[key] } // GetSliceStoreEnvVariable to get the env variable from slice store object -func (e *EnvInMemoryStore) GetSliceStoreEnvVariable(key string) []string { +func (e *EnvStore) GetSliceStoreEnvVariable(key string) []string { // e.mutex.Lock() // defer e.mutex.Unlock() return e.store.SliceEnv[key] } // GetEnvStoreClone to get clone of current env store object -func (e *EnvInMemoryStore) GetEnvStoreClone() Store { +func (e *EnvStore) GetEnvStoreClone() Store { e.mutex.Lock() defer e.mutex.Unlock() result := *e.store return result } + +func (e *EnvStore) ResetStore() { + e.mutex.Lock() + defer e.mutex.Unlock() + e.store = defaultStore.store +} diff --git a/server/handlers/app.go b/server/handlers/app.go index 563e502..61325a9 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -23,7 +23,7 @@ type State struct { func AppHandler() gin.HandlerFunc { return func(c *gin.Context) { hostname := utils.GetHost(c) - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) { c.JSON(400, gin.H{"error": "login page is not enabled"}) return } @@ -79,8 +79,8 @@ func AppHandler() gin.HandlerFunc { "data": map[string]string{ "authorizerURL": stateObj.AuthorizerURL, "redirectURL": stateObj.RedirectURL, - "organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), - "organizationLogo": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), + "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), + "organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), }, }) } diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go index 5e54513..7eb7dce 100644 --- a/server/handlers/dashboard.go +++ b/server/handlers/dashboard.go @@ -13,7 +13,7 @@ func DashboardHandler() gin.HandlerFunc { return func(c *gin.Context) { isOnboardingCompleted := false - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" { + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" { isOnboardingCompleted = true } diff --git a/server/handlers/jwks.go b/server/handlers/jwks.go index b74c6fa..cbcec38 100644 --- a/server/handlers/jwks.go +++ b/server/handlers/jwks.go @@ -11,7 +11,7 @@ import ( func JWKsHandler() gin.HandlerFunc { return func(c *gin.Context) { var data map[string]string - jwk := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK) + jwk := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK) err := json.Unmarshal([]byte(jwk), &data) if err != nil { c.JSON(500, gin.H{ diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 50d783d..5ba684a 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -74,7 +74,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { // make sure inputRoles don't include protected roles hasProtectedRole := false for _, ir := range inputRoles { - if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) { + if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) { hasProtectedRole = true } } @@ -122,7 +122,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { // check if it contains protected unassigned role hasProtectedRole := false for _, ur := range unasignedRoles { - if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { + if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { hasProtectedRole = true } } diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index f23547e..a01a300 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -33,14 +33,14 @@ func OAuthLoginHandler() gin.HandlerFunc { // use protected roles verification for admin login only. // though if not associated with user, it will be rejected from oauth_callback - if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) { + if !utils.IsValidRoles(append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) { c.JSON(400, gin.H{ "error": "invalid role", }) return } } else { - roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") + roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } uuid := uuid.New() diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index eac91ca..de41ed6 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -12,7 +12,7 @@ import ( func OpenIDConfigurationHandler() gin.HandlerFunc { return func(c *gin.Context) { issuer := utils.GetHost(c) - jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) c.JSON(200, gin.H{ "issuer": issuer, diff --git a/server/main.go b/server/main.go index 89b8fc0..4af8299 100644 --- a/server/main.go +++ b/server/main.go @@ -21,7 +21,7 @@ func main() { envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") flag.Parse() - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION) // initialize required envs (mainly db & env file path) err := env.InitRequiredEnv() @@ -61,5 +61,5 @@ func main() { } router := routes.InitRouter() - router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort)) + router.Run(":" + envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort)) } diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index 5d79125..7e7c2d7 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -33,32 +33,32 @@ var ( // InitOAuth initializes the OAuth providers based on EnvData func InitOAuth() error { ctx := context.Background() - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" { + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" { p, err := oidc.NewProvider(ctx, "https://accounts.google.com") if err != nil { return err } OIDCProviders.GoogleOIDC = p OAuthProviders.GoogleConfig = &oauth2.Config{ - ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID), - ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret), + ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID), + ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret), RedirectURL: "/oauth_callback/google", Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } } - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" { + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" { OAuthProviders.GithubConfig = &oauth2.Config{ - ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID), - ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret), + ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID), + ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret), RedirectURL: "/oauth_callback/github", Endpoint: githubOAuth2.Endpoint, } } - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" { + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" { OAuthProviders.FacebookConfig = &oauth2.Config{ - ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID), - ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret), + ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID), + ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret), RedirectURL: "/oauth_callback/facebook", Endpoint: facebookOAuth2.Endpoint, Scopes: []string{"public_profile", "email"}, diff --git a/server/resolvers/admin_login.go b/server/resolvers/admin_login.go index 67f582c..306a214 100644 --- a/server/resolvers/admin_login.go +++ b/server/resolvers/admin_login.go @@ -20,7 +20,7 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod return res, err } - adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) if params.AdminSecret != adminSecret { return res, fmt.Errorf(`invalid admin secret`) } diff --git a/server/resolvers/admin_session.go b/server/resolvers/admin_session.go index 1b8baee..f464aee 100644 --- a/server/resolvers/admin_session.go +++ b/server/resolvers/admin_session.go @@ -25,7 +25,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) { return res, fmt.Errorf("unauthorized") } - hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + hashedKey, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) if err != nil { return res, err } diff --git a/server/resolvers/admin_signup.go b/server/resolvers/admin_signup.go index 231b21a..8fa00d7 100644 --- a/server/resolvers/admin_signup.go +++ b/server/resolvers/admin_signup.go @@ -33,18 +33,18 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) if adminSecret != "" { err = fmt.Errorf("admin sign up already completed") return res, err } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret) // consvert EnvData to JSON var storeData envstore.Store - jsonBytes, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) + jsonBytes, err := json.Marshal(envstore.EnvStoreObj.GetEnvStoreClone()) if err != nil { return res, err } diff --git a/server/resolvers/env.go b/server/resolvers/env.go index 917bee6..af04c3c 100644 --- a/server/resolvers/env.go +++ b/server/resolvers/env.go @@ -26,7 +26,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) { } // get clone of store - store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + store := envstore.EnvStoreObj.GetEnvStoreClone() adminSecret := store.StringEnv[constants.EnvKeyAdminSecret] clientID := store.StringEnv[constants.EnvKeyClientID] databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL] diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index fd53320..2ca5b52 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -24,7 +24,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu if err != nil { return res, err } - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } params.Email = strings.ToLower(params.Email) diff --git a/server/resolvers/is_valid_jwt.go b/server/resolvers/is_valid_jwt.go index f9e33e3..d2bd6bf 100644 --- a/server/resolvers/is_valid_jwt.go +++ b/server/resolvers/is_valid_jwt.go @@ -31,7 +31,7 @@ func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) return nil, err } - claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{}) + claimRoleInterface := claims[envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{}) claimRoles := []string{} for _, v := range claimRoleInterface { claimRoles = append(claimRoles, v.(string)) diff --git a/server/resolvers/login.go b/server/resolvers/login.go index cd74f07..76f31d7 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -25,7 +25,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes return res, err } - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } @@ -49,7 +49,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes log.Println("compare password error:", err) return res, fmt.Errorf(`invalid password`) } - roles := envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) + roles := envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) currentRoles := strings.Split(user.Roles, ",") if len(params.Roles) > 0 { if !utils.IsValidRoles(currentRoles, params.Roles) { diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index 8455b56..5ed1801 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -25,7 +25,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu return res, err } - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) { return res, fmt.Errorf(`magic link login is disabled for this instance`) } @@ -49,13 +49,13 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu // define roles for new user if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { + if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) + inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) } user.Roles = strings.Join(inputRoles, ",") @@ -80,7 +80,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu // check if it contains protected unassigned role hasProtectedRole := false for _, ur := range unasignedRoles { - if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { + if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { hasProtectedRole = true } } @@ -107,7 +107,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu } hostname := utils.GetHost(gc) - if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { + if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request verificationType := constants.VerificationTypeMagicLinkLogin verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index d48ae4b..843370f 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -17,7 +17,7 @@ import ( // ResetPasswordResolver is a resolver for reset password mutation func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { var res *model.Response - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index adeb20f..b227797 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -27,7 +27,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } if params.ConfirmPassword != params.Password { @@ -57,13 +57,13 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { + if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) + inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) } user := models.User{ @@ -108,7 +108,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR } user.SignupMethods = constants.SignupMethodBasicAuth - if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { + if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { now := time.Now().Unix() user.EmailVerifiedAt = &now } @@ -120,7 +120,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR userToReturn := user.AsAPIUser() hostname := utils.GetHost(gc) - if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { + if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request verificationType := constants.VerificationTypeBasicAuthSignup verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) diff --git a/server/resolvers/update_env.go b/server/resolvers/update_env.go index b4afc20..317bd88 100644 --- a/server/resolvers/update_env.go +++ b/server/resolvers/update_env.go @@ -34,7 +34,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model return res, fmt.Errorf("unauthorized") } - updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + updatedData := envstore.EnvStoreObj.GetEnvStoreClone() isJWTUpdated := false algo := updatedData.StringEnv[constants.EnvKeyJwtType] @@ -111,7 +111,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model return res, errors.New("admin secret and old admin secret are required for secret change") } - if *params.OldAdminSecret != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) { + if *params.OldAdminSecret != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) { return res, errors.New("old admin secret is not correct") } @@ -176,13 +176,13 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model } // Update local store - envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData) + envstore.EnvStoreObj.UpdateEnvStore(updatedData) jwk, err := crypto.GenerateJWKBasedOnEnv() if err != nil { return res, err } // updating jwk - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) err = sessionstore.InitSession() if err != nil { return res, err @@ -199,7 +199,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model } if params.AdminSecret != nil { - hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + hashedKey, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) if err != nil { return res, err } diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index efbd280..40b4206 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -128,7 +128,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod inputRoles = append(inputRoles, *item) } - if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) { + if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) { return res, fmt.Errorf("invalid list of roles") } diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 883ba08..c244992 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -24,13 +24,13 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m verificationRequest, err := db.Provider.GetVerificationRequestByToken(params.Token) if err != nil { - return res, fmt.Errorf(`invalid token`) + return res, fmt.Errorf(`invalid token: %s`, err.Error()) } // verify if token exists in db claim, err := token.ParseJWTToken(params.Token) if err != nil { - return res, fmt.Errorf(`invalid token`) + return res, fmt.Errorf(`invalid token: %s`, err.Error()) } user, err := db.Provider.GetUserByEmail(claim["email"].(string)) diff --git a/server/sessionstore/session.go b/server/sessionstore/session.go index 0287a46..f3fdf39 100644 --- a/server/sessionstore/session.go +++ b/server/sessionstore/session.go @@ -120,10 +120,10 @@ func RemoveSocialLoginState(key string) { // InitializeSessionStore initializes the SessionStoreObj based on environment variables func InitSession() error { - if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" { + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" { log.Println("using redis store to save sessions") - redisURL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) + redisURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) redisURLHostPortsList := strings.Split(redisURL, ",") if len(redisURLHostPortsList) > 1 { @@ -151,7 +151,7 @@ func InitSession() error { return nil } - opt, err := redis.ParseURL(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) + opt, err := redis.ParseURL(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)) if err != nil { return err } diff --git a/server/test/admin_login_test.go b/server/test/admin_login_test.go index 63307c1..cf949f8 100644 --- a/server/test/admin_login_test.go +++ b/server/test/admin_login_test.go @@ -21,7 +21,7 @@ func adminLoginTests(t *testing.T, s TestSetup) { assert.NotNil(t, err) _, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ - AdminSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret), + AdminSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret), }) assert.Nil(t, err) diff --git a/server/test/admin_logout_test.go b/server/test/admin_logout_test.go index 3a885c1..d322578 100644 --- a/server/test/admin_logout_test.go +++ b/server/test/admin_logout_test.go @@ -18,9 +18,9 @@ func adminLogoutTests(t *testing.T, s TestSetup) { _, err := resolvers.AdminLogoutResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.AdminLogoutResolver(ctx) assert.Nil(t, err) diff --git a/server/test/admin_session_test.go b/server/test/admin_session_test.go index 8b28cdf..8954f08 100644 --- a/server/test/admin_session_test.go +++ b/server/test/admin_session_test.go @@ -18,9 +18,9 @@ func adminSessionTests(t *testing.T, s TestSetup) { _, err := resolvers.AdminSessionResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.AdminSessionResolver(ctx) assert.Nil(t, err) diff --git a/server/test/admin_signup_test.go b/server/test/admin_signup_test.go index 6ef545c..c478475 100644 --- a/server/test/admin_signup_test.go +++ b/server/test/admin_signup_test.go @@ -20,7 +20,7 @@ func adminSignupTests(t *testing.T, s TestSetup) { assert.NotNil(t, err) // reset env for test to pass - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "") _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ AdminSecret: "admin123", diff --git a/server/test/delete_user_test.go b/server/test/delete_user_test.go index 398e33e..4c385a3 100644 --- a/server/test/delete_user_test.go +++ b/server/test/delete_user_test.go @@ -28,9 +28,9 @@ func deleteUserTest(t *testing.T, s TestSetup) { }) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{ Email: email, diff --git a/server/test/env_file_test.go b/server/test/env_file_test.go index 50f8e74..75cc498 100644 --- a/server/test/env_file_test.go +++ b/server/test/env_file_test.go @@ -10,15 +10,15 @@ import ( ) func TestEnvs(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") env.InitAllEnv() - store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + store := envstore.EnvStoreObj.GetEnvStoreClone() assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production") assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification]) assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin]) assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication]) - assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256") + assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "RS256") assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role") assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"}) assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"}) diff --git a/server/test/env_test.go b/server/test/env_test.go index 9505251..19707a3 100644 --- a/server/test/env_test.go +++ b/server/test/env_test.go @@ -18,12 +18,12 @@ func envTests(t *testing.T, s TestSetup) { _, err := resolvers.EnvResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) res, err := resolvers.EnvResolver(ctx) assert.Nil(t, err) - assert.Equal(t, *res.AdminSecret, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + assert.Equal(t, *res.AdminSecret, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) }) } diff --git a/server/test/jwt_test.go b/server/test/jwt_test.go index 185e028..a72cedf 100644 --- a/server/test/jwt_test.go +++ b/server/test/jwt_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/token" "github.com/golang-jwt/jwt" @@ -12,24 +13,28 @@ import ( ) func TestJwt(t *testing.T) { + // persist older data till test is done and then reset it + jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey) + privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey) + clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) claims := jwt.MapClaims{ "exp": time.Now().Add(time.Minute * 30).Unix(), "iat": time.Now().Unix(), "email": "test@yopmail.com", + "sub": "test", + "aud": clientID, } - // persist older data till test is done and then reset it - jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) - jwtSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret) - t.Run("invalid jwt type", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "invalid") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "invalid") token, err := token.SignJWTToken(claims) assert.Error(t, err, "unsupported signing method") assert.Empty(t, token) }) t.Run("expired jwt token", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test") expiredClaims := jwt.MapClaims{ "exp": time.Now().Add(-time.Minute * 30).Unix(), "iat": time.Now().Unix(), @@ -41,8 +46,9 @@ func TestJwt(t *testing.T) { assert.Error(t, err, err.Error(), "Token is expired") }) t.Run("HMAC algorithms", func(t *testing.T) { + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test") t.Run("HS256", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256") jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -51,7 +57,7 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("HS384", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS384") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS384") jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -60,7 +66,7 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("HS512", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS512") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS512") jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -71,10 +77,12 @@ func TestJwt(t *testing.T) { }) t.Run("RSA algorithms", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgHUQac/v0f3c8m4L9BMWfxBiEzkdV5CoaqfxhO5IwAX/1cs0WceN\njM7g/qzC7YmEOSiYqupiRtsyn6riz0xT/VUg4uv1uZ/muC6EVfOjR5Ack3Brquql\nD+oMxN4CeA0Wzp2dEV4N3Gv7wWHdhg9ZSc4g6+ZUdlkhIPfeO9RNK9pPAgMBAAEC\ngYBqLrIbp0dNQn0vbm48ZhppDNys4L2NfAYKQZs23Aw5JN6Si/CnffBrsk+u+ryl\nEKcb+KaHJQ9qQdfsFAC+FizhMQy0Dq9yw6shnqHX+paB6E6z2/vX8ToPzJRwxBY3\nyuaetCEpSXR7pQEd5YWDTUH7qYnb9FObD+umhVvmlsTHCQJBALagPmexu0DvMXKZ\nWdplik6eXg9lptiuj5MYqitEUyzU9E9HNeHKlZM7szGeWG3jNduoKcyo4M0Flvt9\ncP+soVUCQQCkGOQ5Y3/GoZmclKWMVwqGdmL6wEjhNfg4PRfgUalHBif9Q1KnM8FP\nAvIqIH8bttRfyT185WmaM2gml0ApwF0TAkBVil9QoK4t7xvBKtUsd809n+481gc9\njR4Q70edtoYjBKhejeNOHF7NNPRtNFcFOZybg3v4sc2CGrEqoQoRp+F1AkBeLmMe\nhPrbF/jAI5h4WaSS0/OvExlBGOaj8Hx5pKTRPLlK5I7VpCC4pmoyv3/0ehSd/TQr\nMMhRVlvaeki7Lcq9AkBravJUadVCAIsB6oh03mo8gUFFFqXDyEl6BiJYqrjCQ5wd\nAQYJGbqQvgjPxN9+PTPldDNi6KVXntSg5gF/dA+Z\n-----END RSA PRIVATE KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHUQac/v0f3c8m4L9BMWfxBiEzkd\nV5CoaqfxhO5IwAX/1cs0WceNjM7g/qzC7YmEOSiYqupiRtsyn6riz0xT/VUg4uv1\nuZ/muC6EVfOjR5Ack3BrquqlD+oMxN4CeA0Wzp2dEV4N3Gv7wWHdhg9ZSc4g6+ZU\ndlkhIPfeO9RNK9pPAgMBAAE=\n-----END PUBLIC KEY-----") t.Run("RS256", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS256") + _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS256", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS256") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -83,7 +91,11 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("RS384", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS384") + _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS384") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -92,7 +104,11 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("RS512", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS512") + _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS512") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -104,9 +120,11 @@ func TestJwt(t *testing.T) { t.Run("ECDSA algorithms", func(t *testing.T) { t.Run("ES256", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES256") + _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES256", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES256") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -115,9 +133,11 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("ES384", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p\nE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz\nMIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw\n8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=\n-----END PRIVATE KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES384") + _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES384") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -126,9 +146,11 @@ func TestJwt(t *testing.T) { assert.Equal(t, c["email"].(string), claims["email"]) }) t.Run("ES512", func(t *testing.T) { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga\n9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf\nZ6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN\nv3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear\njMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12\new==\n-----END PRIVATE KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES512") + _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID) + assert.NoError(t, err) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES512") + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey) jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) @@ -138,6 +160,7 @@ func TestJwt(t *testing.T) { }) }) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, jwtType) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, jwtSecret) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, jwtType) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publicKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey) } diff --git a/server/test/login_test.go b/server/test/login_test.go index 4efb76b..4c25f5a 100644 --- a/server/test/login_test.go +++ b/server/test/login_test.go @@ -29,10 +29,11 @@ func loginTests(t *testing.T, s TestSetup) { assert.NotNil(t, err, "should fail because email is not verified") verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup) - resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ + res, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) - + assert.NoError(t, err) + assert.NotNil(t, res) _, err = resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password, diff --git a/server/test/logout_test.go b/server/test/logout_test.go index fbbfed7..3fbab1e 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -41,7 +41,7 @@ func logoutTests(t *testing.T, s TestSetup) { fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint)) token := *verifyRes.AccessToken - cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) + cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) req.Header.Set("Cookie", cookie) _, err = resolvers.LogoutResolver(ctx) diff --git a/server/test/magic_link_login_test.go b/server/test/magic_link_login_test.go index d4d6a18..b42378f 100644 --- a/server/test/magic_link_login_test.go +++ b/server/test/magic_link_login_test.go @@ -29,7 +29,7 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) { }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) _, err = resolvers.ProfileResolver(ctx) assert.Nil(t, err) diff --git a/server/test/profile_test.go b/server/test/profile_test.go index 5a2f370..4801afc 100644 --- a/server/test/profile_test.go +++ b/server/test/profile_test.go @@ -33,7 +33,7 @@ func profileTests(t *testing.T, s TestSetup) { }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) profileRes, err := resolvers.ProfileResolver(ctx) assert.Nil(t, err) diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 70879dd..a39f3b5 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -1,7 +1,6 @@ package test import ( - "log" "testing" "github.com/authorizerdev/authorizer/server/constants" @@ -12,28 +11,30 @@ import ( func TestResolvers(t *testing.T) { databases := map[string]string{ - constants.DbTypeSqlite: "../../data.db", - constants.DbTypeArangodb: "http://localhost:8529", - constants.DbTypeMongodb: "mongodb://localhost:27017", + constants.DbTypeSqlite: "../../data.db", + // constants.DbTypeArangodb: "http://localhost:8529", + // constants.DbTypeMongodb: "mongodb://localhost:27017", } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test") + envstore.EnvStoreObj.ResetStore() + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test") for dbType, dbURL := range databases { - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) s := testSetup() defer s.Server.Close() - db.InitDB() // clean the persisted config for test to use fresh config envData, err := db.Provider.GetEnv() - log.Println("=> envData:", envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) if err == nil { envData.EnvData = "" db.Provider.UpdateEnv(envData) } - env.InitAllEnv() + err = env.InitAllEnv() + if err != nil { + t.Error(err) + } env.PersistEnv() t.Run("should pass tests for "+dbType, func(t *testing.T) { diff --git a/server/test/session_test.go b/server/test/session_test.go index d28f585..e42c99b 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -46,7 +46,7 @@ func sessionTests(t *testing.T, s TestSetup) { fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint)) token := *verifyRes.AccessToken - cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) + cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) req.Header.Set("Cookie", cookie) diff --git a/server/test/test.go b/server/test/test.go index e0ff189..e99b7cd 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -71,13 +71,15 @@ func testSetup() TestSetup { Password: "test", } - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"}) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") + 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"}) + env.InitRequiredEnv() + db.InitDB() env.InitAllEnv() sessionstore.InitSession() diff --git a/server/test/update_env_test.go b/server/test/update_env_test.go index 3aca3fd..8b687e4 100644 --- a/server/test/update_env_test.go +++ b/server/test/update_env_test.go @@ -16,16 +16,16 @@ func updateEnvTests(t *testing.T, s TestSetup) { t.Helper() t.Run(`should update envs`, func(t *testing.T) { req, ctx := createContext(s) - originalAppURL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + originalAppURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) data := model.UpdateEnvInput{} _, err := resolvers.UpdateEnvResolver(ctx, data) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) newURL := "https://test.com" disableLoginPage := true allowedOrigins := []string{"http://localhost:8080"} @@ -37,9 +37,9 @@ func updateEnvTests(t *testing.T, s TestSetup) { _, err = resolvers.UpdateEnvResolver(ctx, data) assert.Nil(t, err) - assert.Equal(t, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL) - assert.True(t, envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage)) - assert.Equal(t, envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins) + assert.Equal(t, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL) + assert.True(t, envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage)) + assert.Equal(t, envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins) disableLoginPage = false data = model.UpdateEnvInput{ diff --git a/server/test/update_profile_test.go b/server/test/update_profile_test.go index 2d0dea2..04d1785 100644 --- a/server/test/update_profile_test.go +++ b/server/test/update_profile_test.go @@ -36,7 +36,7 @@ func updateProfileTests(t *testing.T, s TestSetup) { }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ FamilyName: &fName, }) diff --git a/server/test/update_user_test.go b/server/test/update_user_test.go index 0740af7..cc2333c 100644 --- a/server/test/update_user_test.go +++ b/server/test/update_user_test.go @@ -33,9 +33,9 @@ func updateUserTest(t *testing.T, s TestSetup) { }) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{ ID: user.ID, Roles: newRoles, diff --git a/server/test/users_test.go b/server/test/users_test.go index 0dc6e8e..223cb61 100644 --- a/server/test/users_test.go +++ b/server/test/users_test.go @@ -2,7 +2,6 @@ package test import ( "fmt" - "log" "testing" "github.com/authorizerdev/authorizer/server/constants" @@ -36,13 +35,12 @@ func usersTest(t *testing.T, s TestSetup) { usersRes, err := resolvers.UsersResolver(ctx, pagination) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) usersRes, err = resolvers.UsersResolver(ctx, pagination) assert.Nil(t, err) - log.Println("=> userRes:", usersRes) rLen := len(usersRes.Users) assert.GreaterOrEqual(t, rLen, 1) diff --git a/server/test/validator_test.go b/server/test/validator_test.go index 798bba3..1e6c9e5 100644 --- a/server/test/validator_test.go +++ b/server/test/validator_test.go @@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) { func TestIsValidOrigin(t *testing.T) { // don't use portocal(http/https) for ALLOWED_ORIGINS while testing, // as we trim them off while running the main function - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}) + envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}) assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin") assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin") assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin") @@ -32,7 +32,7 @@ func TestIsValidOrigin(t *testing.T) { assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin") - envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"}) + envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"}) } func TestIsValidIdentifier(t *testing.T) { diff --git a/server/test/verification_requests_test.go b/server/test/verification_requests_test.go index 0374e3c..1e40c22 100644 --- a/server/test/verification_requests_test.go +++ b/server/test/verification_requests_test.go @@ -37,9 +37,9 @@ func verificationRequestsTest(t *testing.T, s TestSetup) { requests, err := resolvers.VerificationRequestsResolver(ctx, pagination) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) requests, err = resolvers.VerificationRequestsResolver(ctx, pagination) assert.Nil(t, err) diff --git a/server/token/admin_token.go b/server/token/admin_token.go index f1780d2..46ce97a 100644 --- a/server/token/admin_token.go +++ b/server/token/admin_token.go @@ -13,7 +13,7 @@ import ( // CreateAdminAuthToken creates the admin token based on secret key func CreateAdminAuthToken(tokenType string, c *gin.Context) (string, error) { - return utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + return utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) } // GetAdminAuthToken helps in getting the admin token from the request cookie @@ -23,7 +23,7 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) { return "", fmt.Errorf("unauthorized") } - err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))) + err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))) if err != nil { return "", fmt.Errorf(`unauthorized`) @@ -41,7 +41,7 @@ func IsSuperAdmin(gc *gin.Context) bool { return false } - return secret == envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + return secret == envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) } return token != "" diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 5906e2d..8f2c7a4 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -95,10 +95,10 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error) var userMap map[string]interface{} json.Unmarshal(userBytes, &userMap) - claimKey := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) + claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) customClaims := jwt.MapClaims{ "iss": "", - "aud": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), + "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), "nonce": "", "sub": user.ID, "exp": expiresAt, diff --git a/server/token/jwt.go b/server/token/jwt.go index ee64695..6631ffe 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -4,13 +4,14 @@ import ( "errors" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/golang-jwt/jwt" ) // SignJWTToken common util to sing jwt token func SignJWTToken(claims jwt.MapClaims) (string, error) { - jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) signingMethod := jwt.GetSigningMethod(jwtType) if signingMethod == nil { return "", errors.New("unsupported signing method") @@ -23,18 +24,19 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) { switch signingMethod { case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512: - return t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret))) + return t.SignedString([]byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret))) case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512: - key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey))) + key, err := crypto.ParseRsaPrivateKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)) if err != nil { return "", err } return t.SignedString(key) case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512: - key, err := jwt.ParseECPrivateKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey))) + key, err := crypto.ParseEcdsaPrivateKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)) if err != nil { return "", err } + return t.SignedString(key) default: return "", errors.New("unsupported signing method") @@ -43,7 +45,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) { // ParseJWTToken common util to parse jwt token func ParseJWTToken(token string) (jwt.MapClaims, error) { - jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) + jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) signingMethod := jwt.GetSigningMethod(jwtType) var err error @@ -52,11 +54,11 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) { switch signingMethod { case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512: _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - return []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)), nil + return []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)), nil }) case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512: _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))) + key, err := crypto.ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) if err != nil { return nil, err } @@ -64,7 +66,7 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) { }) case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512: _, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { - key, err := jwt.ParseECPublicKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))) + key, err := crypto.ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)) if err != nil { return nil, err } diff --git a/server/token/verification_token.go b/server/token/verification_token.go index 8b0bdcc..ca9a64b 100644 --- a/server/token/verification_token.go +++ b/server/token/verification_token.go @@ -16,7 +16,7 @@ func CreateVerificationToken(email, tokenType, hostname string) (string, error) "token_type": tokenType, "email": email, "host": hostname, - "redirect_url": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), + "redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), } return SignJWTToken(claims) diff --git a/server/utils/crypto.go b/server/utils/crypto.go index 483d6be..cd84d33 100644 --- a/server/utils/crypto.go +++ b/server/utils/crypto.go @@ -29,7 +29,7 @@ func DecryptB64(s string) (string, error) { // EncryptAES encrypts data using AES algorithm func EncryptAES(text []byte) ([]byte, error) { - key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) + key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) c, err := aes.NewCipher(key) var res []byte if err != nil { @@ -63,7 +63,7 @@ func EncryptAES(text []byte) ([]byte, error) { // DecryptAES decrypts data using AES algorithm func DecryptAES(ciphertext []byte) ([]byte, error) { - key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) + key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) c, err := aes.NewCipher(key) var res []byte if err != nil { @@ -96,14 +96,14 @@ func EncryptEnvData(data envstore.Store) (string, error) { return "", err } - envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + storeData := envstore.EnvStoreObj.GetEnvStoreClone() - err = json.Unmarshal(jsonBytes, &envStoreObj) + err = json.Unmarshal(jsonBytes, &storeData) if err != nil { return "", err } - configData, err := json.Marshal(envStoreObj) + configData, err := json.Marshal(storeData) if err != nil { return "", err } diff --git a/server/utils/meta.go b/server/utils/meta.go index c9a82aa..916e3f1 100644 --- a/server/utils/meta.go +++ b/server/utils/meta.go @@ -9,13 +9,13 @@ import ( // GetMeta helps in getting the meta data about the deployment from EnvData func GetMetaInfo() model.Meta { return model.Meta{ - Version: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyVersion), - ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), - IsGoogleLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "", - IsGithubLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "", - IsFacebookLoginEnabled: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret) != "", - IsBasicAuthenticationEnabled: !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication), - IsEmailVerificationEnabled: !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification), - IsMagicLinkLoginEnabled: !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin), + Version: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyVersion), + ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), + IsGoogleLoginEnabled: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "", + IsGithubLoginEnabled: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "", + IsFacebookLoginEnabled: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret) != "", + IsBasicAuthenticationEnabled: !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication), + IsEmailVerificationEnabled: !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification), + IsMagicLinkLoginEnabled: !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin), } } diff --git a/server/utils/urls.go b/server/utils/urls.go index f95fc70..64b8406 100644 --- a/server/utils/urls.go +++ b/server/utils/urls.go @@ -1,7 +1,6 @@ package utils import ( - "log" "net/url" "strings" @@ -15,7 +14,6 @@ func GetHost(c *gin.Context) string { scheme = "http" } host := c.Request.Host - log.Println("=> host:", scheme+"://"+host) return scheme + "://" + host } diff --git a/server/utils/validator.go b/server/utils/validator.go index 11fbc32..8ba840f 100644 --- a/server/utils/validator.go +++ b/server/utils/validator.go @@ -17,7 +17,7 @@ func IsValidEmail(email string) bool { // IsValidOrigin validates origin based on ALLOWED_ORIGINS func IsValidOrigin(url string) bool { - allowedOrigins := envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins) + allowedOrigins := envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins) if len(allowedOrigins) == 1 && allowedOrigins[0] == "*" { return true } From 4830a7e9ac2eb59adf4c5bc24fb3057de62d502d Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 28 Feb 2022 13:14:16 +0530 Subject: [PATCH 08/41] feat: add client secret --- server/constants/env.go | 2 ++ server/env/env.go | 7 +++++ server/env/persist_env.go | 2 +- server/graph/generated/generated.go | 49 +++++++++++++++++++++++++++++ server/graph/model/models_gen.go | 1 + server/graph/schema.graphqls | 1 + server/handlers/openid_config.go | 2 +- server/resolvers/env.go | 2 ++ 8 files changed, 64 insertions(+), 2 deletions(-) diff --git a/server/constants/env.go b/server/constants/env.go index c14f232..26eb8bd 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -97,6 +97,8 @@ const ( // Not Exposed Keys // EnvKeyClientID key for env variable CLIENT_ID EnvKeyClientID = "CLIENT_ID" + // EnvKeyClientSecret key for env variable CLIENT_SECRET + EnvKeyClientSecret = "CLIENT_SECRET" // EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY EnvKeyEncryptionKey = "ENCRYPTION_KEY" // EnvKeyJWK key for env variable JWK diff --git a/server/env/env.go b/server/env/env.go index 173daa7..a29914e 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -88,6 +88,13 @@ func InitAllEnv() error { envData.StringEnv[constants.EnvKeyClientID] = clientID } + clientSecret := envData.StringEnv[constants.EnvKeyClientSecret] + // unique client id for each instance + if clientID == "" { + clientSecret = uuid.New().String() + envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret + } + if envData.StringEnv[constants.EnvKeyEnv] == "" { envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv) if envData.StringEnv[constants.EnvKeyEnv] == "" { diff --git a/server/env/persist_env.go b/server/env/persist_env.go index f00ea04..30e93d3 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -110,7 +110,7 @@ func PersistEnv() error { for key, value := range storeData.StringEnv { // don't override unexposed envs - if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyJWK { + if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyClientSecret && key != constants.EnvKeyJWK { // check only for derivative keys // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data // as we have removed it from json diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 8ccae86..39e22a8 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -55,6 +55,7 @@ type ComplexityRoot struct { AllowedOrigins func(childComplexity int) int AppURL func(childComplexity int) int ClientID func(childComplexity int) int + ClientSecret func(childComplexity int) int CookieName func(childComplexity int) int CustomAccessTokenScript func(childComplexity int) int DatabaseName func(childComplexity int) int @@ -290,6 +291,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Env.ClientID(childComplexity), true + case "Env.CLIENT_SECRET": + if e.complexity.Env.ClientSecret == nil { + break + } + + return e.complexity.Env.ClientSecret(childComplexity), true + case "Env.COOKIE_NAME": if e.complexity.Env.CookieName == nil { break @@ -1232,6 +1240,7 @@ type Env { DATABASE_URL: String! DATABASE_TYPE: String! CLIENT_ID: String! + CLIENT_SECRET: String! CUSTOM_ACCESS_TOKEN_SCRIPT: String SMTP_HOST: String SMTP_PORT: String @@ -2055,6 +2064,41 @@ func (ec *executionContext) _Env_CLIENT_ID(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _Env_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Env", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ClientSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -7734,6 +7778,11 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { invalids++ } + case "CLIENT_SECRET": + out.Values[i] = ec._Env_CLIENT_SECRET(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "CUSTOM_ACCESS_TOKEN_SCRIPT": out.Values[i] = ec._Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx, field, obj) case "SMTP_HOST": diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 5db3db7..b5213b2 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -27,6 +27,7 @@ type Env struct { DatabaseURL string `json:"DATABASE_URL"` DatabaseType string `json:"DATABASE_TYPE"` ClientID string `json:"CLIENT_ID"` + ClientSecret string `json:"CLIENT_SECRET"` CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"` SMTPHost *string `json:"SMTP_HOST"` SMTPPort *string `json:"SMTP_PORT"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 7d92c92..cca567e 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -91,6 +91,7 @@ type Env { DATABASE_URL: String! DATABASE_TYPE: String! CLIENT_ID: String! + CLIENT_SECRET: String! CUSTOM_ACCESS_TOKEN_SCRIPT: String SMTP_HOST: String SMTP_PORT: String diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index de41ed6..5b98d03 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -17,7 +17,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { c.JSON(200, gin.H{ "issuer": issuer, "authorization_endpoint": issuer + "/authorize", - "token_endpoint": issuer + "/oauth/token", + "token_endpoint": issuer + "/token", "userinfo_endpoint": issuer + "/userinfo", "jwks_uri": issuer + "/.well-known/jwks.json", "response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"}, diff --git a/server/resolvers/env.go b/server/resolvers/env.go index af04c3c..623d6b3 100644 --- a/server/resolvers/env.go +++ b/server/resolvers/env.go @@ -29,6 +29,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) { store := envstore.EnvStoreObj.GetEnvStoreClone() adminSecret := store.StringEnv[constants.EnvKeyAdminSecret] clientID := store.StringEnv[constants.EnvKeyClientID] + clientSecret := store.StringEnv[constants.EnvKeyClientSecret] databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL] databaseName := store.StringEnv[constants.EnvKeyDatabaseName] databaseType := store.StringEnv[constants.EnvKeyDatabaseType] @@ -70,6 +71,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) { DatabaseURL: databaseURL, DatabaseType: databaseType, ClientID: clientID, + ClientSecret: clientSecret, CustomAccessTokenScript: &customAccessTokenScript, SMTPHost: &smtpHost, SMTPPort: &smtpPort, From 5399ea8f32c6b91daa6c20473c1107f600040afd Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 28 Feb 2022 21:26:49 +0530 Subject: [PATCH 09/41] feat: add session token --- server/cookie/cookie.go | 24 ++++++++ server/{utils/crypto.go => crypto/aes.go} | 55 +---------------- server/crypto/b64.go | 17 ++++++ server/crypto/common.go | 38 ++++++++++++ server/env/persist_env.go | 18 +++--- server/handlers/app.go | 3 +- server/handlers/authorize.go | 72 +++++++++++++++++++++++ server/handlers/oauth_callback.go | 4 +- server/handlers/oauth_login.go | 6 +- server/resolvers/admin_login.go | 3 +- server/resolvers/admin_session.go | 3 +- server/resolvers/admin_signup.go | 5 +- server/resolvers/logout.go | 3 +- server/resolvers/reset_password.go | 4 +- server/resolvers/session.go | 3 +- server/resolvers/signup.go | 3 +- server/resolvers/update_env.go | 4 +- server/resolvers/update_profile.go | 3 +- server/sessionstore/in_memory_session.go | 46 +++++++-------- server/sessionstore/redis_store.go | 12 ++-- server/sessionstore/session.go | 28 ++++----- server/test/admin_logout_test.go | 4 +- server/test/admin_session_test.go | 4 +- server/test/delete_user_test.go | 4 +- server/test/env_test.go | 4 +- server/test/logout_test.go | 4 +- server/test/session_test.go | 4 +- server/test/update_env_test.go | 4 +- server/test/update_user_test.go | 4 +- server/test/users_test.go | 4 +- server/test/verification_requests_test.go | 4 +- server/token/admin_token.go | 4 +- server/token/auth_token.go | 4 +- templates/authorize.tmpl | 16 +++++ 34 files changed, 270 insertions(+), 148 deletions(-) rename server/{utils/crypto.go => crypto/aes.go} (60%) create mode 100644 server/crypto/b64.go create mode 100644 server/handlers/authorize.go create mode 100644 templates/authorize.tmpl diff --git a/server/cookie/cookie.go b/server/cookie/cookie.go index ad9fca8..f90dd21 100644 --- a/server/cookie/cookie.go +++ b/server/cookie/cookie.go @@ -10,6 +10,30 @@ import ( "github.com/gin-gonic/gin" ) +// SetSessionCookie sets the session cookie in the response +func SetSessionCookie(gc *gin.Context, sessionID string) { + secure := true + httpOnly := true + hostname := utils.GetHost(gc) + host, _ := utils.GetHostParts(hostname) + domain := utils.GetDomainName(hostname) + if domain != "localhost" { + domain = "." + domain + } + + // TODO allow configuring from dashboard + year := 60 * 60 * 24 * 365 + + gc.SetSameSite(http.SameSiteNoneMode) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain", sessionID, year, "/", domain, secure, httpOnly) + + // Fallback cookie for anomaly getection on browsers that don’t support the sameSite=None attribute. + gc.SetSameSite(http.SameSiteDefaultMode) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", sessionID, year, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", sessionID, year, "/", domain, secure, httpOnly) +} + // SetCookie sets the cookie in the response. It sets 4 cookies // 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com) // 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com). diff --git a/server/utils/crypto.go b/server/crypto/aes.go similarity index 60% rename from server/utils/crypto.go rename to server/crypto/aes.go index cd84d33..5d0bed4 100644 --- a/server/utils/crypto.go +++ b/server/crypto/aes.go @@ -1,32 +1,15 @@ -package utils +package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "encoding/base64" - "encoding/json" "io" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" - "golang.org/x/crypto/bcrypt" ) -// EncryptB64 encrypts data into base64 string -func EncryptB64(text string) string { - return base64.StdEncoding.EncodeToString([]byte(text)) -} - -// DecryptB64 decrypts from base64 string to readable string -func DecryptB64(s string) (string, error) { - data, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", err - } - return string(data), nil -} - // EncryptAES encrypts data using AES algorithm func EncryptAES(text []byte) ([]byte, error) { key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) @@ -88,39 +71,3 @@ func DecryptAES(ciphertext []byte) ([]byte, error) { return plaintext, nil } - -// EncryptEnvData is used to encrypt the env data -func EncryptEnvData(data envstore.Store) (string, error) { - jsonBytes, err := json.Marshal(data) - if err != nil { - return "", err - } - - storeData := envstore.EnvStoreObj.GetEnvStoreClone() - - err = json.Unmarshal(jsonBytes, &storeData) - if err != nil { - return "", err - } - - configData, err := json.Marshal(storeData) - if err != nil { - return "", err - } - encryptedConfig, err := EncryptAES(configData) - if err != nil { - return "", err - } - - return EncryptB64(string(encryptedConfig)), nil -} - -// EncryptPassword is used for encrypting password -func EncryptPassword(password string) (string, error) { - pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return "", err - } - - return string(pw), nil -} diff --git a/server/crypto/b64.go b/server/crypto/b64.go new file mode 100644 index 0000000..3453422 --- /dev/null +++ b/server/crypto/b64.go @@ -0,0 +1,17 @@ +package crypto + +import "encoding/base64" + +// EncryptB64 encrypts data into base64 string +func EncryptB64(text string) string { + return base64.StdEncoding.EncodeToString([]byte(text)) +} + +// DecryptB64 decrypts from base64 string to readable string +func DecryptB64(s string) (string, error) { + data, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return "", err + } + return string(data), nil +} diff --git a/server/crypto/common.go b/server/crypto/common.go index 698917a..45f8917 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -2,9 +2,11 @@ package crypto import ( "crypto/x509" + "encoding/json" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" + "golang.org/x/crypto/bcrypt" "gopkg.in/square/go-jose.v2" ) @@ -73,3 +75,39 @@ func GenerateJWKBasedOnEnv() (string, error) { return jwk, nil } + +// EncryptEnvData is used to encrypt the env data +func EncryptEnvData(data envstore.Store) (string, error) { + jsonBytes, err := json.Marshal(data) + if err != nil { + return "", err + } + + storeData := envstore.EnvStoreObj.GetEnvStoreClone() + + err = json.Unmarshal(jsonBytes, &storeData) + if err != nil { + return "", err + } + + configData, err := json.Marshal(storeData) + if err != nil { + return "", err + } + encryptedConfig, err := EncryptAES(configData) + if err != nil { + return "", err + } + + return EncryptB64(string(encryptedConfig)), nil +} + +// EncryptPassword is used for encrypting password +func EncryptPassword(password string) (string, error) { + pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + + return string(pw), nil +} diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 30e93d3..a7fba71 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -27,18 +27,18 @@ func GetEnvData() (envstore.Store, error) { } encryptionKey := env.Hash - decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey) + decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey) if err != nil { return result, err } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - b64DecryptedConfig, err := utils.DecryptB64(env.EnvData) + b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) if err != nil { return result, err } - decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig)) + decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) if err != nil { return result, err } @@ -59,9 +59,9 @@ func PersistEnv() error { // AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid hash := uuid.New().String()[:36-4] envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash) - encodedHash := utils.EncryptB64(hash) + encodedHash := crypto.EncryptB64(hash) - encryptedConfig, err := utils.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone()) + encryptedConfig, err := crypto.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone()) if err != nil { return err } @@ -79,18 +79,18 @@ func PersistEnv() error { // decrypt the config data from db // decryption can be done using the hash stored in db encryptionKey := env.Hash - decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey) + decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey) if err != nil { return err } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - b64DecryptedConfig, err := utils.DecryptB64(env.EnvData) + b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) if err != nil { return err } - decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig)) + decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) if err != nil { return err } @@ -172,7 +172,7 @@ func PersistEnv() error { envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk) if hasChanged { - encryptedConfig, err := utils.EncryptEnvData(storeData) + encryptedConfig, err := crypto.EncryptEnvData(storeData) if err != nil { return err } diff --git a/server/handlers/app.go b/server/handlers/app.go index 61325a9..483b8e1 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" @@ -36,7 +37,7 @@ func AppHandler() gin.HandlerFunc { stateObj.AuthorizerURL = hostname stateObj.RedirectURL = hostname + "/app" } else { - decodedState, err := utils.DecryptB64(state) + decodedState, err := crypto.DecryptB64(state) if err != nil { c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) return diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go new file mode 100644 index 0000000..67123cd --- /dev/null +++ b/server/handlers/authorize.go @@ -0,0 +1,72 @@ +package handlers + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +// AuthorizeHandler is the handler for the /authorize route +// required params +// ?redirect_uri = redirect url +// state[recommended] = to prevent CSRF attack (for authorizer its compulsory) +// code_challenge = to prevent CSRF attack +// code_challenge_method = to prevent CSRF attack [only sh256 is supported] +func AuthorizeHandler() gin.HandlerFunc { + return func(c *gin.Context) { + redirectURI := strings.TrimSpace(c.Query("redirect_uri")) + responseType := strings.TrimSpace(c.Query("response_type")) + state := strings.TrimSpace(c.Query("state")) + codeChallenge := strings.TrimSpace(c.Query("code_challenge")) + codeChallengeMethod := strings.TrimSpace(c.Query("code_challenge_method")) + fmt.Println(codeChallengeMethod) + template := "authorize.tmpl" + + if redirectURI == "" { + c.HTML(http.StatusBadRequest, template, gin.H{ + "targetOrigin": nil, + "authorizationResponse": nil, + "error": "redirect_uri is required", + }) + return + } + + if state == "" { + c.HTML(http.StatusBadRequest, template, gin.H{ + "targetOrigin": nil, + "authorizationResponse": nil, + "error": "state is required", + }) + return + } + + if responseType == "" { + responseType = "code" + } + + isCode := responseType == "code" + isToken := responseType == "token" + + if !isCode && !isToken { + c.HTML(http.StatusBadRequest, template, gin.H{ + "targetOrigin": nil, + "authorizationResponse": nil, + "error": "response_type is invalid", + }) + return + } + + if isCode { + if codeChallenge == "" { + c.HTML(http.StatusBadRequest, template, gin.H{ + "targetOrigin": nil, + "authorizationResponse": nil, + "error": "code_challenge is required", + }) + return + } + } + } +} diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 5ba684a..3232c47 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -30,11 +30,11 @@ func OAuthCallbackHandler() gin.HandlerFunc { provider := c.Param("oauth_provider") state := c.Request.FormValue("state") - sessionState := sessionstore.GetSocailLoginState(state) + sessionState := sessionstore.GetState(state) if sessionState == "" { c.JSON(400, gin.H{"error": "invalid oauth state"}) } - sessionstore.RemoveSocialLoginState(state) + sessionstore.GetState(state) // contains random token, redirect url, role sessionSplit := strings.Split(state, "___") diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index a01a300..9ef000e 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -54,7 +54,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle) + sessionstore.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) @@ -64,7 +64,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub) + sessionstore.SetState(oauthStateString, constants.SignupMethodGithub) oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github" url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) @@ -73,7 +73,7 @@ func OAuthLoginHandler() gin.HandlerFunc { isProviderConfigured = false break } - sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook) + sessionstore.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/resolvers/admin_login.go b/server/resolvers/admin_login.go index 306a214..3e28b3f 100644 --- a/server/resolvers/admin_login.go +++ b/server/resolvers/admin_login.go @@ -6,6 +6,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" @@ -25,7 +26,7 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod return res, fmt.Errorf(`invalid admin secret`) } - hashedKey, err := utils.EncryptPassword(adminSecret) + hashedKey, err := crypto.EncryptPassword(adminSecret) if err != nil { return res, err } diff --git a/server/resolvers/admin_session.go b/server/resolvers/admin_session.go index f464aee..9809835 100644 --- a/server/resolvers/admin_session.go +++ b/server/resolvers/admin_session.go @@ -6,6 +6,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/token" @@ -25,7 +26,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) { return res, fmt.Errorf("unauthorized") } - hashedKey, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) if err != nil { return res, err } diff --git a/server/resolvers/admin_signup.go b/server/resolvers/admin_signup.go index 8fa00d7..bb2f1eb 100644 --- a/server/resolvers/admin_signup.go +++ b/server/resolvers/admin_signup.go @@ -8,6 +8,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" @@ -58,7 +59,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - envData, err := utils.EncryptEnvData(storeData) + envData, err := crypto.EncryptEnvData(storeData) if err != nil { return res, err } @@ -68,7 +69,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - hashedKey, err := utils.EncryptPassword(params.AdminSecret) + hashedKey, err := crypto.EncryptPassword(params.AdminSecret) if err != nil { return res, err } diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 4653896..a6626e7 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -4,6 +4,7 @@ import ( "context" "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/token" @@ -30,7 +31,7 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) { return res, err } - decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash)) + decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash)) if err != nil { return res, err } diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index 843370f..2eceb79 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -7,11 +7,11 @@ import ( "time" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/token" - "github.com/authorizerdev/authorizer/server/utils" ) // ResetPasswordResolver is a resolver for reset password mutation @@ -41,7 +41,7 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) return res, err } - password, _ := utils.EncryptPassword(params.Password) + password, _ := crypto.EncryptPassword(params.Password) user.Password = &password signupMethod := user.SignupMethods diff --git a/server/resolvers/session.go b/server/resolvers/session.go index e390b5a..acd72c4 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/sessionstore" @@ -33,7 +34,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod return res, err } - decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash)) + decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash)) if err != nil { return res, err } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index b227797..0ce3953 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -9,6 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/email" @@ -72,7 +73,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR user.Roles = strings.Join(inputRoles, ",") - password, _ := utils.EncryptPassword(params.Password) + password, _ := crypto.EncryptPassword(params.Password) user.Password = &password if params.GivenName != nil { diff --git a/server/resolvers/update_env.go b/server/resolvers/update_env.go index 317bd88..0f428bb 100644 --- a/server/resolvers/update_env.go +++ b/server/resolvers/update_env.go @@ -199,14 +199,14 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model } if params.AdminSecret != nil { - hashedKey, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) if err != nil { return res, err } cookie.SetAdminCookie(gc, hashedKey) } - encryptedConfig, err := utils.EncryptEnvData(updatedData) + encryptedConfig, err := crypto.EncryptEnvData(updatedData) if err != nil { return res, err } diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index 1f19adb..2fef3e3 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -9,6 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/email" @@ -92,7 +93,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) return res, fmt.Errorf(`password and confirm password does not match`) } - password, _ := utils.EncryptPassword(*params.NewPassword) + password, _ := crypto.EncryptPassword(*params.NewPassword) user.Password = &password } diff --git a/server/sessionstore/in_memory_session.go b/server/sessionstore/in_memory_session.go index 9e52928..567c881 100644 --- a/server/sessionstore/in_memory_session.go +++ b/server/sessionstore/in_memory_session.go @@ -6,9 +6,9 @@ import ( // InMemoryStore is a simple in-memory store for sessions. type InMemoryStore struct { - mutex sync.Mutex - store map[string]map[string]string - socialLoginState map[string]string + mutex sync.Mutex + sessionStore map[string]map[string]string + stateStore map[string]string } // AddUserSession adds a user session to the in-memory store. @@ -16,20 +16,20 @@ func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) c.mutex.Lock() defer c.mutex.Unlock() // delete sessions > 500 // not recommended for production - if len(c.store) >= 500 { - c.store = map[string]map[string]string{} + if len(c.sessionStore) >= 500 { + c.sessionStore = map[string]map[string]string{} } // check if entry exists in map - _, exists := c.store[userId] + _, exists := c.sessionStore[userId] if exists { - tempMap := c.store[userId] + tempMap := c.sessionStore[userId] tempMap[accessToken] = refreshToken - c.store[userId] = tempMap + c.sessionStore[userId] = tempMap } else { tempMap := map[string]string{ accessToken: refreshToken, } - c.store[userId] = tempMap + c.sessionStore[userId] = tempMap } } @@ -37,21 +37,21 @@ func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) func (c *InMemoryStore) DeleteAllUserSession(userId string) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.store, userId) + delete(c.sessionStore, userId) } // DeleteUserSession deletes the particular user session from in-memory store. func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.store[userId], accessToken) + delete(c.sessionStore[userId], accessToken) } // ClearStore clears the in-memory store. func (c *InMemoryStore) ClearStore() { c.mutex.Lock() defer c.mutex.Unlock() - c.store = map[string]map[string]string{} + c.sessionStore = map[string]map[string]string{} } // GetUserSession returns the user session token from the in-memory store. @@ -60,7 +60,7 @@ func (c *InMemoryStore) GetUserSession(userId, accessToken string) string { // defer c.mutex.Unlock() token := "" - if sessionMap, ok := c.store[userId]; ok { + if sessionMap, ok := c.sessionStore[userId]; ok { if val, ok := sessionMap[accessToken]; ok { token = val } @@ -74,7 +74,7 @@ func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { // c.mutex.Lock() // defer c.mutex.Unlock() - sessionMap, ok := c.store[userId] + sessionMap, ok := c.sessionStore[userId] if !ok { return nil } @@ -82,31 +82,31 @@ func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { return sessionMap } -// SetSocialLoginState sets the social login state in the in-memory store. -func (c *InMemoryStore) SetSocialLoginState(key, state string) { +// SetState sets the state in the in-memory store. +func (c *InMemoryStore) SetState(key, state string) { c.mutex.Lock() defer c.mutex.Unlock() - c.socialLoginState[key] = state + c.stateStore[key] = state } -// GetSocialLoginState gets the social login state from the in-memory store. -func (c *InMemoryStore) GetSocialLoginState(key string) string { +// GetState gets the state from the in-memory store. +func (c *InMemoryStore) GetState(key string) string { c.mutex.Lock() defer c.mutex.Unlock() state := "" - if stateVal, ok := c.socialLoginState[key]; ok { + if stateVal, ok := c.stateStore[key]; ok { state = stateVal } return state } -// RemoveSocialLoginState removes the social login state from the in-memory store. -func (c *InMemoryStore) RemoveSocialLoginState(key string) { +// RemoveState removes the state from the in-memory store. +func (c *InMemoryStore) RemoveState(key string) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.socialLoginState, key) + delete(c.stateStore, key) } diff --git a/server/sessionstore/redis_store.go b/server/sessionstore/redis_store.go index 4531e0f..98a1711 100644 --- a/server/sessionstore/redis_store.go +++ b/server/sessionstore/redis_store.go @@ -68,16 +68,16 @@ func (c *RedisStore) GetUserSessions(userID string) map[string]string { return res } -// SetSocialLoginState sets the social login state in redis store. -func (c *RedisStore) SetSocialLoginState(key, state string) { +// SetState sets the state in redis store. +func (c *RedisStore) SetState(key, state string) { err := c.store.Set(c.ctx, key, state, 0).Err() if err != nil { log.Fatalln("Error saving redis token:", err) } } -// GetSocialLoginState gets the social login state from redis store. -func (c *RedisStore) GetSocialLoginState(key string) string { +// GetState gets the state from redis store. +func (c *RedisStore) GetState(key string) string { state := "" state, err := c.store.Get(c.ctx, key).Result() if err != nil { @@ -87,8 +87,8 @@ func (c *RedisStore) GetSocialLoginState(key string) string { return state } -// RemoveSocialLoginState removes the social login state from redis store. -func (c *RedisStore) RemoveSocialLoginState(key string) { +// RemoveState removes the state from redis store. +func (c *RedisStore) RemoveState(key string) { err := c.store.Del(c.ctx, key).Err() if err != nil { log.Fatalln("Error deleting redis token:", err) diff --git a/server/sessionstore/session.go b/server/sessionstore/session.go index f3fdf39..f33638c 100644 --- a/server/sessionstore/session.go +++ b/server/sessionstore/session.go @@ -86,35 +86,35 @@ func ClearStore() { } } -// SetSocialLoginState sets the social login state in the session store -func SetSocailLoginState(key, state string) { +// SetState sets the login state (key, value form) in the session store +func SetState(key, state string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.SetSocialLoginState(key, state) + SessionStoreObj.RedisMemoryStoreObj.SetState(key, state) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.SetSocialLoginState(key, state) + SessionStoreObj.InMemoryStoreObj.SetState(key, state) } } -// GetSocialLoginState returns the social login state from the session store -func GetSocailLoginState(key string) string { +// GetState returns the state from the session store +func GetState(key string) string { if SessionStoreObj.RedisMemoryStoreObj != nil { - return SessionStoreObj.RedisMemoryStoreObj.GetSocialLoginState(key) + return SessionStoreObj.RedisMemoryStoreObj.GetState(key) } if SessionStoreObj.InMemoryStoreObj != nil { - return SessionStoreObj.InMemoryStoreObj.GetSocialLoginState(key) + return SessionStoreObj.InMemoryStoreObj.GetState(key) } return "" } -// RemoveSocialLoginState removes the social login state from the session store -func RemoveSocialLoginState(key string) { +// RemoveState removes the social login state from the session store +func RemoveState(key string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.RemoveSocialLoginState(key) + SessionStoreObj.RedisMemoryStoreObj.RemoveState(key) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.RemoveSocialLoginState(key) + SessionStoreObj.InMemoryStoreObj.RemoveState(key) } } @@ -174,8 +174,8 @@ func InitSession() error { // if redis url is not set use in memory store SessionStoreObj.InMemoryStoreObj = &InMemoryStore{ - store: map[string]map[string]string{}, - socialLoginState: map[string]string{}, + sessionStore: map[string]map[string]string{}, + stateStore: map[string]string{}, } return nil diff --git a/server/test/admin_logout_test.go b/server/test/admin_logout_test.go index d322578..94f65c0 100644 --- a/server/test/admin_logout_test.go +++ b/server/test/admin_logout_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -18,7 +18,7 @@ func adminLogoutTests(t *testing.T, s TestSetup) { _, err := resolvers.AdminLogoutResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.AdminLogoutResolver(ctx) diff --git a/server/test/admin_session_test.go b/server/test/admin_session_test.go index 8954f08..96f5bfc 100644 --- a/server/test/admin_session_test.go +++ b/server/test/admin_session_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -18,7 +18,7 @@ func adminSessionTests(t *testing.T, s TestSetup) { _, err := resolvers.AdminSessionResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.AdminSessionResolver(ctx) diff --git a/server/test/delete_user_test.go b/server/test/delete_user_test.go index 4c385a3..2ecbe35 100644 --- a/server/test/delete_user_test.go +++ b/server/test/delete_user_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -28,7 +28,7 @@ func deleteUserTest(t *testing.T, s TestSetup) { }) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) diff --git a/server/test/env_test.go b/server/test/env_test.go index 19707a3..f825a50 100644 --- a/server/test/env_test.go +++ b/server/test/env_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -18,7 +18,7 @@ func envTests(t *testing.T, s TestSetup) { _, err := resolvers.EnvResolver(ctx) assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) res, err := resolvers.EnvResolver(ctx) diff --git a/server/test/logout_test.go b/server/test/logout_test.go index 3fbab1e..db5688a 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/sessionstore" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -38,7 +38,7 @@ func logoutTests(t *testing.T, s TestSetup) { refreshToken = val } - fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint)) + fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint)) token := *verifyRes.AccessToken cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) diff --git a/server/test/session_test.go b/server/test/session_test.go index e42c99b..99b1295 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/sessionstore" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -43,7 +43,7 @@ func sessionTests(t *testing.T, s TestSetup) { refreshToken = val } - fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint)) + fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint)) token := *verifyRes.AccessToken cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) diff --git a/server/test/update_env_test.go b/server/test/update_env_test.go index 8b687e4..527becc 100644 --- a/server/test/update_env_test.go +++ b/server/test/update_env_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -23,7 +23,7 @@ func updateEnvTests(t *testing.T, s TestSetup) { assert.NotNil(t, err) - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) newURL := "https://test.com" diff --git a/server/test/update_user_test.go b/server/test/update_user_test.go index cc2333c..d072a27 100644 --- a/server/test/update_user_test.go +++ b/server/test/update_user_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -33,7 +33,7 @@ func updateUserTest(t *testing.T, s TestSetup) { }) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) _, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{ diff --git a/server/test/users_test.go b/server/test/users_test.go index 223cb61..f390ed0 100644 --- a/server/test/users_test.go +++ b/server/test/users_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -35,7 +35,7 @@ func usersTest(t *testing.T, s TestSetup) { usersRes, err := resolvers.UsersResolver(ctx, pagination) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) diff --git a/server/test/verification_requests_test.go b/server/test/verification_requests_test.go index 1e40c22..58891a0 100644 --- a/server/test/verification_requests_test.go +++ b/server/test/verification_requests_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -37,7 +37,7 @@ func verificationRequestsTest(t *testing.T, s TestSetup) { requests, err := resolvers.VerificationRequestsResolver(ctx, pagination) assert.NotNil(t, err, "unauthorized") - h, err := utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Nil(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) requests, err = resolvers.VerificationRequestsResolver(ctx, pagination) diff --git a/server/token/admin_token.go b/server/token/admin_token.go index 46ce97a..1cdfe50 100644 --- a/server/token/admin_token.go +++ b/server/token/admin_token.go @@ -5,15 +5,15 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) // CreateAdminAuthToken creates the admin token based on secret key func CreateAdminAuthToken(tokenType string, c *gin.Context) (string, error) { - return utils.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) + return crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) } // GetAdminAuthToken helps in getting the admin token from the request cookie diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 8f2c7a4..b9eb25d 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -11,10 +11,10 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "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/utils" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "github.com/google/uuid" @@ -38,7 +38,7 @@ type Token struct { // CreateAuthToken creates a new auth token when userlogs in func CreateAuthToken(user models.User, roles []string) (*Token, error) { fingerprint := uuid.NewString() - fingerPrintHashBytes, err := utils.EncryptAES([]byte(fingerprint)) + fingerPrintHashBytes, err := crypto.EncryptAES([]byte(fingerprint)) if err != nil { return nil, err } diff --git a/templates/authorize.tmpl b/templates/authorize.tmpl new file mode 100644 index 0000000..a42c15b --- /dev/null +++ b/templates/authorize.tmpl @@ -0,0 +1,16 @@ + + + + Authorization Response + + + + + From f0f2e0b6c8081a8535c6c54d548a55c761543282 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 2 Mar 2022 17:42:31 +0530 Subject: [PATCH 10/41] fix: auth flow --- server/constants/token_types.go | 2 + server/cookie/cookie.go | 100 ++---- server/crypto/aes.go | 73 ++-- server/crypto/common.go | 2 +- server/db/models/verification_requests.go | 1 + server/db/providers/sql/sql.go | 5 +- server/email/email.go | 4 + server/env/persist_env.go | 18 +- server/graph/generated/generated.go | 407 +++++++--------------- server/graph/model/models_gen.go | 23 +- server/graph/schema.graphqls | 18 +- server/graph/schema.resolvers.go | 4 - server/handlers/oauth_callback.go | 14 +- server/handlers/verify_email.go | 26 +- server/resolvers/delete_user.go | 2 +- server/resolvers/forgot_password.go | 11 +- server/resolvers/is_valid_jwt.go | 52 --- server/resolvers/login.go | 26 +- server/resolvers/logout.go | 22 +- server/resolvers/magic_link_login.go | 11 +- server/resolvers/profile.go | 9 +- server/resolvers/resend_verify_email.go | 11 +- server/resolvers/reset_password.go | 14 +- server/resolvers/session.go | 60 ++-- server/resolvers/signup.go | 29 +- server/resolvers/update_profile.go | 63 ++-- server/resolvers/update_user.go | 20 +- server/resolvers/verify_email.go | 41 ++- server/sessionstore/in_memory_session.go | 74 +--- server/sessionstore/redis_store.go | 68 ++-- server/sessionstore/session.go | 32 -- server/test/is_valid_jwt_test.go | 38 -- server/test/jwt_test.go | 28 +- server/test/login_test.go | 13 +- server/test/logout_test.go | 21 +- server/test/magic_link_login_test.go | 12 +- server/test/profile_test.go | 12 +- server/test/resolvers_test.go | 16 +- server/test/session_test.go | 21 +- server/test/test.go | 2 +- server/test/update_profile_test.go | 13 +- server/test/verification_requests_test.go | 5 +- server/token/auth_token.go | 271 ++++++++++---- server/token/jwt.go | 18 +- server/token/verification_token.go | 8 +- server/utils/common.go | 2 +- server/utils/nonce.go | 36 ++ 47 files changed, 786 insertions(+), 972 deletions(-) delete mode 100644 server/resolvers/is_valid_jwt.go delete mode 100644 server/test/is_valid_jwt_test.go create mode 100644 server/utils/nonce.go diff --git a/server/constants/token_types.go b/server/constants/token_types.go index 993d4ea..df671bf 100644 --- a/server/constants/token_types.go +++ b/server/constants/token_types.go @@ -5,4 +5,6 @@ const ( TokenTypeRefreshToken = "refresh_token" // TokenTypeAccessToken is the access_token token type TokenTypeAccessToken = "access_token" + // TokenTypeIdentityToken is the identity_token token type + TokenTypeIdentityToken = "id_token" ) diff --git a/server/cookie/cookie.go b/server/cookie/cookie.go index f90dd21..42445fb 100644 --- a/server/cookie/cookie.go +++ b/server/cookie/cookie.go @@ -2,7 +2,6 @@ package cookie import ( "net/http" - "net/url" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" @@ -10,8 +9,8 @@ import ( "github.com/gin-gonic/gin" ) -// SetSessionCookie sets the session cookie in the response -func SetSessionCookie(gc *gin.Context, sessionID string) { +// SetSession sets the session cookie in the response +func SetSession(gc *gin.Context, sessionID string) { secure := true httpOnly := true hostname := utils.GetHost(gc) @@ -26,21 +25,16 @@ func SetSessionCookie(gc *gin.Context, sessionID string) { gc.SetSameSite(http.SameSiteNoneMode) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain", sessionID, year, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly) // Fallback cookie for anomaly getection on browsers that don’t support the sameSite=None attribute. gc.SetSameSite(http.SameSiteDefaultMode) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", sessionID, year, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", sessionID, year, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain_compat", sessionID, year, "/", domain, secure, httpOnly) } -// SetCookie sets the cookie in the response. It sets 4 cookies -// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com) -// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com). -// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification. -// 4 COOKIE_NAME.refresh_token refresh token -// Note all sites don't allow 2nd type of cookie -func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) { +// DeleteSession sets session cookies to expire +func DeleteSession(gc *gin.Context) { secure := true httpOnly := true hostname := utils.GetHost(gc) @@ -50,77 +44,33 @@ func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash strin domain = "." + domain } - year := 60 * 60 * 24 * 365 - thirtyMin := 60 * 30 - gc.SetSameSite(http.SameSiteNoneMode) - // set cookie for host - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly) - // in case of subdomain, set cookie for domain - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly) - - // set finger print cookie (this should be accessed via cookie only) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly) - - // set refresh token cookie (this should be accessed via cookie only) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly) + gc.SetSameSite(http.SameSiteDefaultMode) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", "", -1, "/", domain, secure, httpOnly) } -// GetAccessTokenCookie to get access token cookie from the request -func GetAccessTokenCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token") +// GetSession gets the session cookie from context +func GetSession(gc *gin.Context) (string, error) { + var cookie *http.Cookie + var err error + cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session") if err != nil { - cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain") + cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain") if err != nil { - return "", err + cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_compat") + if err != nil { + cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain_compat") + } + + if err != nil { + return "", err + } } } return cookie.Value, nil } - -// GetRefreshTokenCookie to get refresh token cookie -func GetRefreshTokenCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token") - if err != nil { - return "", err - } - - return cookie.Value, nil -} - -// GetFingerPrintCookie to get fingerprint cookie -func GetFingerPrintCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint") - if err != nil { - return "", err - } - - // cookie escapes special characters like $ - // hence we need to unescape before comparing - decodedValue, err := url.QueryUnescape(cookie.Value) - if err != nil { - return "", err - } - - return decodedValue, nil -} - -// DeleteCookie sets response cookies to expire -func DeleteCookie(gc *gin.Context) { - secure := true - httpOnly := true - hostname := utils.GetHost(gc) - host, _ := utils.GetHostParts(hostname) - domain := utils.GetDomainName(hostname) - if domain != "localhost" { - domain = "." + domain - } - - gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly) -} diff --git a/server/crypto/aes.go b/server/crypto/aes.go index 5d0bed4..2750486 100644 --- a/server/crypto/aes.go +++ b/server/crypto/aes.go @@ -3,71 +3,40 @@ package crypto import ( "crypto/aes" "crypto/cipher" - "crypto/rand" - "io" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" ) -// EncryptAES encrypts data using AES algorithm -func EncryptAES(text []byte) ([]byte, error) { +var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} + +// EncryptAES method is to encrypt or hide any classified text +func EncryptAES(text string) (string, error) { key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) - c, err := aes.NewCipher(key) - var res []byte + block, err := aes.NewCipher(key) if err != nil { - return res, err + return "", err } - - // gcm or Galois/Counter Mode, is a mode of operation - // for symmetric key cryptographic block ciphers - // - https://en.wikipedia.org/wiki/Galois/Counter_Mode - gcm, err := cipher.NewGCM(c) - if err != nil { - return res, err - } - - // creates a new byte array the size of the nonce - // which must be passed to Seal - nonce := make([]byte, gcm.NonceSize()) - // populates our nonce with a cryptographically secure - // random sequence - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return res, err - } - - // here we encrypt our text using the Seal function - // Seal encrypts and authenticates plaintext, authenticates the - // additional data and appends the result to dst, returning the updated - // slice. The nonce must be NonceSize() bytes long and unique for all - // time, for a given key. - return gcm.Seal(nonce, nonce, text, nil), nil + plainText := []byte(text) + cfb := cipher.NewCFBEncrypter(block, bytes) + cipherText := make([]byte, len(plainText)) + cfb.XORKeyStream(cipherText, plainText) + return EncryptB64(string(cipherText)), nil } -// DecryptAES decrypts data using AES algorithm -func DecryptAES(ciphertext []byte) ([]byte, error) { +// DecryptAES method is to extract back the encrypted text +func DecryptAES(text string) (string, error) { key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) - c, err := aes.NewCipher(key) - var res []byte + block, err := aes.NewCipher(key) if err != nil { - return res, err + return "", err } - - gcm, err := cipher.NewGCM(c) + cipherText, err := DecryptB64(text) if err != nil { - return res, err + return "", err } - - nonceSize := gcm.NonceSize() - if len(ciphertext) < nonceSize { - return res, err - } - - nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] - plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return res, err - } - - return plaintext, nil + cfb := cipher.NewCFBDecrypter(block, bytes) + plainText := make([]byte, len(cipherText)) + cfb.XORKeyStream(plainText, []byte(cipherText)) + return string(plainText), nil } diff --git a/server/crypto/common.go b/server/crypto/common.go index 45f8917..df3754c 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -94,7 +94,7 @@ func EncryptEnvData(data envstore.Store) (string, error) { if err != nil { return "", err } - encryptedConfig, err := EncryptAES(configData) + encryptedConfig, err := EncryptAES(string(configData)) if err != nil { return "", err } diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index 5c23301..eec5427 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -12,6 +12,7 @@ type VerificationRequest struct { CreatedAt int64 `json:"created_at" bson:"created_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"` Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` + Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"` } func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { diff --git a/server/db/providers/sql/sql.go b/server/db/providers/sql/sql.go index 28af527..279b707 100644 --- a/server/db/providers/sql/sql.go +++ b/server/db/providers/sql/sql.go @@ -56,7 +56,10 @@ func NewProvider() (*provider, error) { return nil, err } - sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}) + err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}) + if err != nil { + return nil, err + } return &provider{ db: sqlDB, }, nil diff --git a/server/email/email.go b/server/email/email.go index 2c57c3c..7b423d2 100644 --- a/server/email/email.go +++ b/server/email/email.go @@ -31,6 +31,10 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s // SendMail function to send mail func SendMail(to []string, Subject, bodyMessage string) error { + // dont trigger email sending in case of test + if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" { + return nil + } m := gomail.NewMessage() m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)) m.SetHeader("To", to...) diff --git a/server/env/persist_env.go b/server/env/persist_env.go index a7fba71..24df138 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -33,17 +33,13 @@ func GetEnvData() (envstore.Store, error) { } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) + + decryptedConfigs, err := crypto.DecryptAES(env.EnvData) if err != nil { return result, err } - decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) - if err != nil { - return result, err - } - - err = json.Unmarshal(decryptedConfigs, &result) + err = json.Unmarshal([]byte(decryptedConfigs), &result) if err != nil { return result, err } @@ -85,12 +81,8 @@ func PersistEnv() error { } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) - if err != nil { - return err - } - decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) + decryptedConfigs, err := crypto.DecryptAES(env.EnvData) if err != nil { return err } @@ -98,7 +90,7 @@ func PersistEnv() error { // temp store variable var storeData envstore.Store - err = json.Unmarshal(decryptedConfigs, &storeData) + err = json.Unmarshal([]byte(decryptedConfigs), &storeData) if err != nil { return err } diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 39e22a8..69b021f 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -44,10 +44,12 @@ type DirectiveRoot struct { type ComplexityRoot struct { AuthResponse struct { - AccessToken func(childComplexity int) int - ExpiresAt func(childComplexity int) int - Message func(childComplexity int) int - User func(childComplexity int) int + AccessToken func(childComplexity int) int + ExpiresIn func(childComplexity int) int + IDToken func(childComplexity int) int + Message func(childComplexity int) int + RefreshToken func(childComplexity int) int + User func(childComplexity int) int } Env struct { @@ -134,7 +136,6 @@ type ComplexityRoot struct { Query struct { AdminSession func(childComplexity int) int Env func(childComplexity int) int - IsValidJwt func(childComplexity int, params *model.IsValidJWTQueryInput) int Meta func(childComplexity int) int Profile func(childComplexity int) int Session func(childComplexity int, params *model.SessionQueryInput) int @@ -171,11 +172,6 @@ type ComplexityRoot struct { Users func(childComplexity int) int } - ValidJWTResponse struct { - Message func(childComplexity int) int - Valid func(childComplexity int) int - } - VerificationRequest struct { CreatedAt func(childComplexity int) int Email func(childComplexity int) int @@ -212,7 +208,6 @@ type MutationResolver interface { type QueryResolver interface { Meta(ctx context.Context) (*model.Meta, error) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) - IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) Profile(ctx context.Context) (*model.User, error) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) @@ -242,12 +237,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.AccessToken(childComplexity), true - case "AuthResponse.expires_at": - if e.complexity.AuthResponse.ExpiresAt == nil { + case "AuthResponse.expires_in": + if e.complexity.AuthResponse.ExpiresIn == nil { break } - return e.complexity.AuthResponse.ExpiresAt(childComplexity), true + return e.complexity.AuthResponse.ExpiresIn(childComplexity), true + + case "AuthResponse.id_token": + if e.complexity.AuthResponse.IDToken == nil { + break + } + + return e.complexity.AuthResponse.IDToken(childComplexity), true case "AuthResponse.message": if e.complexity.AuthResponse.Message == nil { @@ -256,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.Message(childComplexity), true + case "AuthResponse.refresh_token": + if e.complexity.AuthResponse.RefreshToken == nil { + break + } + + return e.complexity.AuthResponse.RefreshToken(childComplexity), true + case "AuthResponse.user": if e.complexity.AuthResponse.User == nil { break @@ -804,18 +813,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Env(childComplexity), true - case "Query.is_valid_jwt": - if e.complexity.Query.IsValidJwt == nil { - break - } - - args, err := ec.field_Query_is_valid_jwt_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.IsValidJwt(childComplexity, args["params"].(*model.IsValidJWTQueryInput)), true - case "Query.meta": if e.complexity.Query.Meta == nil { break @@ -1006,20 +1003,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Users.Users(childComplexity), true - case "ValidJWTResponse.message": - if e.complexity.ValidJWTResponse.Message == nil { - break - } - - return e.complexity.ValidJWTResponse.Message(childComplexity), true - - case "ValidJWTResponse.valid": - if e.complexity.ValidJWTResponse.Valid == nil { - break - } - - return e.complexity.ValidJWTResponse.Valid(childComplexity), true - case "VerificationRequest.created_at": if e.complexity.VerificationRequest.CreatedAt == nil { break @@ -1221,7 +1204,9 @@ type Error { type AuthResponse { message: String! access_token: String - expires_at: Int64 + id_token: String + refresh_token: String + expires_in: Int64 user: User } @@ -1229,11 +1214,6 @@ type Response { message: String! } -type ValidJWTResponse { - valid: Boolean! - message: String! -} - type Env { ADMIN_SECRET: String DATABASE_NAME: String! @@ -1337,6 +1317,7 @@ input LoginInput { email: String! password: String! roles: [String!] + scope: [String!] } input VerifyEmailInput { @@ -1395,15 +1376,12 @@ input DeleteUserInput { input MagicLinkLoginInput { email: String! roles: [String!] + scope: [String!] } input SessionQueryInput { roles: [String!] -} - -input IsValidJWTQueryInput { - jwt: String - roles: [String!] + scope: [String!] } input PaginationInput { @@ -1437,7 +1415,6 @@ type Mutation { type Query { meta: Meta! session(params: SessionQueryInput): AuthResponse! - is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse! profile: User! # admin only apis _users(params: PaginatedInput): Users! @@ -1693,21 +1670,6 @@ func (ec *executionContext) field_Query__verification_requests_args(ctx context. return args, nil } -func (ec *executionContext) field_Query_is_valid_jwt_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *model.IsValidJWTQueryInput - if tmp, ok := rawArgs["params"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) - arg0, err = ec.unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx, tmp) - if err != nil { - return nil, err - } - } - args["params"] = arg0 - return args, nil -} - func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1828,7 +1790,7 @@ func (ec *executionContext) _AuthResponse_access_token(ctx context.Context, fiel return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { +func (ec *executionContext) _AuthResponse_id_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1846,7 +1808,71 @@ func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ExpiresAt, nil + return obj.IDToken, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _AuthResponse_refresh_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "AuthResponse", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.RefreshToken, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _AuthResponse_expires_in(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "AuthResponse", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ExpiresIn, nil }) if err != nil { ec.Error(ctx, err) @@ -4274,48 +4300,6 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Query_is_valid_jwt(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, - IsResolver: true, - } - - ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_is_valid_jwt_args(ctx, rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - fc.Args = args - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().IsValidJwt(rctx, args["params"].(*model.IsValidJWTQueryInput)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(*model.ValidJWTResponse) - fc.Result = res - return ec.marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx, field.Selections, res) -} - func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5240,76 +5224,6 @@ func (ec *executionContext) _Users_users(ctx context.Context, field graphql.Coll return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _ValidJWTResponse_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "ValidJWTResponse", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Valid, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(bool) - fc.Result = res - return ec.marshalNBoolean2bool(ctx, field.Selections, res) -} - -func (ec *executionContext) _ValidJWTResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "ValidJWTResponse", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Message, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6821,37 +6735,6 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex return it, nil } -func (ec *executionContext) unmarshalInputIsValidJWTQueryInput(ctx context.Context, obj interface{}) (model.IsValidJWTQueryInput, error) { - var it model.IsValidJWTQueryInput - asMap := map[string]interface{}{} - for k, v := range obj.(map[string]interface{}) { - asMap[k] = v - } - - for k, v := range asMap { - switch k { - case "jwt": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jwt")) - it.Jwt, err = ec.unmarshalOString2ᚖstring(ctx, v) - if err != nil { - return it, err - } - case "roles": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles")) - it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) - if err != nil { - return it, err - } - } - } - - return it, nil -} - func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) { var it model.LoginInput asMap := map[string]interface{}{} @@ -6885,6 +6768,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in if err != nil { return it, err } + case "scope": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope")) + it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } } } @@ -6916,6 +6807,14 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex if err != nil { return it, err } + case "scope": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope")) + it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } } } @@ -7063,6 +6962,14 @@ func (ec *executionContext) unmarshalInputSessionQueryInput(ctx context.Context, if err != nil { return it, err } + case "scope": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope")) + it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } } } @@ -7730,8 +7637,12 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection } case "access_token": out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj) - case "expires_at": - out.Values[i] = ec._AuthResponse_expires_at(ctx, field, obj) + case "id_token": + out.Values[i] = ec._AuthResponse_id_token(ctx, field, obj) + case "refresh_token": + out.Values[i] = ec._AuthResponse_refresh_token(ctx, field, obj) + case "expires_in": + out.Values[i] = ec._AuthResponse_expires_in(ctx, field, obj) case "user": out.Values[i] = ec._AuthResponse_user(ctx, field, obj) default: @@ -8136,20 +8047,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) - case "is_valid_jwt": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_is_valid_jwt(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) case "profile": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -8365,38 +8262,6 @@ func (ec *executionContext) _Users(ctx context.Context, sel ast.SelectionSet, ob return out } -var validJWTResponseImplementors = []string{"ValidJWTResponse"} - -func (ec *executionContext) _ValidJWTResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidJWTResponse) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, validJWTResponseImplementors) - - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ValidJWTResponse") - case "valid": - out.Values[i] = ec._ValidJWTResponse_valid(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "message": - out.Values[i] = ec._ValidJWTResponse_message(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - var verificationRequestImplementors = []string{"VerificationRequest"} func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler { @@ -9012,20 +8877,6 @@ func (ec *executionContext) marshalNUsers2ᚖgithubᚗcomᚋauthorizerdevᚋauth return ec._Users(ctx, sel, v) } -func (ec *executionContext) marshalNValidJWTResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidJWTResponse) graphql.Marshaler { - return ec._ValidJWTResponse(ctx, sel, &v) -} - -func (ec *executionContext) marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidJWTResponse) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - return ec._ValidJWTResponse(ctx, sel, v) -} - func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -9395,14 +9246,6 @@ func (ec *executionContext) marshalOInt642ᚖint64(ctx context.Context, sel ast. return graphql.MarshalInt64(*v) } -func (ec *executionContext) unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx context.Context, v interface{}) (*model.IsValidJWTQueryInput, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalInputIsValidJWTQueryInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) { if v == nil { return nil, nil diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index b5213b2..5134dda 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -11,10 +11,12 @@ type AdminSignupInput struct { } type AuthResponse struct { - Message string `json:"message"` - AccessToken *string `json:"access_token"` - ExpiresAt *int64 `json:"expires_at"` - User *User `json:"user"` + Message string `json:"message"` + AccessToken *string `json:"access_token"` + IDToken *string `json:"id_token"` + RefreshToken *string `json:"refresh_token"` + ExpiresIn *int64 `json:"expires_in"` + User *User `json:"user"` } type DeleteUserInput struct { @@ -70,20 +72,17 @@ type ForgotPasswordInput struct { Email string `json:"email"` } -type IsValidJWTQueryInput struct { - Jwt *string `json:"jwt"` - Roles []string `json:"roles"` -} - type LoginInput struct { Email string `json:"email"` Password string `json:"password"` Roles []string `json:"roles"` + Scope []string `json:"scope"` } type MagicLinkLoginInput struct { Email string `json:"email"` Roles []string `json:"roles"` + Scope []string `json:"scope"` } type Meta struct { @@ -130,6 +129,7 @@ type Response struct { type SessionQueryInput struct { Roles []string `json:"roles"` + Scope []string `json:"scope"` } type SignUpInput struct { @@ -238,11 +238,6 @@ type Users struct { Users []*User `json:"users"` } -type ValidJWTResponse struct { - Valid bool `json:"valid"` - Message string `json:"message"` -} - type VerificationRequest struct { ID string `json:"id"` Identifier *string `json:"identifier"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index cca567e..cf01374 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -72,7 +72,9 @@ type Error { type AuthResponse { message: String! access_token: String - expires_at: Int64 + id_token: String + refresh_token: String + expires_in: Int64 user: User } @@ -80,11 +82,6 @@ type Response { message: String! } -type ValidJWTResponse { - valid: Boolean! - message: String! -} - type Env { ADMIN_SECRET: String DATABASE_NAME: String! @@ -188,6 +185,7 @@ input LoginInput { email: String! password: String! roles: [String!] + scope: [String!] } input VerifyEmailInput { @@ -246,15 +244,12 @@ input DeleteUserInput { input MagicLinkLoginInput { email: String! roles: [String!] + scope: [String!] } input SessionQueryInput { roles: [String!] -} - -input IsValidJWTQueryInput { - jwt: String - roles: [String!] + scope: [String!] } input PaginationInput { @@ -288,7 +283,6 @@ type Mutation { type Query { meta: Meta! session(params: SessionQueryInput): AuthResponse! - is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse! profile: User! # admin only apis _users(params: PaginatedInput): Users! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index e3cd241..79f718a 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -79,10 +79,6 @@ func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryI return resolvers.SessionResolver(ctx, params) } -func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) { - return resolvers.IsValidJwtResolver(ctx, params) -} - func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { return resolvers.ProfileResolver(ctx) } diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 3232c47..b16c789 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -144,11 +144,13 @@ func OAuthCallbackHandler() gin.HandlerFunc { } } - authToken, _ := token.CreateAuthToken(user, inputRoles) - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) - utils.SaveSessionInDB(user.ID, c) + // TODO use query param + scope := []string{"openid", "email", "profile"} + authToken, _ := token.CreateAuthToken(c, user, inputRoles, scope) + sessionstore.SetState(authToken.FingerPrint, user.ID) + cookie.SetSession(c, authToken.FingerPrintHash) + go utils.SaveSessionInDB(c, user.ID) c.Redirect(http.StatusTemporaryRedirect, redirectURL) } } @@ -227,7 +229,7 @@ func processGithubUserInfo(code string) (models.User, error) { GivenName: &firstName, FamilyName: &lastName, Picture: &picture, - Email: userRawData["email"], + Email: userRawData["sub"], } return user, nil @@ -260,7 +262,7 @@ func processFacebookUserInfo(code string) (models.User, error) { userRawData := make(map[string]interface{}) json.Unmarshal(body, &userRawData) - email := fmt.Sprintf("%v", userRawData["email"]) + email := fmt.Sprintf("%v", userRawData["sub"]) picObject := userRawData["picture"].(map[string]interface{})["data"] picDataObject := picObject.(map[string]interface{}) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 9aecff4..acc5dea 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -11,6 +11,7 @@ import ( "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) // VerifyEmailHandler handles the verify email route. @@ -18,7 +19,7 @@ import ( func VerifyEmailHandler() gin.HandlerFunc { return func(c *gin.Context) { errorRes := gin.H{ - "message": "invalid token", + "error": "invalid token", } tokenInQuery := c.Query("token") if tokenInQuery == "" { @@ -33,13 +34,21 @@ func VerifyEmailHandler() gin.HandlerFunc { } // verify if token exists in db - claim, err := token.ParseJWTToken(tokenInQuery) + hostname := utils.GetHost(c) + encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) + if err != nil { + c.JSON(400, gin.H{ + "error": err.Error(), + }) + return + } + claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email) if err != nil { c.JSON(400, errorRes) return } - user, err := db.Provider.GetUserByEmail(claim["email"].(string)) + user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) if err != nil { c.JSON(400, gin.H{ "message": err.Error(), @@ -57,16 +66,19 @@ func VerifyEmailHandler() gin.HandlerFunc { db.Provider.DeleteVerificationRequest(verificationRequest) roles := strings.Split(user.Roles, ",") - authToken, err := token.CreateAuthToken(user, roles) + scope := []string{"openid", "email", "profile"} + nonce := uuid.New().String() + _, authToken, err := token.CreateSessionToken(user, nonce, roles, scope) if err != nil { c.JSON(400, gin.H{ "message": err.Error(), }) return } - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) - utils.SaveSessionInDB(user.ID, c) + sessionstore.SetState(authToken, nonce+"@"+user.ID) + cookie.SetSession(c, authToken) + + go utils.SaveSessionInDB(c, user.ID) c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string)) } diff --git a/server/resolvers/delete_user.go b/server/resolvers/delete_user.go index 56f4c3a..164c413 100644 --- a/server/resolvers/delete_user.go +++ b/server/resolvers/delete_user.go @@ -29,7 +29,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod return res, err } - sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) + go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) err = db.Provider.DeleteUser(user) if err != nil { diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index 2ca5b52..49eaa75 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -39,7 +39,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu } hostname := utils.GetHost(gc) - verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname) + nonce, nonceHash, err := utils.GenerateNonce() + if err != nil { + return res, err + } + verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash) if err != nil { log.Println(`error generating token`, err) } @@ -48,12 +52,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu Identifier: constants.VerificationTypeForgotPassword, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: params.Email, + Nonce: nonce, }) // exec it as go routin so that we can reduce the api latency - go func() { - email.SendForgotPasswordMail(params.Email, verificationToken, hostname) - }() + go email.SendForgotPasswordMail(params.Email, verificationToken, hostname) res = &model.Response{ Message: `Please check your inbox! We have sent a password reset link.`, diff --git a/server/resolvers/is_valid_jwt.go b/server/resolvers/is_valid_jwt.go deleted file mode 100644 index d2bd6bf..0000000 --- a/server/resolvers/is_valid_jwt.go +++ /dev/null @@ -1,52 +0,0 @@ -package resolvers - -import ( - "context" - "errors" - "fmt" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/envstore" - "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/token" - tokenHelper "github.com/authorizerdev/authorizer/server/token" - "github.com/authorizerdev/authorizer/server/utils" -) - -// IsValidJwtResolver resolver to return if given jwt is valid -func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) { - gc, err := utils.GinContextFromContext(ctx) - token, err := token.GetAccessToken(gc) - - if token == "" || err != nil { - if params != nil && *params.Jwt != "" { - token = *params.Jwt - } else { - return nil, errors.New("no jwt provided via cookie / header / params") - } - } - - claims, err := tokenHelper.ParseJWTToken(token) - if err != nil { - return nil, err - } - - claimRoleInterface := claims[envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{}) - claimRoles := []string{} - for _, v := range claimRoleInterface { - claimRoles = append(claimRoles, v.(string)) - } - - if params != nil && params.Roles != nil && len(params.Roles) > 0 { - for _, v := range params.Roles { - if !utils.StringSliceContains(claimRoles, v) { - return nil, fmt.Errorf(`unauthorized`) - } - } - } - - return &model.ValidJWTResponse{ - Valid: true, - Message: "Valid JWT", - }, nil -} diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 76f31d7..03ffbb5 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -59,20 +59,36 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes roles = params.Roles } - authToken, err := token.CreateAuthToken(user, roles) + scope := []string{"openid", "email", "profile"} + if params.Scope != nil && len(scope) > 0 { + scope = params.Scope + } + + authToken, err := token.CreateAuthToken(gc, user, roles, scope) if err != nil { return res, err } - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) - utils.SaveSessionInDB(user.ID, gc) + cookie.SetSession(gc, authToken.FingerPrintHash) + + expiresIn := int64(1800) res = &model.AuthResponse{ Message: `Logged in successfully`, AccessToken: &authToken.AccessToken.Token, - ExpiresAt: &authToken.AccessToken.ExpiresAt, + IDToken: &authToken.IDToken.Token, + ExpiresIn: &expiresIn, User: user.AsAPIUser(), } + sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + } + + go utils.SaveSessionInDB(gc, user.ID) + return res, nil } diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index a6626e7..d2dfbc2 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -7,7 +7,6 @@ import ( "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/sessionstore" - "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -19,34 +18,21 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) { return res, err } - // get refresh token - refreshToken, err := token.GetRefreshToken(gc) - if err != nil { - return res, err - } - // get fingerprint hash - fingerprintHash, err := token.GetFingerPrint(gc) + fingerprintHash, err := cookie.GetSession(gc) if err != nil { return res, err } - decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash)) + decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash) if err != nil { return res, err } fingerPrint := string(decryptedFingerPrint) - // verify refresh token and fingerprint - claims, err := token.ParseJWTToken(refreshToken) - if err != nil { - return res, err - } - - userID := claims["id"].(string) - sessionstore.DeleteUserSession(userID, fingerPrint) - cookie.DeleteCookie(gc) + sessionstore.RemoveState(fingerPrint) + cookie.DeleteSession(gc) res = &model.Response{ Message: "Logged out successfully", diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index 5ed1801..a5d80ed 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -109,8 +109,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu hostname := utils.GetHost(gc) if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request + nonce, nonceHash, err := utils.GenerateNonce() + if err != nil { + return res, err + } verificationType := constants.VerificationTypeMagicLinkLogin - verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) + verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) if err != nil { log.Println(`error generating token`, err) } @@ -119,12 +123,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu Identifier: verificationType, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: params.Email, + Nonce: nonce, }) // exec it as go routin so that we can reduce the api latency - go func() { - email.SendVerificationMail(params.Email, verificationToken, hostname) - }() + go email.SendVerificationMail(params.Email, verificationToken, hostname) } res = &model.Response{ diff --git a/server/resolvers/profile.go b/server/resolvers/profile.go index 8b1e44c..882f250 100644 --- a/server/resolvers/profile.go +++ b/server/resolvers/profile.go @@ -2,7 +2,6 @@ package resolvers import ( "context" - "fmt" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" @@ -18,13 +17,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) { return res, err } - claims, err := token.ValidateAccessToken(gc) + accessToken, err := token.GetAccessToken(gc) if err != nil { return res, err } - userID := fmt.Sprintf("%v", claims["id"]) + claims, err := token.ValidateAccessToken(gc, accessToken) + if err != nil { + return res, err + } + userID := claims["sub"].(string) user, err := db.Provider.GetUserByID(userID) if err != nil { return res, err diff --git a/server/resolvers/resend_verify_email.go b/server/resolvers/resend_verify_email.go index 214668f..f514681 100644 --- a/server/resolvers/resend_verify_email.go +++ b/server/resolvers/resend_verify_email.go @@ -44,7 +44,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma } hostname := utils.GetHost(gc) - verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname) + nonce, nonceHash, err := utils.GenerateNonce() + if err != nil { + return res, err + } + verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash) if err != nil { log.Println(`error generating token`, err) } @@ -53,12 +57,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma Identifier: params.Identifier, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: params.Email, + Nonce: nonce, }) // exec it as go routin so that we can reduce the api latency - go func() { - email.SendVerificationMail(params.Email, verificationToken, hostname) - }() + go email.SendVerificationMail(params.Email, verificationToken, hostname) res = &model.Response{ Message: `Verification email has been sent. Please check your inbox`, diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index 2eceb79..d94707f 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -12,11 +12,16 @@ import ( "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" ) // ResetPasswordResolver is a resolver for reset password mutation func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { var res *model.Response + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + return res, err + } if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } @@ -31,12 +36,17 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) } // verify if token exists in db - claim, err := token.ParseJWTToken(params.Token) + hostname := utils.GetHost(gc) + encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) + if err != nil { + return res, err + } + claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email) if err != nil { return res, fmt.Errorf(`invalid token`) } - user, err := db.Provider.GetUserByEmail(claim["email"].(string)) + user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) if err != nil { return res, err } diff --git a/server/resolvers/session.go b/server/resolvers/session.go index acd72c4..3d9c668 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/authorizerdev/authorizer/server/cookie" - "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/sessionstore" @@ -14,6 +13,7 @@ import ( ) // SessionResolver is a resolver for session query +// TODO allow validating with code and code verifier instead of cookie (PKCE flow) func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) { var res *model.AuthResponse @@ -22,48 +22,27 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod return res, err } - // get refresh token - refreshToken, err := token.GetRefreshToken(gc) + sessionToken, err := cookie.GetSession(gc) if err != nil { return res, err } - // get fingerprint hash - fingerprintHash, err := token.GetFingerPrint(gc) + // get session from cookie + claims, err := token.ValidateBrowserSession(gc, sessionToken) if err != nil { return res, err } - - decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash)) - if err != nil { - return res, err - } - - fingerPrint := string(decryptedFingerPrint) - - // verify refresh token and fingerprint - claims, err := token.ParseJWTToken(refreshToken) - if err != nil { - return res, err - } - - userID := claims["id"].(string) - - persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint) - if refreshToken != persistedRefresh { - return res, fmt.Errorf(`unauthorized`) - } - + userID := claims.Subject user, err := db.Provider.GetUserByID(userID) if err != nil { return res, err } // refresh token has "roles" as claim - claimRoleInterface := claims["roles"].([]interface{}) + claimRoleInterface := claims.Roles claimRoles := []string{} for _, v := range claimRoleInterface { - claimRoles = append(claimRoles, v.(string)) + claimRoles = append(claimRoles, v) } if params != nil && params.Roles != nil && len(params.Roles) > 0 { @@ -74,22 +53,35 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod } } - // delete older session - sessionstore.DeleteUserSession(userID, fingerPrint) + scope := []string{"openid", "email", "profile"} + if params != nil && params.Scope != nil && len(scope) > 0 { + scope = params.Scope + } - authToken, err := token.CreateAuthToken(user, claimRoles) + authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope) if err != nil { return res, err } - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) + // 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) + cookie.SetSession(gc, authToken.FingerPrintHash) + + expiresIn := int64(1800) res = &model.AuthResponse{ Message: `Session token refreshed`, AccessToken: &authToken.AccessToken.Token, - ExpiresAt: &authToken.AccessToken.ExpiresAt, + ExpiresIn: &expiresIn, + IDToken: &authToken.IDToken.Token, User: user.AsAPIUser(), } + if authToken.RefreshToken != nil { + res.RefreshToken = &authToken.RefreshToken.Token + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + } + return res, nil } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 0ce3953..69e57c3 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -123,41 +123,48 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR hostname := utils.GetHost(gc) if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request - verificationType := constants.VerificationTypeBasicAuthSignup - verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) + nonce, nonceHash, err := utils.GenerateNonce() if err != nil { - log.Println(`error generating token`, err) + return res, err + } + verificationType := constants.VerificationTypeBasicAuthSignup + verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) + if err != nil { + return res, err } db.Provider.AddVerificationRequest(models.VerificationRequest{ Token: verificationToken, Identifier: verificationType, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: params.Email, + Nonce: nonce, }) // exec it as go routin so that we can reduce the api latency - go func() { - email.SendVerificationMail(params.Email, verificationToken, hostname) - }() + go email.SendVerificationMail(params.Email, verificationToken, hostname) res = &model.AuthResponse{ Message: `Verification email has been sent. Please check your inbox`, User: userToReturn, } } else { + scope := []string{"openid", "email", "profile"} - authToken, err := token.CreateAuthToken(user, roles) + authToken, err := token.CreateAuthToken(gc, user, roles, scope) if err != nil { return res, err } - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) - utils.SaveSessionInDB(user.ID, gc) + + sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + cookie.SetSession(gc, authToken.FingerPrintHash) + go utils.SaveSessionInDB(gc, user.ID) + + expiresIn := int64(1800) res = &model.AuthResponse{ Message: `Signed up successfully.`, AccessToken: &authToken.AccessToken.Token, - ExpiresAt: &authToken.AccessToken.ExpiresAt, + ExpiresIn: &expiresIn, User: userToReturn, } } diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index 2fef3e3..cc43f4c 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -13,6 +13,7 @@ import ( "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" "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/token" @@ -28,7 +29,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) return res, err } - claims, err := token.ValidateAccessToken(gc) + accessToken, err := token.GetAccessToken(gc) + if err != nil { + return res, err + } + claims, err := token.ValidateAccessToken(gc, accessToken) if err != nil { return res, err } @@ -38,8 +43,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) return res, fmt.Errorf("please enter at least one param to update") } - userEmail := fmt.Sprintf("%v", claims["email"]) - user, err := db.Provider.GetUserByEmail(userEmail) + userID := claims["sub"].(string) + user, err := db.Provider.GetUserByID(userID) if err != nil { return res, err } @@ -108,38 +113,44 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) newEmail := strings.ToLower(*params.Email) // check if user with new email exists _, err := db.Provider.GetUserByEmail(newEmail) - // err = nil means user exists if err == nil { return res, fmt.Errorf("user with this email address already exists") } - sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) - cookie.DeleteCookie(gc) + // TODO figure out how to delete all user sessions + go sessionstore.DeleteAllUserSession(user.ID) - hostname := utils.GetHost(gc) + cookie.DeleteSession(gc) user.Email = newEmail - user.EmailVerifiedAt = nil - hasEmailChanged = true - // insert verification request - verificationType := constants.VerificationTypeUpdateEmail - verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) - if err != nil { - log.Println(`error generating token`, err) + + if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { + hostname := utils.GetHost(gc) + user.EmailVerifiedAt = nil + hasEmailChanged = true + // insert verification request + nonce, nonceHash, err := utils.GenerateNonce() + if err != nil { + return res, err + } + verificationType := constants.VerificationTypeUpdateEmail + verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) + if err != nil { + log.Println(`error generating token`, err) + } + db.Provider.AddVerificationRequest(models.VerificationRequest{ + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: newEmail, + Nonce: nonce, + }) + + // exec it as go routin so that we can reduce the api latency + go email.SendVerificationMail(newEmail, verificationToken, hostname) + } - db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: newEmail, - }) - - // exec it as go routin so that we can reduce the api latency - go func() { - email.SendVerificationMail(newEmail, verificationToken, hostname) - }() } - _, err = db.Provider.UpdateUser(user) if err != nil { log.Println("error updating user:", err) diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index 40b4206..948565e 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -8,7 +8,6 @@ import ( "time" "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/email" @@ -95,15 +94,19 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod return res, fmt.Errorf("user with this email address already exists") } - sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) - cookie.DeleteCookie(gc) + // TODO figure out how to do this + go sessionstore.DeleteAllUserSession(user.ID) hostname := utils.GetHost(gc) user.Email = newEmail user.EmailVerifiedAt = nil // insert verification request + nonce, nonceHash, err := utils.GenerateNonce() + if err != nil { + return res, err + } verificationType := constants.VerificationTypeUpdateEmail - verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) + verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) if err != nil { log.Println(`error generating token`, err) } @@ -112,12 +115,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod Identifier: verificationType, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: newEmail, + Nonce: nonce, }) // exec it as go routin so that we can reduce the api latency - go func() { - email.SendVerificationMail(newEmail, verificationToken, hostname) - }() + go email.SendVerificationMail(newEmail, verificationToken, hostname) + } rolesToSave := "" @@ -136,8 +139,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod rolesToSave = strings.Join(inputRoles, ",") } - sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) - cookie.DeleteCookie(gc) + go sessionstore.DeleteAllUserSession(user.ID) } if rolesToSave != "" { diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index c244992..953db47 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -28,12 +28,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m } // verify if token exists in db - claim, err := token.ParseJWTToken(params.Token) + hostname := utils.GetHost(gc) + encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) + if err != nil { + return res, err + } + claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email) if err != nil { return res, fmt.Errorf(`invalid token: %s`, err.Error()) } - user, err := db.Provider.GetUserByEmail(claim["email"].(string)) + user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) if err != nil { return res, err } @@ -41,25 +46,35 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m // update email_verified_at in users table now := time.Now().Unix() user.EmailVerifiedAt = &now - db.Provider.UpdateUser(user) - // delete from verification table - db.Provider.DeleteVerificationRequest(verificationRequest) - - roles := strings.Split(user.Roles, ",") - authToken, err := token.CreateAuthToken(user, roles) + user, err = db.Provider.UpdateUser(user) + if err != nil { + return res, err + } + // delete from verification table + err = db.Provider.DeleteVerificationRequest(verificationRequest) if err != nil { return res, err } - sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) - cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) - utils.SaveSessionInDB(user.ID, gc) + roles := strings.Split(user.Roles, ",") + scope := []string{"openid", "email", "profile"} + authToken, err := token.CreateAuthToken(gc, user, roles, scope) + if err != nil { + return res, err + } + + 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) + + expiresIn := int64(1800) res = &model.AuthResponse{ Message: `Email verified successfully.`, AccessToken: &authToken.AccessToken.Token, - ExpiresAt: &authToken.AccessToken.ExpiresAt, + IDToken: &authToken.IDToken.Token, + ExpiresIn: &expiresIn, User: user.AsAPIUser(), } - return res, nil } diff --git a/server/sessionstore/in_memory_session.go b/server/sessionstore/in_memory_session.go index 567c881..edc6924 100644 --- a/server/sessionstore/in_memory_session.go +++ b/server/sessionstore/in_memory_session.go @@ -1,6 +1,7 @@ package sessionstore import ( + "strings" "sync" ) @@ -11,42 +12,6 @@ type InMemoryStore struct { stateStore map[string]string } -// AddUserSession adds a user session to the in-memory store. -func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) { - c.mutex.Lock() - defer c.mutex.Unlock() - // delete sessions > 500 // not recommended for production - if len(c.sessionStore) >= 500 { - c.sessionStore = map[string]map[string]string{} - } - // check if entry exists in map - _, exists := c.sessionStore[userId] - if exists { - tempMap := c.sessionStore[userId] - tempMap[accessToken] = refreshToken - c.sessionStore[userId] = tempMap - } else { - tempMap := map[string]string{ - accessToken: refreshToken, - } - c.sessionStore[userId] = tempMap - } -} - -// DeleteAllUserSession deletes all the user sessions from in-memory store. -func (c *InMemoryStore) DeleteAllUserSession(userId string) { - c.mutex.Lock() - defer c.mutex.Unlock() - delete(c.sessionStore, userId) -} - -// DeleteUserSession deletes the particular user session from in-memory store. -func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) { - c.mutex.Lock() - defer c.mutex.Unlock() - delete(c.sessionStore[userId], accessToken) -} - // ClearStore clears the in-memory store. func (c *InMemoryStore) ClearStore() { c.mutex.Lock() @@ -54,32 +19,29 @@ func (c *InMemoryStore) ClearStore() { c.sessionStore = map[string]map[string]string{} } -// GetUserSession returns the user session token from the in-memory store. -func (c *InMemoryStore) GetUserSession(userId, accessToken string) string { - // c.mutex.Lock() - // defer c.mutex.Unlock() - - token := "" - if sessionMap, ok := c.sessionStore[userId]; ok { - if val, ok := sessionMap[accessToken]; ok { - token = val - } - } - - return token -} - // GetUserSessions returns all the user session token from the in-memory store. func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { // c.mutex.Lock() // defer c.mutex.Unlock() - - sessionMap, ok := c.sessionStore[userId] - if !ok { - return nil + res := map[string]string{} + for k, v := range c.stateStore { + split := strings.Split(v, "@") + if split[1] == userId { + res[k] = split[0] + } } - return sessionMap + return res +} + +// DeleteAllUserSession deletes all the user sessions from in-memory store. +func (c *InMemoryStore) DeleteAllUserSession(userId string) { + // c.mutex.Lock() + // defer c.mutex.Unlock() + sessions := GetUserSessions(userId) + for k := range sessions { + RemoveState(k) + } } // SetState sets the state in the in-memory store. diff --git a/server/sessionstore/redis_store.go b/server/sessionstore/redis_store.go index 98a1711..7e48335 100644 --- a/server/sessionstore/redis_store.go +++ b/server/sessionstore/redis_store.go @@ -2,8 +2,8 @@ package sessionstore import ( "context" - "fmt" "log" + "strings" ) type RedisStore struct { @@ -11,32 +11,6 @@ type RedisStore struct { store RedisSessionClient } -// AddUserSession adds the user session to redis -func (c *RedisStore) AddUserSession(userId, accessToken, refreshToken string) { - err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{ - accessToken: refreshToken, - }).Err() - if err != nil { - log.Fatalln("Error saving redis token:", err) - } -} - -// DeleteAllUserSession deletes all the user session from redis -func (c *RedisStore) DeleteAllUserSession(userId string) { - err := c.store.Del(c.ctx, "authorizer_"+userId).Err() - if err != nil { - log.Fatalln("Error deleting redis token:", err) - } -} - -// DeleteUserSession deletes the particular user session from redis -func (c *RedisStore) DeleteUserSession(userId, accessToken string) { - err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err() - if err != nil { - log.Fatalln("Error deleting redis token:", err) - } -} - // ClearStore clears the redis store for authorizer related tokens func (c *RedisStore) ClearStore() { err := c.store.Del(c.ctx, "authorizer_*").Err() @@ -45,32 +19,40 @@ func (c *RedisStore) ClearStore() { } } -// GetUserSession returns the user session token from the redis store. -func (c *RedisStore) GetUserSession(userId, accessToken string) string { - token := "" - res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result() - if err != nil { - log.Println("error getting token from redis store:", err) - } - if len(res) > 0 && res[0] != nil { - token = fmt.Sprintf("%v", res[0]) - } - return token -} - // GetUserSessions returns all the user session token from the redis store. func (c *RedisStore) GetUserSessions(userID string) map[string]string { - res, err := c.store.HGetAll(c.ctx, "authorizer_"+userID).Result() + data, err := c.store.HGetAll(c.ctx, "*").Result() if err != nil { log.Println("error getting token from redis store:", err) } + res := map[string]string{} + for k, v := range data { + split := strings.Split(v, "@") + if split[1] == userID { + res[k] = split[0] + } + } + return res } +// DeleteAllUserSession deletes all the user session from redis +func (c *RedisStore) DeleteAllUserSession(userId string) { + sessions := GetUserSessions(userId) + for k, v := range sessions { + if k == "token" { + err := c.store.Del(c.ctx, v) + if err != nil { + log.Println("Error deleting redis token:", err) + } + } + } +} + // SetState sets the state in redis store. -func (c *RedisStore) SetState(key, state string) { - err := c.store.Set(c.ctx, key, state, 0).Err() +func (c *RedisStore) SetState(key, value string) { + err := c.store.Set(c.ctx, key, value, 0).Err() if err != nil { log.Fatalln("Error saving redis token:", err) } diff --git a/server/sessionstore/session.go b/server/sessionstore/session.go index f33638c..659ddaa 100644 --- a/server/sessionstore/session.go +++ b/server/sessionstore/session.go @@ -22,26 +22,6 @@ type SessionStore struct { // reference to various session store instances var SessionStoreObj SessionStore -// SetUserSession sets the user session in the session store -func SetUserSession(userId, fingerprint, refreshToken string) { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken) - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken) - } -} - -// DeleteUserSession deletes the particular user session from the session store -func DeleteUserSession(userId, fingerprint string) { - if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, fingerprint) - } - if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, fingerprint) - } -} - // DeleteAllSessions deletes all the sessions from the session store func DeleteAllUserSession(userId string) { if SessionStoreObj.RedisMemoryStoreObj != nil { @@ -52,18 +32,6 @@ func DeleteAllUserSession(userId string) { } } -// GetUserSession returns the user session from the session store -func GetUserSession(userId, fingerprint string) string { - if SessionStoreObj.RedisMemoryStoreObj != nil { - return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, fingerprint) - } - if SessionStoreObj.InMemoryStoreObj != nil { - return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, fingerprint) - } - - return "" -} - // GetUserSessions returns all the user sessions from the session store func GetUserSessions(userId string) map[string]string { if SessionStoreObj.RedisMemoryStoreObj != nil { diff --git a/server/test/is_valid_jwt_test.go b/server/test/is_valid_jwt_test.go deleted file mode 100644 index 12abbc1..0000000 --- a/server/test/is_valid_jwt_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package test - -import ( - "testing" - - "github.com/authorizerdev/authorizer/server/db/models" - "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/token" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func isValidJWTTests(t *testing.T, s TestSetup) { - t.Helper() - _, ctx := createContext(s) - expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs" - - t.Run(`should fail for invalid jwt`, func(t *testing.T) { - _, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{ - Jwt: &expiredToken, - }) - assert.NotNil(t, err) - }) - - t.Run(`should pass with valid jwt`, func(t *testing.T) { - authToken, err := token.CreateAuthToken(models.User{ - ID: uuid.New().String(), - Email: "john.doe@gmail.com", - }, []string{}) - assert.Nil(t, err) - res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{ - Jwt: &authToken.AccessToken.Token, - }) - assert.Nil(t, err) - assert.True(t, res.Valid) - }) -} diff --git a/server/test/jwt_test.go b/server/test/jwt_test.go index a72cedf..71f74be 100644 --- a/server/test/jwt_test.go +++ b/server/test/jwt_test.go @@ -9,6 +9,7 @@ import ( "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/token" "github.com/golang-jwt/jwt" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) @@ -18,12 +19,17 @@ func TestJwt(t *testing.T) { publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey) privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey) clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) + nonce := uuid.New().String() + hostname := "localhost" + subject := "test" claims := jwt.MapClaims{ "exp": time.Now().Add(time.Minute * 30).Unix(), "iat": time.Now().Unix(), "email": "test@yopmail.com", - "sub": "test", + "sub": subject, "aud": clientID, + "nonce": nonce, + "iss": hostname, } t.Run("invalid jwt type", func(t *testing.T) { @@ -42,7 +48,7 @@ func TestJwt(t *testing.T) { } jwtToken, err := token.SignJWTToken(expiredClaims) assert.NoError(t, err) - _, err = token.ParseJWTToken(jwtToken) + _, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.Error(t, err, err.Error(), "Token is expired") }) t.Run("HMAC algorithms", func(t *testing.T) { @@ -52,7 +58,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -61,7 +67,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -70,7 +76,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -86,7 +92,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -99,7 +105,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -112,7 +118,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -128,7 +134,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -141,7 +147,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) @@ -154,7 +160,7 @@ func TestJwt(t *testing.T) { jwtToken, err := token.SignJWTToken(claims) assert.NoError(t, err) assert.NotEmpty(t, jwtToken) - c, err := token.ParseJWTToken(jwtToken) + c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) assert.NoError(t, err) assert.Equal(t, c["email"].(string), claims["email"]) }) diff --git a/server/test/login_test.go b/server/test/login_test.go index 4c25f5a..ebfbf68 100644 --- a/server/test/login_test.go +++ b/server/test/login_test.go @@ -5,14 +5,17 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) func loginTests(t *testing.T, s TestSetup) { t.Helper() t.Run(`should login`, func(t *testing.T) { + t.Logf("=> is enabled: %v", envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)) _, ctx := createContext(s) email := "login." + s.TestInfo.Email _, err := resolvers.SignupResolver(ctx, model.SignUpInput{ @@ -21,15 +24,19 @@ func loginTests(t *testing.T, s TestSetup) { ConfirmPassword: s.TestInfo.Password, }) - _, err = resolvers.LoginResolver(ctx, model.LoginInput{ + res, err := resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password, }) assert.NotNil(t, err, "should fail because email is not verified") - + assert.Nil(t, res) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup) - res, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ + n, err := utils.EncryptNonce(verificationRequest.Nonce) + assert.NoError(t, err) + assert.NotEmpty(t, n) + assert.NotNil(t, verificationRequest) + res, err = resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) assert.NoError(t, err) diff --git a/server/test/logout_test.go b/server/test/logout_test.go index db5688a..8956b31 100644 --- a/server/test/logout_test.go +++ b/server/test/logout_test.go @@ -2,11 +2,9 @@ package test import ( "fmt" - "net/url" "testing" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" @@ -30,18 +28,15 @@ func logoutTests(t *testing.T, s TestSetup) { Token: verificationRequest.Token, }) - sessions := sessionstore.GetUserSessions(verifyRes.User.ID) - fingerPrint := "" - refreshToken := "" - for key, val := range sessions { - fingerPrint = key - refreshToken = val - } - - fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint)) - token := *verifyRes.AccessToken - cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) + sessions := sessionstore.GetUserSessions(verifyRes.User.ID) + cookie := "" + // set all they keys in cookie one of them should be session cookie + for key := range sessions { + if key != token { + cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key) + } + } req.Header.Set("Cookie", cookie) _, err = resolvers.LogoutResolver(ctx) diff --git a/server/test/magic_link_login_test.go b/server/test/magic_link_login_test.go index b42378f..95e0712 100644 --- a/server/test/magic_link_login_test.go +++ b/server/test/magic_link_login_test.go @@ -1,12 +1,11 @@ package test import ( - "fmt" + "context" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" @@ -27,12 +26,13 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) { verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) - - token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) + assert.NoError(t, err) + assert.NotNil(t, verifyRes.AccessToken) + s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken) + ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) _, err = resolvers.ProfileResolver(ctx) assert.Nil(t, err) - + s.GinContext.Request.Header.Set("Authorization", "") cleanData(email) }) } diff --git a/server/test/profile_test.go b/server/test/profile_test.go index 4801afc..8f5a283 100644 --- a/server/test/profile_test.go +++ b/server/test/profile_test.go @@ -1,12 +1,11 @@ package test import ( - "fmt" + "context" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" @@ -14,7 +13,7 @@ import ( func profileTests(t *testing.T, s TestSetup) { t.Helper() - t.Run(`should get profile only with token`, func(t *testing.T) { + t.Run(`should get profile only access_token token`, func(t *testing.T) { req, ctx := createContext(s) email := "profile." + s.TestInfo.Email @@ -31,11 +30,14 @@ func profileTests(t *testing.T, s TestSetup) { verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) + assert.NoError(t, err) + assert.NotNil(t, verifyRes.AccessToken) - token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) + s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken) + ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) profileRes, err := resolvers.ProfileResolver(ctx) assert.Nil(t, err) + s.GinContext.Request.Header.Set("Authorization", "") newEmail := *&profileRes.Email assert.Equal(t, email, newEmail, "emails should be equal") diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index a39f3b5..f47e034 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -15,15 +15,16 @@ func TestResolvers(t *testing.T) { // constants.DbTypeArangodb: "http://localhost:8529", // constants.DbTypeMongodb: "mongodb://localhost:27017", } - envstore.EnvStoreObj.ResetStore() envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test") for dbType, dbURL := range databases { + s := testSetup() envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) - - s := testSetup() defer s.Server.Close() - db.InitDB() + err := db.InitDB() + if err != nil { + t.Errorf("Error initializing database: %s", err) + } // clean the persisted config for test to use fresh config envData, err := db.Provider.GetEnv() @@ -31,12 +32,10 @@ func TestResolvers(t *testing.T) { envData.EnvData = "" db.Provider.UpdateEnv(envData) } - err = env.InitAllEnv() - if err != nil { - t.Error(err) - } env.PersistEnv() + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnv, "test") + envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyIsProd, false) t.Run("should pass tests for "+dbType, func(t *testing.T) { // admin tests adminSignupTests(t, s) @@ -63,7 +62,6 @@ func TestResolvers(t *testing.T) { magicLinkLoginTests(t, s) logoutTests(t, s) metaTests(t, s) - isValidJWTTests(t, s) }) } } diff --git a/server/test/session_test.go b/server/test/session_test.go index 99b1295..65ce57e 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -2,11 +2,10 @@ package test import ( "fmt" - "net/url" + "strings" "testing" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" @@ -36,17 +35,15 @@ func sessionTests(t *testing.T, s TestSetup) { }) sessions := sessionstore.GetUserSessions(verifyRes.User.ID) - fingerPrint := "" - refreshToken := "" - for key, val := range sessions { - fingerPrint = key - refreshToken = val - } - - fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint)) - + cookie := "" token := *verifyRes.AccessToken - cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) + // set all they keys in cookie one of them should be session cookie + for key := range sessions { + if key != token { + cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key) + } + } + cookie = strings.TrimSuffix(cookie, ";") req.Header.Set("Cookie", cookie) diff --git a/server/test/test.go b/server/test/test.go index e99b7cd..6c39cc6 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -72,13 +72,13 @@ func testSetup() TestSetup { } envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") + env.InitRequiredEnv() 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"}) - env.InitRequiredEnv() db.InitDB() env.InitAllEnv() sessionstore.InitSession() diff --git a/server/test/update_profile_test.go b/server/test/update_profile_test.go index 04d1785..68dae47 100644 --- a/server/test/update_profile_test.go +++ b/server/test/update_profile_test.go @@ -1,12 +1,11 @@ package test import ( - "fmt" + "context" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" @@ -34,18 +33,16 @@ func updateProfileTests(t *testing.T, s TestSetup) { verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) + assert.NoError(t, err) - token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) - _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ - FamilyName: &fName, - }) - assert.Nil(t, err) + s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken) + ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) newEmail := "new_" + email _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ Email: &newEmail, }) + s.GinContext.Request.Header.Set("Authorization", "") assert.Nil(t, err) _, err = resolvers.ProfileResolver(ctx) assert.NotNil(t, err, "unauthorized") diff --git a/server/test/verification_requests_test.go b/server/test/verification_requests_test.go index 58891a0..b81a35f 100644 --- a/server/test/verification_requests_test.go +++ b/server/test/verification_requests_test.go @@ -19,12 +19,15 @@ func verificationRequestsTest(t *testing.T, s TestSetup) { req, ctx := createContext(s) email := "verification_requests." + s.TestInfo.Email - resolvers.SignupResolver(ctx, model.SignUpInput{ + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) + assert.NoError(t, err) + assert.NotNil(t, res) + limit := int64(10) page := int64(1) pagination := &model.PaginatedInput{ diff --git a/server/token/auth_token.go b/server/token/auth_token.go index b9eb25d..16abbd6 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -2,23 +2,23 @@ package token import ( "encoding/json" - "errors" "fmt" "log" "os" "strings" "time" - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/cookie" - "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/gin-gonic/gin" "github.com/golang-jwt/jwt" "github.com/google/uuid" "github.com/robertkrimen/otto" + + "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/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/utils" ) // JWTToken is a struct to hold JWT token and its expiration time @@ -33,60 +33,219 @@ type Token struct { FingerPrintHash string `json:"fingerprint_hash"` RefreshToken *JWTToken `json:"refresh_token"` AccessToken *JWTToken `json:"access_token"` + IDToken *JWTToken `json:"id_token"` +} + +// SessionData +type SessionData struct { + Subject string `json:"sub"` + Roles []string `json:"roles"` + Scope []string `json:"scope"` + Nonce string `json:"nonce"` + IssuedAt int64 `json:"iat"` + ExpiresAt int64 `json:"exp"` +} + +// CreateSessionToken creates a new session token +func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) { + fingerPrintMap := &SessionData{ + Nonce: nonce, + Roles: roles, + Subject: user.ID, + Scope: scope, + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(), + } + fingerPrintBytes, _ := json.Marshal(fingerPrintMap) + fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes)) + if err != nil { + return nil, "", err + } + + return fingerPrintMap, fingerPrintHash, nil } // CreateAuthToken creates a new auth token when userlogs in -func CreateAuthToken(user models.User, roles []string) (*Token, error) { - fingerprint := uuid.NewString() - fingerPrintHashBytes, err := crypto.EncryptAES([]byte(fingerprint)) +func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) { + hostname := utils.GetHost(gc) + nonce := uuid.New().String() + _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope) if err != nil { return nil, err } - refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles) + accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce) if err != nil { return nil, err } - accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles) + idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce) if err != nil { return nil, err } - return &Token{ - FingerPrint: fingerprint, - FingerPrintHash: string(fingerPrintHashBytes), - RefreshToken: &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt}, + res := &Token{ + FingerPrint: nonce, + FingerPrintHash: fingerPrintHash, AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt}, - }, nil + IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt}, + } + + if utils.StringSliceContains(scope, "offline_access") { + refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce) + if err != nil { + return nil, err + } + + res.RefreshToken = &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt} + } + + return res, nil } // CreateRefreshToken util to create JWT token -func CreateRefreshToken(user models.User, roles []string) (string, int64, error) { +func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) { // expires in 1 year expiryBound := time.Hour * 8760 expiresAt := time.Now().Add(expiryBound).Unix() - customClaims := jwt.MapClaims{ - "iss": "", - "aud": "", + "iss": hostname, + "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), "token_type": constants.TokenTypeRefreshToken, "roles": roles, - "id": user.ID, + "nonce": nonce, } token, err := SignJWTToken(customClaims) if err != nil { return "", 0, err } + return token, expiresAt, nil } // CreateAccessToken util to create JWT token, based on // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT -func CreateAccessToken(user models.User, roles []string) (string, int64, error) { +func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) { + expiryBound := time.Minute * 30 + expiresAt := time.Now().Add(expiryBound).Unix() + + customClaims := jwt.MapClaims{ + "iss": hostName, + "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), + "nonce": nonce, + "sub": user.ID, + "exp": expiresAt, + "iat": time.Now().Unix(), + "token_type": constants.TokenTypeAccessToken, + "scope": scopes, + "roles": roles, + } + + token, err := SignJWTToken(customClaims) + if err != nil { + return "", 0, err + } + + return token, expiresAt, nil +} + +// GetAccessToken returns the access token from the request (either from header or cookie) +func GetAccessToken(gc *gin.Context) (string, error) { + // try to check in auth header for cookie + auth := gc.Request.Header.Get("Authorization") + if auth == "" { + return "", fmt.Errorf(`unauthorized`) + } + + if !strings.HasPrefix(auth, "Bearer ") { + return "", fmt.Errorf(`not a bearer token`) + } + + token := strings.TrimPrefix(auth, "Bearer ") + return token, nil +} + +// Function to validate access token for authorizer apis (profile, update_profile) +func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interface{}, error) { + var res map[string]interface{} + + if accessToken == "" { + return res, fmt.Errorf(`unauthorized`) + } + + savedSession := sessionstore.GetState(accessToken) + if savedSession == "" { + return res, fmt.Errorf(`unauthorized`) + } + + savedSessionSplit := strings.Split(savedSession, "@") + nonce := savedSessionSplit[0] + userID := savedSessionSplit[1] + + hostname := utils.GetHost(gc) + res, err := ParseJWTToken(accessToken, hostname, nonce, userID) + if err != nil { + return res, err + } + + if res["token_type"] != constants.TokenTypeAccessToken { + return res, fmt.Errorf(`unauthorized: invalid token type`) + } + + return res, nil +} + +func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) { + if encryptedSession == "" { + return nil, fmt.Errorf(`unauthorized`) + } + + savedSession := sessionstore.GetState(encryptedSession) + if savedSession == "" { + return nil, fmt.Errorf(`unauthorized`) + } + + savedSessionSplit := strings.Split(savedSession, "@") + nonce := savedSessionSplit[0] + userID := savedSessionSplit[1] + + decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession) + if err != nil { + return nil, err + } + + var res SessionData + err = json.Unmarshal([]byte(decryptedFingerPrint), &res) + if err != nil { + return nil, err + } + + if res.Nonce != nonce { + return nil, fmt.Errorf(`unauthorized: invalid nonce`) + } + + if res.Subject != userID { + return nil, fmt.Errorf(`unauthorized: invalid user id`) + } + + if res.ExpiresAt < time.Now().Unix() { + return nil, fmt.Errorf(`unauthorized: token expired`) + } + + // TODO validate scope + // if !reflect.DeepEqual(res.Roles, roles) { + // return res, "", fmt.Errorf(`unauthorized`) + // } + + return &res, nil +} + +// CreateIDToken util to create JWT token, based on +// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT +func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) { expiryBound := time.Minute * 30 expiresAt := time.Now().Add(expiryBound).Unix() @@ -97,13 +256,13 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error) claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) customClaims := jwt.MapClaims{ - "iss": "", + "iss": hostname, "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), - "nonce": "", + "nonce": nonce, "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), - "token_type": constants.TokenTypeAccessToken, + "token_type": constants.TokenTypeIdentityToken, "allowed_roles": strings.Split(user.Roles, ","), claimKey: roles, } @@ -152,58 +311,18 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error) return token, expiresAt, nil } -// GetAccessToken returns the access token from the request (either from header or cookie) -func GetAccessToken(gc *gin.Context) (string, error) { - token, err := cookie.GetAccessTokenCookie(gc) - if err != nil || token == "" { - // try to check in auth header for cookie - auth := gc.Request.Header.Get("Authorization") - if auth == "" { - return "", fmt.Errorf(`unauthorized`) - } - - token = strings.TrimPrefix(auth, "Bearer ") - - } - return token, nil -} - -// GetRefreshToken returns the refresh token from cookie / request query url -func GetRefreshToken(gc *gin.Context) (string, error) { - token, err := cookie.GetRefreshTokenCookie(gc) - - if err != nil || token == "" { +// GetIDToken returns the id token from the request header +func GetIDToken(gc *gin.Context) (string, error) { + // try to check in auth header for cookie + auth := gc.Request.Header.Get("Authorization") + if auth == "" { return "", fmt.Errorf(`unauthorized`) } + if !strings.HasPrefix(auth, "Bearer ") { + return "", fmt.Errorf(`not a bearer token`) + } + + token := strings.TrimPrefix(auth, "Bearer ") return token, nil } - -// GetFingerPrint returns the finger print from cookie -func GetFingerPrint(gc *gin.Context) (string, error) { - fingerPrint, err := cookie.GetFingerPrintCookie(gc) - if err != nil || fingerPrint == "" { - return "", fmt.Errorf(`no finger print`) - } - return fingerPrint, nil -} - -func ValidateAccessToken(gc *gin.Context) (map[string]interface{}, error) { - token, err := GetAccessToken(gc) - if err != nil { - return nil, err - } - - claims, err := ParseJWTToken(token) - if err != nil { - return nil, err - } - - // also validate if there is user session present with access token - sessions := sessionstore.GetUserSessions(claims["id"].(string)) - if len(sessions) == 0 { - return nil, errors.New("unauthorized") - } - - return claims, nil -} diff --git a/server/token/jwt.go b/server/token/jwt.go index 6631ffe..90f6333 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -44,7 +44,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) { } // ParseJWTToken common util to parse jwt token -func ParseJWTToken(token string) (jwt.MapClaims, error) { +func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) { jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) signingMethod := jwt.GetSigningMethod(jwtType) @@ -87,5 +87,21 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) { claims["exp"] = intExp claims["iat"] = intIat + if claims["aud"] != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) { + return claims, errors.New("invalid audience") + } + + if claims["nonce"] != nonce { + return claims, errors.New("invalid nonce") + } + + if claims["iss"] != hostname { + return claims, errors.New("invalid issuer") + } + + if claims["sub"] != subject { + return claims, errors.New("invalid subject") + } + return claims, nil } diff --git a/server/token/verification_token.go b/server/token/verification_token.go index ca9a64b..7765392 100644 --- a/server/token/verification_token.go +++ b/server/token/verification_token.go @@ -9,13 +9,15 @@ import ( ) // CreateVerificationToken creates a verification JWT token -func CreateVerificationToken(email, tokenType, hostname string) (string, error) { +func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) { claims := jwt.MapClaims{ + "iss": hostname, + "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), + "sub": email, "exp": time.Now().Add(time.Minute * 30).Unix(), "iat": time.Now().Unix(), "token_type": tokenType, - "email": email, - "host": hostname, + "nonce": nonceHash, "redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), } diff --git a/server/utils/common.go b/server/utils/common.go index 2438d3b..d4a8d51 100644 --- a/server/utils/common.go +++ b/server/utils/common.go @@ -20,7 +20,7 @@ func StringSliceContains(s []string, e string) bool { // SaveSessionInDB saves sessions generated for a given user with meta information // Do not store token here as that could be security breach -func SaveSessionInDB(userId string, c *gin.Context) { +func SaveSessionInDB(c *gin.Context, userId string) { sessionData := models.Session{ UserID: userId, UserAgent: GetUserAgent(c.Request), diff --git a/server/utils/nonce.go b/server/utils/nonce.go new file mode 100644 index 0000000..311b325 --- /dev/null +++ b/server/utils/nonce.go @@ -0,0 +1,36 @@ +package utils + +import ( + "github.com/google/uuid" + + "github.com/authorizerdev/authorizer/server/crypto" +) + +// GenerateNonce generats random nonce string and returns +// the nonce string, nonce hash, error +func GenerateNonce() (string, string, error) { + nonce := uuid.New().String() + nonceHash, err := crypto.EncryptAES(nonce) + if err != nil { + return "", "", err + } + return nonce, nonceHash, err +} + +// EncryptNonce nonce string +func EncryptNonce(nonce string) (string, error) { + nonceHash, err := crypto.EncryptAES(nonce) + if err != nil { + return "", err + } + return nonceHash, err +} + +// DecryptNonce nonce string +func DecryptNonce(nonceHash string) (string, error) { + nonce, err := crypto.DecryptAES(nonceHash) + if err != nil { + return "", err + } + return nonce, err +} From 5c7d32ec163370cd31b1fc31e4905b694a945556 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 3 Mar 2022 09:21:48 +0530 Subject: [PATCH 11/41] fix: remove compat cookie --- server/cookie/cookie.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/server/cookie/cookie.go b/server/cookie/cookie.go index 42445fb..20103b0 100644 --- a/server/cookie/cookie.go +++ b/server/cookie/cookie.go @@ -26,11 +26,6 @@ func SetSession(gc *gin.Context, sessionID string) { gc.SetSameSite(http.SameSiteNoneMode) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly) - - // Fallback cookie for anomaly getection on browsers that don’t support the sameSite=None attribute. - gc.SetSameSite(http.SameSiteDefaultMode) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", sessionID, year, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain_compat", sessionID, year, "/", domain, secure, httpOnly) } // DeleteSession sets session cookies to expire @@ -47,10 +42,6 @@ func DeleteSession(gc *gin.Context) { gc.SetSameSite(http.SameSiteNoneMode) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly) - - gc.SetSameSite(http.SameSiteDefaultMode) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", "", -1, "/", domain, secure, httpOnly) } // GetSession gets the session cookie from context @@ -61,14 +52,7 @@ func GetSession(gc *gin.Context) (string, error) { if err != nil { cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain") if err != nil { - cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_compat") - if err != nil { - cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain_compat") - } - - if err != nil { - return "", err - } + return "", err } } From 2946428ab82cdf8cba12c23f619e4c1710b825aa Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 4 Mar 2022 00:36:27 +0530 Subject: [PATCH 12/41] feat: add userinfo + logout --- server/crypto/common.go | 2 +- server/handlers/authorize.go | 205 +++++++++++++++++++++++++---- server/handlers/logout.go | 40 ++++++ server/handlers/userinfo.go | 40 ++++++ server/routes/routes.go | 3 + server/sessionstore/redis_store.go | 6 +- templates/authorize.tmpl | 4 +- 7 files changed, 266 insertions(+), 34 deletions(-) create mode 100644 server/handlers/logout.go create mode 100644 server/handlers/userinfo.go diff --git a/server/crypto/common.go b/server/crypto/common.go index df3754c..7bd3513 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -99,7 +99,7 @@ func EncryptEnvData(data envstore.Store) (string, error) { return "", err } - return EncryptB64(string(encryptedConfig)), nil + return string(encryptedConfig), nil } // EncryptPassword is used for encrypting password diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 67123cd..402f64a 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -5,7 +5,12 @@ import ( "net/http" "strings" + "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/authorizerdev/authorizer/server/token" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) // AuthorizeHandler is the handler for the /authorize route @@ -14,59 +19,203 @@ import ( // state[recommended] = to prevent CSRF attack (for authorizer its compulsory) // code_challenge = to prevent CSRF attack // code_challenge_method = to prevent CSRF attack [only sh256 is supported] + +// check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge. func AuthorizeHandler() gin.HandlerFunc { - return func(c *gin.Context) { - redirectURI := strings.TrimSpace(c.Query("redirect_uri")) - responseType := strings.TrimSpace(c.Query("response_type")) - state := strings.TrimSpace(c.Query("state")) - codeChallenge := strings.TrimSpace(c.Query("code_challenge")) - codeChallengeMethod := strings.TrimSpace(c.Query("code_challenge_method")) - fmt.Println(codeChallengeMethod) + return func(gc *gin.Context) { + redirectURI := strings.TrimSpace(gc.Query("redirect_uri")) + responseType := strings.TrimSpace(gc.Query("response_type")) + state := strings.TrimSpace(gc.Query("state")) + codeChallenge := strings.TrimSpace(gc.Query("code_challenge")) template := "authorize.tmpl" if redirectURI == "" { - c.HTML(http.StatusBadRequest, template, gin.H{ - "targetOrigin": nil, - "authorizationResponse": nil, - "error": "redirect_uri is required", + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "redirect_uri is required", + }, + }, }) return } if state == "" { - c.HTML(http.StatusBadRequest, template, gin.H{ - "targetOrigin": nil, - "authorizationResponse": nil, - "error": "state is required", + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "state is required", + }, + }, }) return } if responseType == "" { - responseType = "code" + responseType = "token" } - isCode := responseType == "code" - isToken := responseType == "token" + isResponseTypeCode := responseType == "code" + isResponseTypeToken := responseType == "token" - if !isCode && !isToken { - c.HTML(http.StatusBadRequest, template, gin.H{ - "targetOrigin": nil, - "authorizationResponse": nil, - "error": "response_type is invalid", + if !isResponseTypeCode && !isResponseTypeToken { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "response_type is invalid", + }, + }, }) return } - if isCode { + if isResponseTypeCode { if codeChallenge == "" { - c.HTML(http.StatusBadRequest, template, gin.H{ - "targetOrigin": nil, - "authorizationResponse": nil, - "error": "code_challenge is required", + gc.HTML(http.StatusBadRequest, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "code_challenge is required", + }, + }, }) return } } + + sessionToken, err := cookie.GetSession(gc) + if err != nil { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, + }, + }) + return + } + + // get session from cookie + claims, err := token.ValidateBrowserSession(gc, sessionToken) + if err != nil { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, + }, + }) + return + } + userID := claims.Subject + user, err := db.Provider.GetUserByID(userID) + if err != nil { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "signup_required", + "error_description": "Sign up required", + }, + }, + }) + return + } + + // if user is logged in + // based on the response type, generate the response + if isResponseTypeCode { + // rollover the session for security + sessionstore.RemoveState(sessionToken) + nonce := uuid.New().String() + newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, claims.Scope) + if err != nil { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, + }, + }) + return + } + + sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) + cookie.SetSession(gc, newSessionToken) + code := uuid.New().String() + sessionstore.SetState("code_challenge_"+codeChallenge, code) + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]string{ + "code": code, + "state": state, + }, + }) + return + } + + if isResponseTypeToken { + // rollover the session for security + authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope) + if err != nil { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, + }, + }) + return + } + sessionstore.RemoveState(sessionToken) + sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + cookie.SetSession(gc, authToken.FingerPrintHash) + expiresIn := int64(1800) + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "access_token": authToken.AccessToken.Token, + "id_token": authToken.IDToken.Token, + "state": state, + "scope": claims.Scope, + "expires_in": expiresIn, + }, + }) + return + } + fmt.Println("=> returning from here...") + + // by default return with error + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": nil, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, + }, + }) } } diff --git a/server/handlers/logout.go b/server/handlers/logout.go new file mode 100644 index 0000000..9ffc6cc --- /dev/null +++ b/server/handlers/logout.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "net/http" + + "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" + "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/gin-gonic/gin" +) + +func LogoutHandler() gin.HandlerFunc { + return func(gc *gin.Context) { + // get fingerprint hash + fingerprintHash, err := cookie.GetSession(gc) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + fingerPrint := string(decryptedFingerPrint) + + sessionstore.RemoveState(fingerPrint) + cookie.DeleteSession(gc) + + gc.JSON(http.StatusOK, gin.H{ + "message": "Logged out successfully", + }) + } +} diff --git a/server/handlers/userinfo.go b/server/handlers/userinfo.go new file mode 100644 index 0000000..9e9c6f5 --- /dev/null +++ b/server/handlers/userinfo.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "net/http" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/token" + "github.com/gin-gonic/gin" +) + +func UserInfoHandler() gin.HandlerFunc { + return func(gc *gin.Context) { + accessToken, err := token.GetAccessToken(gc) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + claims, err := token.ValidateAccessToken(gc, accessToken) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + userID := claims["sub"].(string) + user, err := db.Provider.GetUserByID(userID) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + gc.JSON(http.StatusOK, user.AsAPIUser()) + } +} diff --git a/server/routes/routes.go b/server/routes/routes.go index 0544431..a8599fc 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -23,6 +23,9 @@ func InitRouter() *gin.Engine { // OPEN ID routes router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler()) router.GET("/.well-known/jwks.json", handlers.JWKsHandler()) + router.GET("/authorize", handlers.AuthorizeHandler()) + router.GET("/userinfo", handlers.UserInfoHandler()) + router.GET("/logout", handlers.LogoutHandler()) router.LoadHTMLGlob("templates/*") // login page app related routes. diff --git a/server/sessionstore/redis_store.go b/server/sessionstore/redis_store.go index 7e48335..4fad694 100644 --- a/server/sessionstore/redis_store.go +++ b/server/sessionstore/redis_store.go @@ -52,7 +52,7 @@ func (c *RedisStore) DeleteAllUserSession(userId string) { // SetState sets the state in redis store. func (c *RedisStore) SetState(key, value string) { - err := c.store.Set(c.ctx, key, value, 0).Err() + err := c.store.Set(c.ctx, "authorizer_"+key, value, 0).Err() if err != nil { log.Fatalln("Error saving redis token:", err) } @@ -61,7 +61,7 @@ func (c *RedisStore) SetState(key, value string) { // GetState gets the state from redis store. func (c *RedisStore) GetState(key string) string { state := "" - state, err := c.store.Get(c.ctx, key).Result() + state, err := c.store.Get(c.ctx, "authorizer_"+key).Result() if err != nil { log.Println("error getting token from redis store:", err) } @@ -71,7 +71,7 @@ func (c *RedisStore) GetState(key string) string { // RemoveState removes the state from redis store. func (c *RedisStore) RemoveState(key string) { - err := c.store.Del(c.ctx, key).Err() + err := c.store.Del(c.ctx, "authorizer_"+key).Err() if err != nil { log.Fatalln("Error deleting redis token:", err) } diff --git a/templates/authorize.tmpl b/templates/authorize.tmpl index a42c15b..8842463 100644 --- a/templates/authorize.tmpl +++ b/templates/authorize.tmpl @@ -6,8 +6,8 @@ From 513b5d29485a9f0ae4325b9a196ea894c102be8e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 12:23:45 +0530 Subject: [PATCH 16/41] fix: env client secret --- dashboard/src/constants.ts | 2 ++ dashboard/src/graphql/queries/index.ts | 2 ++ dashboard/src/pages/Environment.tsx | 36 ++++++++++++++++++++++++++ server/env/env.go | 2 +- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts index fac0c0b..64fa372 100644 --- a/dashboard/src/constants.ts +++ b/dashboard/src/constants.ts @@ -2,6 +2,7 @@ export const LOGO_URL = 'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png'; export const TextInputType = { + CLIENT_ID: 'CLIENT_ID', GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID', GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID', FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', @@ -25,6 +26,7 @@ export const TextInputType = { }; export const HiddenInputType = { + CLIENT_SECRET: 'CLIENT_SECRET', GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET', GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET', FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET', diff --git a/dashboard/src/graphql/queries/index.ts b/dashboard/src/graphql/queries/index.ts index 3adedc9..b4efc70 100644 --- a/dashboard/src/graphql/queries/index.ts +++ b/dashboard/src/graphql/queries/index.ts @@ -9,6 +9,8 @@ export const AdminSessionQuery = ` export const EnvVariablesQuery = ` query { _env{ + CLIENT_ID, + CLIENT_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, diff --git a/dashboard/src/pages/Environment.tsx b/dashboard/src/pages/Environment.tsx index 58dbd8d..06480bc 100644 --- a/dashboard/src/pages/Environment.tsx +++ b/dashboard/src/pages/Environment.tsx @@ -240,6 +240,42 @@ export default function Environment() { return ( + + Your instance information + + + + + Client ID + +
+ {}} + inputType={TextInputType.CLIENT_ID} + placeholder="Client ID" + isDisabled={true} + /> +
+
+ + + Client Secret + +
+ +
+
+
+ Social Media Logins diff --git a/server/env/env.go b/server/env/env.go index a29914e..d430e2f 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -90,7 +90,7 @@ func InitAllEnv() error { clientSecret := envData.StringEnv[constants.EnvKeyClientSecret] // unique client id for each instance - if clientID == "" { + if clientSecret == "" { clientSecret = uuid.New().String() envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret } From eea63493182d89f2fb6de427ffa9c4da64a5f3dd Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 12:33:42 +0530 Subject: [PATCH 17/41] chore: update app version --- app/package-lock.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 6ed0766..57349c3 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "latest", + "@authorizerdev/authorizer-react": "0.7.0", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -24,9 +24,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz", - "integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", + "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", "dependencies": { "node-fetch": "^2.6.1" }, @@ -35,11 +35,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz", - "integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", + "integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", "dependencies": { - "@authorizerdev/authorizer-js": "^0.2.1", + "@authorizerdev/authorizer-js": "^0.3.0", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" @@ -829,19 +829,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz", - "integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", + "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", "requires": { "node-fetch": "^2.6.1" } }, "@authorizerdev/authorizer-react": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz", - "integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", + "integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", "requires": { - "@authorizerdev/authorizer-js": "^0.2.1", + "@authorizerdev/authorizer-js": "^0.3.0", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" From 136eda15bfbf269291c22ef7780ab1b3f78e02a0 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 15:29:37 +0530 Subject: [PATCH 18/41] fix: env encryption --- server/crypto/aes.go | 68 ++++++++++++++++++++++++++++++++++++++- server/crypto/common.go | 2 +- server/env/persist_env.go | 8 ++--- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/server/crypto/aes.go b/server/crypto/aes.go index 2750486..8d06ffb 100644 --- a/server/crypto/aes.go +++ b/server/crypto/aes.go @@ -3,12 +3,14 @@ package crypto import ( "crypto/aes" "crypto/cipher" + "crypto/rand" + "io" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" ) -var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} +var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5} // EncryptAES method is to encrypt or hide any classified text func EncryptAES(text string) (string, error) { @@ -40,3 +42,67 @@ func DecryptAES(text string) (string, error) { cfb.XORKeyStream(plainText, []byte(cipherText)) return string(plainText), nil } + +// EncryptAESEnv encrypts data using AES algorithm +// kept for the backward compatibility of env data encryption +func EncryptAESEnv(text []byte) ([]byte, error) { + key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) + c, err := aes.NewCipher(key) + var res []byte + if err != nil { + return res, err + } + + // gcm or Galois/Counter Mode, is a mode of operation + // for symmetric key cryptographic block ciphers + // - https://en.wikipedia.org/wiki/Galois/Counter_Mode + gcm, err := cipher.NewGCM(c) + if err != nil { + return res, err + } + + // creates a new byte array the size of the nonce + // which must be passed to Seal + nonce := make([]byte, gcm.NonceSize()) + // populates our nonce with a cryptographically secure + // random sequence + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return res, err + } + + // here we encrypt our text using the Seal function + // Seal encrypts and authenticates plaintext, authenticates the + // additional data and appends the result to dst, returning the updated + // slice. The nonce must be NonceSize() bytes long and unique for all + // time, for a given key. + return gcm.Seal(nonce, nonce, text, nil), nil +} + +// DecryptAES decrypts data using AES algorithm +// Kept for the backward compatibility of env data decryption +func DecryptAESEnv(ciphertext []byte) ([]byte, error) { + key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) + c, err := aes.NewCipher(key) + var res []byte + if err != nil { + return res, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return res, err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return res, err + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return res, err + } + + return plaintext, nil +} diff --git a/server/crypto/common.go b/server/crypto/common.go index 7bd3513..7056913 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -94,7 +94,7 @@ func EncryptEnvData(data envstore.Store) (string, error) { if err != nil { return "", err } - encryptedConfig, err := EncryptAES(string(configData)) + encryptedConfig, err := EncryptAESEnv(configData) if err != nil { return "", err } diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 24df138..a75f195 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -34,12 +34,12 @@ func GetEnvData() (envstore.Store, error) { envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - decryptedConfigs, err := crypto.DecryptAES(env.EnvData) + decryptedConfigs, err := crypto.DecryptAESEnv([]byte(env.EnvData)) if err != nil { return result, err } - err = json.Unmarshal([]byte(decryptedConfigs), &result) + err = json.Unmarshal(decryptedConfigs, &result) if err != nil { return result, err } @@ -82,7 +82,7 @@ func PersistEnv() error { envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - decryptedConfigs, err := crypto.DecryptAES(env.EnvData) + decryptedConfigs, err := crypto.DecryptAESEnv([]byte(env.EnvData)) if err != nil { return err } @@ -90,7 +90,7 @@ func PersistEnv() error { // temp store variable var storeData envstore.Store - err = json.Unmarshal([]byte(decryptedConfigs), &storeData) + err = json.Unmarshal(decryptedConfigs, &storeData) if err != nil { return err } From 4b25e8941ca0ac77123d08fd23e7845dc44f42a3 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 15:33:39 +0530 Subject: [PATCH 19/41] fix: env decryption --- server/env/persist_env.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/env/persist_env.go b/server/env/persist_env.go index a75f195..7c590df 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -2,6 +2,7 @@ package env import ( "encoding/json" + "fmt" "log" "os" "strconv" @@ -80,9 +81,16 @@ func PersistEnv() error { return err } + fmt.Println("decryptedEncryptionKey:", decryptedEncryptionKey) + envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - decryptedConfigs, err := crypto.DecryptAESEnv([]byte(env.EnvData)) + b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) + if err != nil { + return err + } + + decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig)) if err != nil { return err } From 1d61840c6d2cf34cb7c114dd56f9d8d5b639acb9 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 15:35:33 +0530 Subject: [PATCH 20/41] fix: env decryption + remove log --- server/env/persist_env.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 7c590df..1459a71 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -2,7 +2,6 @@ package env import ( "encoding/json" - "fmt" "log" "os" "strconv" @@ -35,7 +34,12 @@ func GetEnvData() (envstore.Store, error) { envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) - decryptedConfigs, err := crypto.DecryptAESEnv([]byte(env.EnvData)) + b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) + if err != nil { + return result, err + } + + decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig)) if err != nil { return result, err } @@ -81,8 +85,6 @@ func PersistEnv() error { return err } - fmt.Println("decryptedEncryptionKey:", decryptedEncryptionKey) - envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData) From 7b09a8817cc3a76cc106951085c2eac873fce214 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 16:16:54 +0530 Subject: [PATCH 21/41] fix: env encryption --- server/crypto/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/crypto/common.go b/server/crypto/common.go index 7056913..69f674c 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -99,7 +99,7 @@ func EncryptEnvData(data envstore.Store) (string, error) { return "", err } - return string(encryptedConfig), nil + return EncryptB64(string(encryptedConfig)), nil } // EncryptPassword is used for encrypting password From 128a2a8f75c49177ff309281389a0150371fd401 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 18:49:18 +0530 Subject: [PATCH 22/41] feat: add support for response mode --- server/crypto/common.go | 1 + server/env/persist_env.go | 2 +- server/handlers/authorize.go | 269 +++++++++++++++++++++-------------- templates/authorize.tmpl | 1 - 4 files changed, 163 insertions(+), 110 deletions(-) diff --git a/server/crypto/common.go b/server/crypto/common.go index 69f674c..35af515 100644 --- a/server/crypto/common.go +++ b/server/crypto/common.go @@ -94,6 +94,7 @@ func EncryptEnvData(data envstore.Store) (string, error) { if err != nil { return "", err } + encryptedConfig, err := EncryptAESEnv(configData) if err != nil { return "", err diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 1459a71..42ba969 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -112,7 +112,7 @@ func PersistEnv() error { for key, value := range storeData.StringEnv { // don't override unexposed envs - if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyClientSecret && key != constants.EnvKeyJWK { + if key != constants.EnvKeyEncryptionKey { // check only for derivative keys // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data // as we have removed it from json diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index c07bfd2..9dafafe 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -6,10 +6,12 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" + "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -17,6 +19,7 @@ import ( // AuthorizeHandler is the handler for the /authorize route // required params // ?redirect_uri = redirect url +// ?response_mode = to decide if result should be html or re-direct // state[recommended] = to prevent CSRF attack (for authorizer its compulsory) // code_challenge = to prevent CSRF attack // code_challenge_method = to prevent CSRF attack [only sh256 is supported] @@ -31,56 +34,74 @@ func AuthorizeHandler() gin.HandlerFunc { scopeString := strings.TrimSpace(gc.Query("scope")) clientID := strings.TrimSpace(gc.Query("client_id")) template := "authorize.tmpl" + responseMode := strings.TrimSpace(gc.Query("response_mode")) + + if responseMode == "" { + responseMode = "query" + } + + if responseMode != "query" && responseMode != "web_message" { + gc.JSON(400, gin.H{"error": "invalid response mode"}) + } + + if redirectURI == "" { + redirectURI = "/app" + } + + isQuery := responseMode == "query" + + hostname := utils.GetHost(gc) + loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `"}`) + loginURL := "/app?state=" + loginRedirectState if clientID == "" { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "client_id is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "client_id is required", + }, }, - }, - }) + }) + } return } if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "invalid_client_id", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "invalid_client_id", + }, }, - }, - }) - return - } - - if redirectURI == "" { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "redirect_uri is required", - }, - }, - }) + }) + } return } if state == "" { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "state is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "state is required", + }, }, - }, - }) + }) + } return } @@ -99,76 +120,96 @@ func AuthorizeHandler() gin.HandlerFunc { isResponseTypeToken := responseType == "token" if !isResponseTypeCode && !isResponseTypeToken { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "response_type is invalid", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "response_type is invalid", + }, }, - }, - }) + }) + } return } if isResponseTypeCode { if codeChallenge == "" { - gc.HTML(http.StatusBadRequest, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "code_challenge is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusBadRequest, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "code_challenge is required", + }, }, - }, - }) + }) + } return } } sessionToken, err := cookie.GetSession(gc) if err != nil { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "login_required", - "error_description": "Login is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, }, - }, - }) + }) + } return } // get session from cookie claims, err := token.ValidateBrowserSession(gc, sessionToken) if err != nil { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "login_required", - "error_description": "Login is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, }, - }, - }) + }) + } return } userID := claims.Subject user, err := db.Provider.GetUserByID(userID) if err != nil { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "signup_required", - "error_description": "Sign up required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "signup_required", + "error_description": "Sign up required", + }, }, - }, - }) + }) + } return } @@ -180,16 +221,20 @@ func AuthorizeHandler() gin.HandlerFunc { nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) if err != nil { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "login_required", - "error_description": "Login is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, }, - }, - }) + }) + } return } @@ -214,16 +259,20 @@ func AuthorizeHandler() gin.HandlerFunc { // rollover the session for security authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope) if err != nil { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "login_required", - "error_description": "Login is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, }, - }, - }) + }) + } return } sessionstore.RemoveState(sessionToken) @@ -256,16 +305,20 @@ func AuthorizeHandler() gin.HandlerFunc { return } - // by default return with error - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "error": "login_required", - "error_description": "Login is required", + if isQuery { + gc.Redirect(http.StatusFound, loginURL) + } else { + // by default return with error + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, }, - }, - }) + }) + } } } diff --git a/templates/authorize.tmpl b/templates/authorize.tmpl index cc6c659..ed55daa 100644 --- a/templates/authorize.tmpl +++ b/templates/authorize.tmpl @@ -8,7 +8,6 @@ (function (window, document) { var targetOrigin = {{.target_origin}}; var authorizationResponse = {{.authorization_response}}; - console.log({targetOrigin}) window.parent.postMessage(authorizationResponse, targetOrigin); })(this, this.document); From 57bc091499897332a3b159731eb047a127ee6f5e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Mon, 7 Mar 2022 23:44:19 +0530 Subject: [PATCH 23/41] fix state management --- app/package-lock.json | 14 +++++++------- app/src/App.tsx | 3 +++ app/src/Root.tsx | 7 ++++++- server/handlers/app.go | 2 ++ server/handlers/authorize.go | 2 +- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 57349c3..99dba0f 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.7.0", + "@authorizerdev/authorizer-react": "0.8.0", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -35,9 +35,9 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", - "integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", + "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", "dependencies": { "@authorizerdev/authorizer-js": "^0.3.0", "final-form": "^4.20.2", @@ -837,9 +837,9 @@ } }, "@authorizerdev/authorizer-react": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", - "integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", + "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", "requires": { "@authorizerdev/authorizer-js": "^0.3.0", "final-form": "^4.20.2", diff --git a/app/src/App.tsx b/app/src/App.tsx index de1c9c7..58ec66a 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -6,6 +6,9 @@ import Root from './Root'; export default function App() { // @ts-ignore const globalState: Record = window['__authorizer__']; + if (globalState.state) { + sessionStorage.setItem('authorizer_state', globalState.state); + } return (
{ if (token) { + const state = sessionStorage.getItem('authorizer_state')?.trim(); const url = new URL(config.redirectURL || '/app'); if (url.origin !== window.location.origin) { - window.location.href = config.redirectURL || '/app'; + console.log({ x: `${config.redirectURL || '/app'}?state=${state}` }); + sessionStorage.removeItem('authorizer_state'); + window.location.replace( + `${config.redirectURL || '/app'}?state=${state}` + ); } } return () => {}; diff --git a/server/handlers/app.go b/server/handlers/app.go index 483b8e1..06cad5b 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -18,6 +18,7 @@ import ( type State struct { AuthorizerURL string `json:"authorizerURL"` RedirectURL string `json:"redirectURL"` + State string `json:"state"` } // AppHandler is the handler for the /app route @@ -80,6 +81,7 @@ func AppHandler() gin.HandlerFunc { "data": map[string]string{ "authorizerURL": stateObj.AuthorizerURL, "redirectURL": stateObj.RedirectURL, + "state": stateObj.State, "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), "organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), }, diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 9dafafe..1ef6fbc 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -51,7 +51,7 @@ func AuthorizeHandler() gin.HandlerFunc { isQuery := responseMode == "query" hostname := utils.GetHost(gc) - loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `"}`) + loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`) loginURL := "/app?state=" + loginRedirectState if clientID == "" { From 8c2bf6ee0d674b75bc67df4da4ba4c88bbe502d1 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 12:36:26 +0530 Subject: [PATCH 24/41] fix: add token information in redirect url --- app/package-lock.json | 30 ++--- app/package.json | 2 +- app/src/App.tsx | 13 +-- app/src/Root.tsx | 17 ++- app/src/index.css | 16 ++- server/db/models/verification_requests.go | 35 +++--- server/graph/generated/generated.go | 136 ++++++++++++++++++++-- server/graph/model/models_gen.go | 28 +++-- server/graph/schema.graphqls | 6 + server/handlers/app.go | 55 +++------ server/handlers/authorize.go | 49 ++++---- server/handlers/oauth_callback.go | 35 ++++-- server/handlers/oauth_login.go | 28 +++-- server/handlers/verify_email.go | 72 ++++++++---- server/resolvers/forgot_password.go | 20 ++-- server/resolvers/login.go | 3 +- server/resolvers/magic_link_login.go | 36 ++++-- server/resolvers/resend_verify_email.go | 16 +-- server/resolvers/reset_password.go | 6 +- server/resolvers/signup.go | 16 +-- server/resolvers/update_profile.go | 16 +-- server/resolvers/update_user.go | 16 +-- server/resolvers/verify_email.go | 6 +- server/token/jwt.go | 2 + server/token/verification_token.go | 4 +- templates/app.tmpl | 2 +- 26 files changed, 440 insertions(+), 225 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 99dba0f..71db036 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.8.0", + "@authorizerdev/authorizer-react": "0.9.0-beta.0", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -24,9 +24,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", - "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz", + "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==", "dependencies": { "node-fetch": "^2.6.1" }, @@ -35,11 +35,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", - "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", + "version": "0.9.0-beta.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz", + "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==", "dependencies": { - "@authorizerdev/authorizer-js": "^0.3.0", + "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" @@ -829,19 +829,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", - "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz", + "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==", "requires": { "node-fetch": "^2.6.1" } }, "@authorizerdev/authorizer-react": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", - "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", + "version": "0.9.0-beta.0", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz", + "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==", "requires": { - "@authorizerdev/authorizer-js": "^0.3.0", + "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" diff --git a/app/package.json b/app/package.json index cd974d6..69baeba 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "latest", + "@authorizerdev/authorizer-react": "0.9.0-beta.0", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/src/App.tsx b/app/src/App.tsx index 58ec66a..284e3ad 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -6,9 +6,6 @@ import Root from './Root'; export default function App() { // @ts-ignore const globalState: Record = window['__authorizer__']; - if (globalState.state) { - sessionStorage.setItem('authorizer_state', globalState.state); - } return (

{globalState.organizationName}

-
+
{ if (token) { - const state = sessionStorage.getItem('authorizer_state')?.trim(); - const url = new URL(config.redirectURL || '/app'); + console.log({ token }); + let redirectURL = config.redirectURL || '/app'; + const params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&refresh_token=${token.refresh_token}`; + const url = new URL(redirectURL); + if (redirectURL.includes('?')) { + redirectURL = `${redirectURL}&${params}`; + } else { + redirectURL = `${redirectURL}?${params}`; + } + if (url.origin !== window.location.origin) { - console.log({ x: `${config.redirectURL || '/app'}?state=${state}` }); sessionStorage.removeItem('authorizer_state'); - window.location.replace( - `${config.redirectURL || '/app'}?state=${state}` - ); + window.location.replace(redirectURL); } } return () => {}; diff --git a/app/src/index.css b/app/src/index.css index 151b3d2..c86863c 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -1,5 +1,5 @@ body { - margin: 0; + margin: 10; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; @@ -14,3 +14,17 @@ body { *:after { box-sizing: inherit; } + +.container { + box-sizing: content-box; + border: 1px solid #d1d5db; + padding: 25px 20px; + border-radius: 5px; +} + +@media only screen and (min-width: 768px) { + .container { + width: 400px; + margin: 0 auto; + } +} diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index eec5427..50fa77f 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -4,25 +4,28 @@ import "github.com/authorizerdev/authorizer/server/graph/model" // VerificationRequest model for db type VerificationRequest struct { - Key string `json:"_key,omitempty" bson:"_key"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"` - Token string `gorm:"type:text" json:"token" bson:"token"` - Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"` - ExpiresAt int64 `json:"expires_at" bson:"expires_at"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` - Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` - Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"` + Key string `json:"_key,omitempty" bson:"_key"` // for arangodb + ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"` + Token string `gorm:"type:text" json:"token" bson:"token"` + Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"` + ExpiresAt int64 `json:"expires_at" bson:"expires_at"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` + Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"` + RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"` } func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { return &model.VerificationRequest{ - ID: v.ID, - Token: &v.Token, - Identifier: &v.Identifier, - Expires: &v.ExpiresAt, - CreatedAt: &v.CreatedAt, - UpdatedAt: &v.UpdatedAt, - Email: &v.Email, + ID: v.ID, + Token: &v.Token, + Identifier: &v.Identifier, + Expires: &v.ExpiresAt, + CreatedAt: &v.CreatedAt, + UpdatedAt: &v.UpdatedAt, + Email: &v.Email, + Nonce: &v.Nonce, + RedirectURI: &v.RedirectURI, } } diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 69b021f..ff0175d 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -173,13 +173,15 @@ type ComplexityRoot struct { } VerificationRequest struct { - CreatedAt func(childComplexity int) int - Email func(childComplexity int) int - Expires func(childComplexity int) int - ID func(childComplexity int) int - Identifier func(childComplexity int) int - Token func(childComplexity int) int - UpdatedAt func(childComplexity int) int + CreatedAt func(childComplexity int) int + Email func(childComplexity int) int + Expires func(childComplexity int) int + ID func(childComplexity int) int + Identifier func(childComplexity int) int + Nonce func(childComplexity int) int + RedirectURI func(childComplexity int) int + Token func(childComplexity int) int + UpdatedAt func(childComplexity int) int } VerificationRequests struct { @@ -1038,6 +1040,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.VerificationRequest.Identifier(childComplexity), true + case "VerificationRequest.nonce": + if e.complexity.VerificationRequest.Nonce == nil { + break + } + + return e.complexity.VerificationRequest.Nonce(childComplexity), true + + case "VerificationRequest.redirect_uri": + if e.complexity.VerificationRequest.RedirectURI == nil { + break + } + + return e.complexity.VerificationRequest.RedirectURI(childComplexity), true + case "VerificationRequest.token": if e.complexity.VerificationRequest.Token == nil { break @@ -1189,6 +1205,8 @@ type VerificationRequest { expires: Int64 created_at: Int64 updated_at: Int64 + nonce: String + redirect_uri: String } type VerificationRequests { @@ -1361,6 +1379,8 @@ input UpdateUserInput { input ForgotPasswordInput { email: String! + state: String + redirect_uri: String } input ResetPasswordInput { @@ -1377,6 +1397,8 @@ input MagicLinkLoginInput { email: String! roles: [String!] scope: [String!] + state: String + redirect_uri: String } input SessionQueryInput { @@ -5451,6 +5473,70 @@ func (ec *executionContext) _VerificationRequest_updated_at(ctx context.Context, return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) } +func (ec *executionContext) _VerificationRequest_nonce(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "VerificationRequest", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Nonce, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _VerificationRequest_redirect_uri(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "VerificationRequest", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.RedirectURI, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + func (ec *executionContext) _VerificationRequests_pagination(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequests) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6729,6 +6815,22 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex if err != nil { return it, err } + case "state": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state")) + it.State, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "redirect_uri": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("redirect_uri")) + it.RedirectURI, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } } } @@ -6815,6 +6917,22 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex if err != nil { return it, err } + case "state": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state")) + it.State, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "redirect_uri": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("redirect_uri")) + it.RedirectURI, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } } } @@ -8290,6 +8408,10 @@ func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.Se out.Values[i] = ec._VerificationRequest_created_at(ctx, field, obj) case "updated_at": out.Values[i] = ec._VerificationRequest_updated_at(ctx, field, obj) + case "nonce": + out.Values[i] = ec._VerificationRequest_nonce(ctx, field, obj) + case "redirect_uri": + out.Values[i] = ec._VerificationRequest_redirect_uri(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 5134dda..2825956 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -69,7 +69,9 @@ type Error struct { } type ForgotPasswordInput struct { - Email string `json:"email"` + Email string `json:"email"` + State *string `json:"state"` + RedirectURI *string `json:"redirect_uri"` } type LoginInput struct { @@ -80,9 +82,11 @@ type LoginInput struct { } type MagicLinkLoginInput struct { - Email string `json:"email"` - Roles []string `json:"roles"` - Scope []string `json:"scope"` + Email string `json:"email"` + Roles []string `json:"roles"` + Scope []string `json:"scope"` + State *string `json:"state"` + RedirectURI *string `json:"redirect_uri"` } type Meta struct { @@ -239,13 +243,15 @@ type Users struct { } type VerificationRequest struct { - ID string `json:"id"` - Identifier *string `json:"identifier"` - Token *string `json:"token"` - Email *string `json:"email"` - Expires *int64 `json:"expires"` - CreatedAt *int64 `json:"created_at"` - UpdatedAt *int64 `json:"updated_at"` + ID string `json:"id"` + Identifier *string `json:"identifier"` + Token *string `json:"token"` + Email *string `json:"email"` + Expires *int64 `json:"expires"` + CreatedAt *int64 `json:"created_at"` + UpdatedAt *int64 `json:"updated_at"` + Nonce *string `json:"nonce"` + RedirectURI *string `json:"redirect_uri"` } type VerificationRequests struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index cf01374..b1549d2 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -57,6 +57,8 @@ type VerificationRequest { expires: Int64 created_at: Int64 updated_at: Int64 + nonce: String + redirect_uri: String } type VerificationRequests { @@ -229,6 +231,8 @@ input UpdateUserInput { input ForgotPasswordInput { email: String! + state: String + redirect_uri: String } input ResetPasswordInput { @@ -245,6 +249,8 @@ input MagicLinkLoginInput { email: String! roles: [String!] scope: [String!] + state: String + redirect_uri: String } input SessionQueryInput { diff --git a/server/handlers/app.go b/server/handlers/app.go index 06cad5b..9300eda 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -1,13 +1,11 @@ package handlers import ( - "encoding/json" "log" "net/http" "strings" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" @@ -18,7 +16,6 @@ import ( type State struct { AuthorizerURL string `json:"authorizerURL"` RedirectURL string `json:"redirectURL"` - State string `json:"state"` } // AppHandler is the handler for the /app route @@ -30,44 +27,25 @@ func AppHandler() gin.HandlerFunc { return } - state := c.Query("state") + redirect_uri := strings.TrimSpace(c.Query("redirect_uri")) + state := strings.TrimSpace(c.Query("state")) + scopeString := strings.TrimSpace(c.Query("scope")) - var stateObj State - - if state == "" { - stateObj.AuthorizerURL = hostname - stateObj.RedirectURL = hostname + "/app" + var scope []string + if scopeString == "" { + scope = []string{"openid", "profile", "email"} } else { - decodedState, err := crypto.DecryptB64(state) - if err != nil { - c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) - return - } - - err = json.Unmarshal([]byte(decodedState), &stateObj) - if err != nil { - c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"}) - return - } - stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/") - stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/") + scope = strings.Split(scopeString, " ") + } + if redirect_uri == "" { + redirect_uri = hostname + "/app" + } else { // validate redirect url with allowed origins - if !utils.IsValidOrigin(stateObj.RedirectURL) { + if !utils.IsValidOrigin(redirect_uri) { c.JSON(400, gin.H{"error": "invalid redirect url"}) return } - - if stateObj.AuthorizerURL == "" { - c.JSON(400, gin.H{"error": "invalid authorizer url"}) - return - } - - // validate host and domain of authorizer url - if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname { - c.JSON(400, gin.H{"error": "invalid host url"}) - return - } } // debug the request state @@ -78,10 +56,11 @@ func AppHandler() gin.HandlerFunc { } } c.HTML(http.StatusOK, "app.tmpl", gin.H{ - "data": map[string]string{ - "authorizerURL": stateObj.AuthorizerURL, - "redirectURL": stateObj.RedirectURL, - "state": stateObj.State, + "data": map[string]interface{}{ + "authorizerURL": hostname, + "redirectURL": redirect_uri, + "scope": scope, + "state": state, "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), "organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), }, diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 1ef6fbc..c9b5a46 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -2,16 +2,15 @@ package handlers import ( "net/http" + "strconv" "strings" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" - "github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/token" - "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -36,6 +35,13 @@ func AuthorizeHandler() gin.HandlerFunc { template := "authorize.tmpl" responseMode := strings.TrimSpace(gc.Query("response_mode")) + var scope []string + if scopeString == "" { + scope = []string{"openid", "profile", "email"} + } else { + scope = strings.Split(scopeString, " ") + } + if responseMode == "" { responseMode = "query" } @@ -50,9 +56,7 @@ func AuthorizeHandler() gin.HandlerFunc { isQuery := responseMode == "query" - hostname := utils.GetHost(gc) - loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`) - loginURL := "/app?state=" + loginRedirectState + loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI if clientID == "" { if isQuery { @@ -109,13 +113,6 @@ func AuthorizeHandler() gin.HandlerFunc { responseType = "token" } - var scope []string - if scopeString == "" { - scope = []string{"openid", "profile", "email"} - } else { - scope = strings.Split(scopeString, " ") - } - isResponseTypeCode := responseType == "code" isResponseTypeToken := responseType == "token" @@ -279,8 +276,11 @@ func AuthorizeHandler() gin.HandlerFunc { sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) cookie.SetSession(gc, authToken.FingerPrintHash) - expiresIn := int64(1800) + + // used of query mode + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, "id_token": authToken.IDToken.Token, @@ -292,16 +292,25 @@ func AuthorizeHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token + params += "&refresh_token=" + authToken.RefreshToken.Token sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) } - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": res, - }, - }) + if isQuery { + if strings.Contains(redirectURI, "?") { + gc.Redirect(http.StatusFound, redirectURI+"&"+params) + } else { + gc.Redirect(http.StatusFound, redirectURI+"?"+params) + } + } else { + gc.HTML(http.StatusOK, template, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": res, + }, + }) + } return } diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 81aea1e..9b76ba9 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "net/http" + "strconv" "strings" "time" @@ -21,7 +22,6 @@ import ( "github.com/authorizerdev/authorizer/server/utils" "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" - "github.com/google/uuid" "golang.org/x/oauth2" ) @@ -37,16 +37,17 @@ func OAuthCallbackHandler() gin.HandlerFunc { } sessionstore.GetState(state) // contains random token, redirect url, role - sessionSplit := strings.Split(state, "___") + sessionSplit := strings.Split(state, "@") - // TODO validate redirect url - if len(sessionSplit) < 2 { + if len(sessionSplit) < 3 { c.JSON(400, gin.H{"error": "invalid redirect url"}) return } - inputRoles := strings.Split(sessionSplit[2], ",") + stateValue := sessionSplit[0] redirectURL := sessionSplit[1] + inputRoles := strings.Split(sessionSplit[2], ",") + scopes := strings.Split(sessionSplit[3], ",") var err error user := models.User{} @@ -145,17 +146,29 @@ func OAuthCallbackHandler() gin.HandlerFunc { } } - // TODO use query param - scope := []string{"openid", "email", "profile"} - nonce := uuid.New().String() - _, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope) + authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) } + expiresIn := int64(1800) + 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) + + if authToken.RefreshToken != nil { + params = params + `&refresh_token=${refresh_token}` + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + } - sessionstore.SetState(newSessionToken, nonce+"@"+user.ID) - cookie.SetSession(c, newSessionToken) go utils.SaveSessionInDB(c, user.ID) + if strings.Contains(redirectURL, "?") { + redirectURL = redirectURL + "&" + params + } else { + redirectURL = redirectURL + "?" + params + } + c.Redirect(http.StatusTemporaryRedirect, redirectURL) } } diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index 9ef000e..4f5e5dd 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -10,23 +10,38 @@ import ( "github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" - "github.com/google/uuid" ) // OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback func OAuthLoginHandler() gin.HandlerFunc { return func(c *gin.Context) { hostname := utils.GetHost(c) - redirectURL := c.Query("redirectURL") - roles := c.Query("roles") + redirectURI := strings.TrimSpace(c.Query("redirectURL")) + roles := strings.TrimSpace(c.Query("roles")) + state := strings.TrimSpace(c.Query("state")) + scopeString := strings.TrimSpace(c.Query("scope")) - if redirectURL == "" { + if redirectURI == "" { c.JSON(400, gin.H{ - "error": "invalid redirect url", + "error": "invalid redirect uri", }) return } + if state == "" { + c.JSON(400, gin.H{ + "error": "invalid state", + }) + return + } + + var scope []string + if scopeString == "" { + scope = []string{"openid", "profile", "email"} + } else { + scope = strings.Split(scopeString, " ") + } + if roles != "" { // validate role rolesSplit := strings.Split(roles, ",") @@ -43,8 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc { roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } - uuid := uuid.New() - oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles + oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",") provider := c.Param("oauth_provider") isProviderConfigured := true diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index acc5dea..99e7e68 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -2,6 +2,7 @@ package handlers import ( "net/http" + "strconv" "strings" "time" @@ -11,7 +12,6 @@ import ( "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" - "github.com/google/uuid" ) // VerifyEmailHandler handles the verify email route. @@ -19,7 +19,7 @@ import ( func VerifyEmailHandler() gin.HandlerFunc { return func(c *gin.Context) { errorRes := gin.H{ - "error": "invalid token", + "error": "invalid_token", } tokenInQuery := c.Query("token") if tokenInQuery == "" { @@ -29,30 +29,24 @@ func VerifyEmailHandler() gin.HandlerFunc { verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery) if err != nil { + errorRes["error_description"] = err.Error() c.JSON(400, errorRes) return } // verify if token exists in db hostname := utils.GetHost(c) - encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) - if err != nil { - c.JSON(400, gin.H{ - "error": err.Error(), - }) - return - } - claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email) if err != nil { + errorRes["error_description"] = err.Error() c.JSON(400, errorRes) return } user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) if err != nil { - c.JSON(400, gin.H{ - "message": err.Error(), - }) + errorRes["error_description"] = err.Error() + c.JSON(400, errorRes) return } @@ -65,21 +59,53 @@ func VerifyEmailHandler() gin.HandlerFunc { // delete from verification table db.Provider.DeleteVerificationRequest(verificationRequest) - roles := strings.Split(user.Roles, ",") - scope := []string{"openid", "email", "profile"} - nonce := uuid.New().String() - _, authToken, err := token.CreateSessionToken(user, nonce, roles, scope) + state := strings.TrimSpace(c.Query("state")) + redirectURL := strings.TrimSpace(c.Query("redirect_uri")) + rolesString := strings.TrimSpace(c.Query("roles")) + var roles []string + if rolesString == "" { + roles = strings.Split(user.Roles, ",") + } else { + roles = strings.Split(rolesString, ",") + } + + scopeString := strings.TrimSpace(c.Query("scope")) + var scope []string + if scopeString == "" { + scope = []string{"openid", "email", "profile"} + } else { + scope = strings.Split(scopeString, " ") + } + authToken, err := token.CreateAuthToken(c, user, roles, scope) if err != nil { - c.JSON(400, gin.H{ - "message": err.Error(), - }) + errorRes["error_description"] = err.Error() + c.JSON(500, errorRes) return } - sessionstore.SetState(authToken, nonce+"@"+user.ID) - cookie.SetSession(c, authToken) + expiresIn := int64(1800) + 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) + + if authToken.RefreshToken != nil { + params = params + `&refresh_token=${refresh_token}` + sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + } + + if redirectURL == "" { + redirectURL = claim["redirect_url"].(string) + } + + if strings.Contains(redirectURL, "?") { + redirectURL = redirectURL + "&" + params + } else { + redirectURL = redirectURL + "?" + params + } go utils.SaveSessionInDB(c, user.ID) - c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string)) + c.Redirect(http.StatusTemporaryRedirect, redirectURL) } } diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index 49eaa75..4be96b1 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -39,20 +39,26 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu } hostname := utils.GetHost(gc) - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } - verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash) + redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + if params.RedirectURI != nil { + redirectURL = *params.RedirectURI + } + + verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL) if err != nil { log.Println(`error generating token`, err) } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: constants.VerificationTypeForgotPassword, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: params.Email, - Nonce: nonce, + Token: verificationToken, + Identifier: constants.VerificationTypeForgotPassword, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.Email, + Nonce: nonceHash, + RedirectURI: redirectURL, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 03ffbb5..5a099b5 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -69,8 +69,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes return res, err } - cookie.SetSession(gc, authToken.FingerPrintHash) - expiresIn := int64(1800) res = &model.AuthResponse{ Message: `Logged in successfully`, @@ -80,6 +78,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes User: user.AsAPIUser(), } + cookie.SetSession(gc, authToken.FingerPrintHash) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index a5d80ed..ff39da8 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -68,6 +68,9 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu // Need to modify roles in this case // find the unassigned roles + if len(params.Roles) <= 0 { + inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) + } existingRoles := strings.Split(existingUser.Roles, ",") unasignedRoles := []string{} for _, ir := range inputRoles { @@ -109,21 +112,40 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu hostname := utils.GetHost(gc) if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } + redirectURLParams := "&roles=" + strings.Join(inputRoles, ",") + if params.State != nil { + redirectURLParams = redirectURLParams + "&state=" + *params.State + } + if params.Scope != nil && len(params.Scope) > 0 { + redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ") + } + redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + if params.RedirectURI != nil { + redirectURL = *params.RedirectURI + } + + if strings.Contains(redirectURL, "?") { + redirectURL = redirectURL + "&" + redirectURLParams + } else { + redirectURL = redirectURL + "?" + redirectURLParams + } + verificationType := constants.VerificationTypeMagicLinkLogin - verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) + verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL) if err != nil { log.Println(`error generating token`, err) } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: params.Email, - Nonce: nonce, + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.Email, + Nonce: nonceHash, + RedirectURI: redirectURL, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/resend_verify_email.go b/server/resolvers/resend_verify_email.go index f514681..68cb502 100644 --- a/server/resolvers/resend_verify_email.go +++ b/server/resolvers/resend_verify_email.go @@ -44,20 +44,22 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma } hostname := utils.GetHost(gc) - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } - verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash) + + verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash, verificationRequest.RedirectURI) if err != nil { log.Println(`error generating token`, err) } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: params.Identifier, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: params.Email, - Nonce: nonce, + Token: verificationToken, + Identifier: params.Identifier, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.Email, + Nonce: nonceHash, + RedirectURI: verificationRequest.RedirectURI, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index d94707f..5a498c8 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -37,11 +37,7 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) // verify if token exists in db hostname := utils.GetHost(gc) - encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) - if err != nil { - return res, err - } - claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) if err != nil { return res, fmt.Errorf(`invalid token`) } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 69e57c3..2f15f15 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -123,21 +123,23 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR hostname := utils.GetHost(gc) if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { // insert verification request - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } verificationType := constants.VerificationTypeBasicAuthSignup - verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) + redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL) if err != nil { return res, err } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: params.Email, - Nonce: nonce, + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.Email, + Nonce: nonceHash, + RedirectURI: redirectURL, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index cc43f4c..5a3f328 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -129,21 +129,23 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) user.EmailVerifiedAt = nil hasEmailChanged = true // insert verification request - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } verificationType := constants.VerificationTypeUpdateEmail - verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) + redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL) if err != nil { log.Println(`error generating token`, err) } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: newEmail, - Nonce: nonce, + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: newEmail, + Nonce: nonceHash, + RedirectURI: redirectURL, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index 948565e..f9438d4 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -101,21 +101,23 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod user.Email = newEmail user.EmailVerifiedAt = nil // insert verification request - nonce, nonceHash, err := utils.GenerateNonce() + _, nonceHash, err := utils.GenerateNonce() if err != nil { return res, err } verificationType := constants.VerificationTypeUpdateEmail - verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) + redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) + verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL) if err != nil { log.Println(`error generating token`, err) } db.Provider.AddVerificationRequest(models.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: newEmail, - Nonce: nonce, + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: newEmail, + Nonce: nonceHash, + RedirectURI: redirectURL, }) // exec it as go routin so that we can reduce the api latency diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 953db47..fe13c9a 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -29,11 +29,7 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m // verify if token exists in db hostname := utils.GetHost(gc) - encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) - if err != nil { - return res, err - } - claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email) + claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) if err != nil { return res, fmt.Errorf(`invalid token: %s`, err.Error()) } diff --git a/server/token/jwt.go b/server/token/jwt.go index 90f6333..912f1e5 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -2,6 +2,7 @@ package token import ( "errors" + "fmt" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/crypto" @@ -91,6 +92,7 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error return claims, errors.New("invalid audience") } + fmt.Println("claims:", claims, claims["nonce"], nonce) if claims["nonce"] != nonce { return claims, errors.New("invalid nonce") } diff --git a/server/token/verification_token.go b/server/token/verification_token.go index 7765392..71114d7 100644 --- a/server/token/verification_token.go +++ b/server/token/verification_token.go @@ -9,7 +9,7 @@ import ( ) // CreateVerificationToken creates a verification JWT token -func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) { +func CreateVerificationToken(email, tokenType, hostname, nonceHash, redirectURL string) (string, error) { claims := jwt.MapClaims{ "iss": hostname, "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), @@ -18,7 +18,7 @@ func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (stri "iat": time.Now().Unix(), "token_type": tokenType, "nonce": nonceHash, - "redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), + "redirect_url": redirectURL, } return SignJWTToken(claims) diff --git a/templates/app.tmpl b/templates/app.tmpl index 58f0159..bd15a4e 100644 --- a/templates/app.tmpl +++ b/templates/app.tmpl @@ -4,7 +4,7 @@ {{.data.organizationName}} - + From 674eeeea4e4c1b73d0833cd962dfb9616f1f5b45 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 14:20:11 +0530 Subject: [PATCH 25/41] chore: bump authorizer-react --- app/package-lock.json | 14 +++++++------- app/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 71db036..6febb77 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.0", + "@authorizerdev/authorizer-react": "0.9.0-beta.2", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -35,9 +35,9 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz", - "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==", + "version": "0.9.0-beta.2", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz", + "integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==", "dependencies": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", @@ -837,9 +837,9 @@ } }, "@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz", - "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==", + "version": "0.9.0-beta.2", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz", + "integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==", "requires": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", diff --git a/app/package.json b/app/package.json index 69baeba..670f8cd 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.0", + "@authorizerdev/authorizer-react": "0.9.0-beta.2", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", From a69b8e290c1bc4faa0f920b126614182679b6892 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 14:56:46 +0530 Subject: [PATCH 26/41] feat: add ability to get access token based on refresh token --- server/handlers/token.go | 154 ++++++++++++++++++++++++------------- server/token/auth_token.go | 35 ++++++++- 2 files changed, 133 insertions(+), 56 deletions(-) diff --git a/server/handlers/token.go b/server/handlers/token.go index 4a18a4d..fdfbf9f 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -15,6 +15,7 @@ import ( "github.com/gin-gonic/gin" ) +// grant type required func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { var reqBody map[string]string @@ -29,6 +30,22 @@ func TokenHandler() gin.HandlerFunc { codeVerifier := strings.TrimSpace(reqBody["code_verifier"]) code := strings.TrimSpace(reqBody["code"]) clientID := strings.TrimSpace(reqBody["client_id"]) + grantType := strings.TrimSpace(reqBody["grant_type"]) + refreshToken := strings.TrimSpace(reqBody["refresh_token"]) + + if grantType == "" { + grantType = "authorization_code" + } + + isRefreshTokenGrant := grantType == "refresh_token" + isAuthorizationCodeGrant := grantType == "authorization_code" + + if !isRefreshTokenGrant && !isAuthorizationCodeGrant { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_grant_type", + "error_description": "grant_type is invalid", + }) + } if clientID == "" { gc.JSON(http.StatusBadRequest, gin.H{ @@ -46,58 +63,87 @@ func TokenHandler() gin.HandlerFunc { return } - if codeVerifier == "" { - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is required", - }) - return + var userID string + var roles, scope []string + if isAuthorizationCodeGrant { + + if codeVerifier == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_code_verifier", + "error_description": "The code verifier is required", + }) + return + } + + if code == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_code", + "error_description": "The code is required", + }) + return + } + + hash := sha256.New() + hash.Write([]byte(codeVerifier)) + encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-") + encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") + encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") + sessionData := sessionstore.GetState(encryptedCode) + if sessionData == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_code_verifier", + "error_description": "The code verifier is invalid", + }) + return + } + + // split session data + // it contains code@sessiontoken + sessionDataSplit := strings.Split(sessionData, "@") + + if sessionDataSplit[0] != code { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_code_verifier", + "error_description": "The code verifier is invalid", + }) + return + } + + // rollover the session for security + sessionstore.RemoveState(sessionDataSplit[1]) + // validate session + claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1]) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": "unauthorized", + "error_description": "Invalid session data", + }) + return + } + userID = claims.Subject + roles = claims.Roles + scope = claims.Scope + } else { + // validate refresh token + if refreshToken == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_refresh_token", + "error_description": "The refresh token is invalid", + }) + } + + claims, err := token.ValidateRefreshToken(gc, refreshToken) + if err != nil { + gc.JSON(http.StatusUnauthorized, gin.H{ + "error": "unauthorized", + "error_description": err.Error(), + }) + } + userID = claims["sub"].(string) + roles = claims["roles"].([]string) + scope = claims["scope"].([]string) } - if code == "" { - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code", - "error_description": "The code is required", - }) - return - } - - hash := sha256.New() - hash.Write([]byte(codeVerifier)) - encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-") - encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") - encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") - sessionData := sessionstore.GetState(encryptedCode) - if sessionData == "" { - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is invalid", - }) - return - } - - // split session data - // it contains code@sessiontoken - sessionDataSplit := strings.Split(sessionData, "@") - - if sessionDataSplit[0] != code { - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is invalid", - }) - return - } - - // validate session - claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1]) - if err != nil { - gc.JSON(http.StatusUnauthorized, gin.H{ - "error": "unauthorized", - "error_description": "Invalid session data", - }) - return - } - userID := claims.Subject user, err := db.Provider.GetUserByID(userID) if err != nil { gc.JSON(http.StatusUnauthorized, gin.H{ @@ -106,9 +152,8 @@ func TokenHandler() gin.HandlerFunc { }) return } - // rollover the session for security - sessionstore.RemoveState(sessionDataSplit[1]) - authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope) + + authToken, err := token.CreateAuthToken(gc, user, roles, scope) if err != nil { gc.JSON(http.StatusUnauthorized, gin.H{ "error": "unauthorized", @@ -124,7 +169,8 @@ func TokenHandler() gin.HandlerFunc { res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, "id_token": authToken.IDToken.Token, - "scope": claims.Scope, + "scope": scope, + "roles": roles, "expires_in": expiresIn, } diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 16abbd6..350da17 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -91,7 +91,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) ( } if utils.StringSliceContains(scope, "offline_access") { - refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce) + refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce) if err != nil { return nil, err } @@ -103,7 +103,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) ( } // CreateRefreshToken util to create JWT token -func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) { +func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce string) (string, int64, error) { // expires in 1 year expiryBound := time.Hour * 8760 expiresAt := time.Now().Add(expiryBound).Unix() @@ -115,6 +115,7 @@ func CreateRefreshToken(user models.User, roles []string, hostname, nonce string "iat": time.Now().Unix(), "token_type": constants.TokenTypeRefreshToken, "roles": roles, + "scope": scopes, "nonce": nonce, } @@ -198,6 +199,36 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf return res, nil } +// Function to validate refreshToken +func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]interface{}, error) { + var res map[string]interface{} + + if refreshToken == "" { + return res, fmt.Errorf(`unauthorized`) + } + + savedSession := sessionstore.GetState(refreshToken) + if savedSession == "" { + return res, fmt.Errorf(`unauthorized`) + } + + savedSessionSplit := strings.Split(savedSession, "@") + nonce := savedSessionSplit[0] + userID := savedSessionSplit[1] + + hostname := utils.GetHost(gc) + res, err := ParseJWTToken(refreshToken, hostname, nonce, userID) + if err != nil { + return res, err + } + + if res["token_type"] != constants.TokenTypeRefreshToken { + return res, fmt.Errorf(`unauthorized: invalid token type`) + } + + return res, nil +} + func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) { if encryptedSession == "" { return nil, fmt.Errorf(`unauthorized`) From 3bb90acc9e4cad11f2d86fddf7db4de5fcfe4ccf Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 18:49:42 +0530 Subject: [PATCH 27/41] feat: add revoke mutation + handler --- server/graph/generated/generated.go | 109 ++++++++++++++++++++++++++++ server/graph/model/models_gen.go | 4 + server/graph/schema.graphqls | 5 ++ server/graph/schema.resolvers.go | 10 ++- server/handlers/logout.go | 1 + server/handlers/revoke.go | 50 +++++++++++++ server/handlers/token.go | 1 + server/resolvers/revoke.go | 16 ++++ server/routes/routes.go | 1 + 9 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 server/handlers/revoke.go create mode 100644 server/resolvers/revoke.go diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index ff0175d..3327538 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -119,6 +119,7 @@ type ComplexityRoot struct { MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int ResetPassword func(childComplexity int, params model.ResetPasswordInput) int + Revoke func(childComplexity int, params model.OAuthRevokeInput) int Signup func(childComplexity int, params model.SignUpInput) int UpdateEnv func(childComplexity int, params model.UpdateEnvInput) int UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int @@ -200,6 +201,7 @@ type MutationResolver interface { ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) + Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) @@ -713,6 +715,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.ResetPassword(childComplexity, args["params"].(model.ResetPasswordInput)), true + case "Mutation.revoke": + if e.complexity.Mutation.Revoke == nil { + break + } + + args, err := ec.field_Mutation_revoke_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.Revoke(childComplexity, args["params"].(model.OAuthRevokeInput)), true + case "Mutation.signup": if e.complexity.Mutation.Signup == nil { break @@ -1415,6 +1429,10 @@ input PaginatedInput { pagination: PaginationInput } +input OAuthRevokeInput { + refresh_token: String! +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -1425,6 +1443,7 @@ type Mutation { resend_verify_email(params: ResendVerifyEmailInput!): Response! forgot_password(params: ForgotPasswordInput!): Response! reset_password(params: ResetPasswordInput!): Response! + revoke(params: OAuthRevokeInput!): Response! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! @@ -1602,6 +1621,21 @@ func (ec *executionContext) field_Mutation_reset_password_args(ctx context.Conte return args, nil } +func (ec *executionContext) field_Mutation_revoke_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.OAuthRevokeInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐOAuthRevokeInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_signup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -3860,6 +3894,48 @@ func (ec *executionContext) _Mutation_reset_password(ctx context.Context, field return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_revoke(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_revoke_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().Revoke(rctx, args["params"].(model.OAuthRevokeInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6939,6 +7015,29 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex return it, nil } +func (ec *executionContext) unmarshalInputOAuthRevokeInput(ctx context.Context, obj interface{}) (model.OAuthRevokeInput, error) { + var it model.OAuthRevokeInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "refresh_token": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("refresh_token")) + it.RefreshToken, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputPaginatedInput(ctx context.Context, obj interface{}) (model.PaginatedInput, error) { var it model.PaginatedInput asMap := map[string]interface{}{} @@ -8039,6 +8138,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "revoke": + out.Values[i] = ec._Mutation_revoke(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "_delete_user": out.Values[i] = ec._Mutation__delete_user(ctx, field) if out.Values[i] == graphql.Null { @@ -8822,6 +8926,11 @@ func (ec *executionContext) marshalNMeta2ᚖgithubᚗcomᚋauthorizerdevᚋautho return ec._Meta(ctx, sel, v) } +func (ec *executionContext) unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐOAuthRevokeInput(ctx context.Context, v interface{}) (model.OAuthRevokeInput, error) { + res, err := ec.unmarshalInputOAuthRevokeInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNPagination2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPagination(ctx context.Context, sel ast.SelectionSet, v *model.Pagination) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 2825956..9f4cdd9 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -100,6 +100,10 @@ type Meta struct { IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"` } +type OAuthRevokeInput struct { + RefreshToken string `json:"refresh_token"` +} + type PaginatedInput struct { Pagination *PaginationInput `json:"pagination"` } diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index b1549d2..cf76429 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -267,6 +267,10 @@ input PaginatedInput { pagination: PaginationInput } +input OAuthRevokeInput { + refresh_token: String! +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -277,6 +281,7 @@ type Mutation { resend_verify_email(params: ResendVerifyEmailInput!): Response! forgot_password(params: ForgotPasswordInput!): Response! reset_password(params: ResetPasswordInput!): Response! + revoke(params: OAuthRevokeInput!): Response! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 79f718a..8830195 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -47,6 +47,10 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, params model.Reset return resolvers.ResetPasswordResolver(ctx, params) } +func (r *mutationResolver) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) { + return resolvers.RevokeResolver(ctx, params) +} + func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { return resolvers.DeleteUserResolver(ctx, params) } @@ -105,5 +109,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type mutationResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } +type ( + mutationResolver struct{ *Resolver } + queryResolver struct{ *Resolver } +) diff --git a/server/handlers/logout.go b/server/handlers/logout.go index 9ffc6cc..f3090aa 100644 --- a/server/handlers/logout.go +++ b/server/handlers/logout.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" ) +// Handler to logout user func LogoutHandler() gin.HandlerFunc { return func(gc *gin.Context) { // get fingerprint hash diff --git a/server/handlers/revoke.go b/server/handlers/revoke.go new file mode 100644 index 0000000..6dc79db --- /dev/null +++ b/server/handlers/revoke.go @@ -0,0 +1,50 @@ +package handlers + +import ( + "net/http" + "strings" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/sessionstore" + "github.com/gin-gonic/gin" +) + +// Revoke handler to revoke refresh token +func RevokeHandler() gin.HandlerFunc { + return func(gc *gin.Context) { + var reqBody map[string]string + if err := gc.BindJSON(&reqBody); err != nil { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "error_binding_json", + "error_description": err.Error(), + }) + return + } + // get fingerprint hash + refreshToken := strings.TrimSpace(reqBody["refresh_token"]) + clientID := strings.TrimSpace(reqBody["client_id"]) + + if clientID == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "client_id_required", + "error_description": "The client id is required", + }) + return + } + + if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_client_id", + "error_description": "The client id is invalid", + }) + return + } + + sessionstore.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 fdfbf9f..aa34045 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -15,6 +15,7 @@ import ( "github.com/gin-gonic/gin" ) +// TokenHandler to handle /oauth/token requests // grant type required func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { diff --git a/server/resolvers/revoke.go b/server/resolvers/revoke.go new file mode 100644 index 0000000..1ab1cb9 --- /dev/null +++ b/server/resolvers/revoke.go @@ -0,0 +1,16 @@ +package resolvers + +import ( + "context" + + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/sessionstore" +) + +// RevokeResolver resolver to revoke refresh token +func RevokeResolver(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) { + sessionstore.RemoveState(params.RefreshToken) + return &model.Response{ + Message: "Token revoked", + }, nil +} diff --git a/server/routes/routes.go b/server/routes/routes.go index 6b99d3a..71e1926 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -27,6 +27,7 @@ func InitRouter() *gin.Engine { router.GET("/userinfo", handlers.UserInfoHandler()) router.GET("/logout", handlers.LogoutHandler()) router.POST("/oauth/token", handlers.TokenHandler()) + router.POST("/oauth/revoke", handlers.RevokeHandler()) router.LoadHTMLGlob("templates/*") // login page app related routes. From 917eaeb2ed45de1adfc85f3b9b6d99e056bfd20b Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 18:51:46 +0530 Subject: [PATCH 28/41] feat: don't set cookie in case of offline_access --- server/handlers/authorize.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index c9b5a46..e0ba477 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -275,7 +275,7 @@ func AuthorizeHandler() gin.HandlerFunc { sessionstore.RemoveState(sessionToken) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) - cookie.SetSession(gc, authToken.FingerPrintHash) + expiresIn := int64(1800) // used of query mode @@ -294,6 +294,9 @@ func AuthorizeHandler() gin.HandlerFunc { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + } else { + // set session if not offline access + cookie.SetSession(gc, authToken.FingerPrintHash) } if isQuery { From fd9eb7c7338d4912ed448f23e197b191fe5a8650 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 19:13:45 +0530 Subject: [PATCH 29/41] fix: oauth state split --- server/handlers/oauth_callback.go | 2 +- server/handlers/oauth_login.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 9b76ba9..cb9eab6 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -37,7 +37,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { } sessionstore.GetState(state) // contains random token, redirect url, role - sessionSplit := strings.Split(state, "@") + sessionSplit := strings.Split(state, "___") if len(sessionSplit) < 3 { c.JSON(400, gin.H{"error": "invalid redirect url"}) diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index 4f5e5dd..87eff74 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -58,7 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc { roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") } - oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",") + oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, ",") provider := c.Param("oauth_provider") isProviderConfigured := true From 7136ee924d7de9a362fd34ba4da89beaeb5854d6 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 19:18:33 +0530 Subject: [PATCH 30/41] fix: rotate refresh token --- server/handlers/token.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/handlers/token.go b/server/handlers/token.go index aa34045..399f10d 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -143,6 +143,8 @@ func TokenHandler() gin.HandlerFunc { userID = claims["sub"].(string) roles = claims["roles"].([]string) scope = claims["scope"].([]string) + // remove older refresh token and rotate it for security + sessionstore.RemoveState(refreshToken) } user, err := db.Provider.GetUserByID(userID) From 9eca697a911d2a0f12aabc574703d19e1934f6d3 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 19:31:19 +0530 Subject: [PATCH 31/41] fix: refresh token param in string --- server/handlers/authorize.go | 5 +---- server/handlers/oauth_callback.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index e0ba477..c9b5a46 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -275,7 +275,7 @@ func AuthorizeHandler() gin.HandlerFunc { sessionstore.RemoveState(sessionToken) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) - + cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := int64(1800) // used of query mode @@ -294,9 +294,6 @@ func AuthorizeHandler() gin.HandlerFunc { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) - } else { - // set session if not offline access - cookie.SetSession(gc, authToken.FingerPrintHash) } if isQuery { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index cb9eab6..2d7a268 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -158,7 +158,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) if authToken.RefreshToken != nil { - params = params + `&refresh_token=${refresh_token}` + params = params + `&refresh_token=` + authToken.RefreshToken.Token sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) } From f5bdc8db39a71e4a4b3b4da630a5fab5bad09c85 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 21:13:23 +0530 Subject: [PATCH 32/41] fix: refresh token store info --- server/handlers/authorize.go | 2 +- server/handlers/oauth_callback.go | 2 +- server/handlers/token.go | 12 +++++++++--- server/handlers/verify_email.go | 2 +- server/resolvers/login.go | 2 +- server/resolvers/session.go | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index c9b5a46..53c94ee 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -293,7 +293,7 @@ func AuthorizeHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } if isQuery { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 2d7a268..1d28234 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -159,7 +159,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { params = params + `&refresh_token=` + authToken.RefreshToken.Token - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } go utils.SaveSessionInDB(c, user.ID) diff --git a/server/handlers/token.go b/server/handlers/token.go index 399f10d..45c66e7 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -141,8 +141,14 @@ func TokenHandler() gin.HandlerFunc { }) } userID = claims["sub"].(string) - roles = claims["roles"].([]string) - scope = claims["scope"].([]string) + rolesInterface := claims["roles"].([]interface{}) + scopeInterface := claims["scope"].([]interface{}) + for _, v := range rolesInterface { + roles = append(roles, v.(string)) + } + for _, v := range scopeInterface { + scope = append(scope, v.(string)) + } // remove older refresh token and rotate it for security sessionstore.RemoveState(refreshToken) } @@ -179,7 +185,7 @@ func TokenHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { res["refresh_token"] = authToken.RefreshToken.Token - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.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 99e7e68..9544046 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -91,7 +91,7 @@ func VerifyEmailHandler() gin.HandlerFunc { if authToken.RefreshToken != nil { params = params + `&refresh_token=${refresh_token}` - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } if redirectURL == "" { diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 5a099b5..355c77c 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -84,7 +84,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } go utils.SaveSessionInDB(gc, user.ID) diff --git a/server/resolvers/session.go b/server/resolvers/session.go index 3d9c668..151321d 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -80,7 +80,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token - sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) + sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) } return res, nil From 60cd317e67923cf538e3c9aff8047ea3b9ab5948 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 21:32:42 +0530 Subject: [PATCH 33/41] fix: add redirect url to logout --- server/handlers/logout.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/handlers/logout.go b/server/handlers/logout.go index f3090aa..14c1730 100644 --- a/server/handlers/logout.go +++ b/server/handlers/logout.go @@ -2,6 +2,7 @@ package handlers import ( "net/http" + "strings" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/crypto" @@ -12,6 +13,7 @@ import ( // Handler to logout user func LogoutHandler() gin.HandlerFunc { return func(gc *gin.Context) { + redirectURL := strings.TrimSpace(gc.Query("redirect_url")) // get fingerprint hash fingerprintHash, err := cookie.GetSession(gc) if err != nil { @@ -34,8 +36,12 @@ func LogoutHandler() gin.HandlerFunc { sessionstore.RemoveState(fingerPrint) cookie.DeleteSession(gc) - gc.JSON(http.StatusOK, gin.H{ - "message": "Logged out successfully", - }) + if redirectURL != "" { + gc.Redirect(http.StatusPermanentRedirect, redirectURL) + } else { + gc.JSON(http.StatusOK, gin.H{ + "message": "Logged out successfully", + }) + } } } From 5d73df0040587f5081206ccbd160e75d12ed1fe2 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 22:41:33 +0530 Subject: [PATCH 34/41] fix: magic link login --- app/package-lock.json | 14 +++++++------- app/package.json | 2 +- server/handlers/logout.go | 4 ++-- server/token/verification_token.go | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 6febb77..b9cf1b6 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.2", + "@authorizerdev/authorizer-react": "0.9.0-beta.3", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -35,9 +35,9 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.2", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz", - "integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==", + "version": "0.9.0-beta.3", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz", + "integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==", "dependencies": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", @@ -837,9 +837,9 @@ } }, "@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.2", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz", - "integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==", + "version": "0.9.0-beta.3", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz", + "integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==", "requires": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", diff --git a/app/package.json b/app/package.json index 670f8cd..ce6d03e 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.2", + "@authorizerdev/authorizer-react": "0.9.0-beta.3", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/server/handlers/logout.go b/server/handlers/logout.go index 14c1730..7c7b756 100644 --- a/server/handlers/logout.go +++ b/server/handlers/logout.go @@ -13,7 +13,7 @@ import ( // Handler to logout user func LogoutHandler() gin.HandlerFunc { return func(gc *gin.Context) { - redirectURL := strings.TrimSpace(gc.Query("redirect_url")) + redirectURL := strings.TrimSpace(gc.Query("redirect_uri")) // get fingerprint hash fingerprintHash, err := cookie.GetSession(gc) if err != nil { @@ -37,7 +37,7 @@ func LogoutHandler() gin.HandlerFunc { cookie.DeleteSession(gc) if redirectURL != "" { - gc.Redirect(http.StatusPermanentRedirect, redirectURL) + gc.Redirect(http.StatusFound, redirectURL) } else { gc.JSON(http.StatusOK, gin.H{ "message": "Logged out successfully", diff --git a/server/token/verification_token.go b/server/token/verification_token.go index 71114d7..ceaccbc 100644 --- a/server/token/verification_token.go +++ b/server/token/verification_token.go @@ -18,7 +18,7 @@ func CreateVerificationToken(email, tokenType, hostname, nonceHash, redirectURL "iat": time.Now().Unix(), "token_type": tokenType, "nonce": nonceHash, - "redirect_url": redirectURL, + "redirect_uri": redirectURL, } return SignJWTToken(claims) From ee7aea7beece6021f2fafdc5f2c6afa139b49f6c Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 8 Mar 2022 22:55:45 +0530 Subject: [PATCH 35/41] fix: verify email --- server/handlers/verify_email.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 9544046..80fe6ad 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -95,7 +95,7 @@ func VerifyEmailHandler() gin.HandlerFunc { } if redirectURL == "" { - redirectURL = claim["redirect_url"].(string) + redirectURL = claim["redirect_uri"].(string) } if strings.Contains(redirectURL, "?") { From 2f9725d8e19b606bf12a03abb4531c93f314b4a4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 06:41:38 +0530 Subject: [PATCH 36/41] fix: verification request --- server/db/providers/sql/verification_requests.go | 2 +- server/token/jwt.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/db/providers/sql/verification_requests.go b/server/db/providers/sql/verification_requests.go index 7e7e7ba..fe044a7 100644 --- a/server/db/providers/sql/verification_requests.go +++ b/server/db/providers/sql/verification_requests.go @@ -21,7 +21,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio verificationRequest.UpdatedAt = time.Now().Unix() result := p.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}}, - DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}), + DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at", "nonce", "redirect_uri"}), }).Create(&verificationRequest) if result.Error != nil { diff --git a/server/token/jwt.go b/server/token/jwt.go index 912f1e5..f3e74c8 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -92,7 +92,7 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error return claims, errors.New("invalid audience") } - fmt.Println("claims:", claims, claims["nonce"], nonce) + fmt.Println("claims:", claims["nonce"], nonce, claims["nonce"] == nonce) if claims["nonce"] != nonce { return claims, errors.New("invalid nonce") } From d1e284116d38f98f195b8f55003307375ff430c4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 07:10:07 +0530 Subject: [PATCH 37/41] fix: verification request model --- server/db/models/verification_requests.go | 2 +- server/email/verification_email.go | 8 +++++++- server/resolvers/magic_link_login.go | 7 +++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index 50fa77f..7d94c6a 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -12,7 +12,7 @@ type VerificationRequest struct { CreatedAt int64 `json:"created_at" bson:"created_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"` Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` - Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"` + Nonce string `gorm:"type:text" json:"nonce" bson:"nonce"` RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"` } diff --git a/server/email/verification_email.go b/server/email/verification_email.go index cc6bb54..2c10ef4 100644 --- a/server/email/verification_email.go +++ b/server/email/verification_email.go @@ -1,6 +1,8 @@ package email import ( + "fmt" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" ) @@ -103,5 +105,9 @@ func SendVerificationMail(toEmail, token, hostname string) error { message = addEmailTemplate(message, data, "verify_email.tmpl") // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) - return SendMail(Receiver, Subject, message) + err := SendMail(Receiver, Subject, message) + if err != nil { + fmt.Println("=> error sending email:", err) + } + return err } diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index ff39da8..7014ca1 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -139,7 +139,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu if err != nil { log.Println(`error generating token`, err) } - db.Provider.AddVerificationRequest(models.VerificationRequest{ + _, err = db.Provider.AddVerificationRequest(models.VerificationRequest{ Token: verificationToken, Identifier: verificationType, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), @@ -147,8 +147,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu Nonce: nonceHash, RedirectURI: redirectURL, }) + if err != nil { + return res, err + } - // exec it as go routin so that we can reduce the api latency + // exec it as go routing so that we can reduce the api latency go email.SendVerificationMail(params.Email, verificationToken, hostname) } From 157b13baa7802672297d0da60fd0cbc2c464159e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 10:10:39 +0530 Subject: [PATCH 38/41] fix: basic auth redirect --- app/package-lock.json | 14 +++++++------- app/package.json | 2 +- app/src/App.tsx | 29 ++++++++++++++++++++++++++--- app/src/Root.tsx | 12 +++++++++--- app/src/utils/common.ts | 22 ++++++++++++++++++++++ server/email/verification_email.go | 4 ++-- 6 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 app/src/utils/common.ts diff --git a/app/package-lock.json b/app/package-lock.json index b9cf1b6..f903c7f 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.3", + "@authorizerdev/authorizer-react": "0.9.0-beta.6", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -35,9 +35,9 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz", - "integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==", + "version": "0.9.0-beta.6", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.6.tgz", + "integrity": "sha512-5kGUUb0d/GQD24eoHeB/4WOloYeMT//Pdch8xmOKUetHk3t62rPtu1ADmitcfkhJUlM9St3fRZsaubPqdE9tvg==", "dependencies": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", @@ -837,9 +837,9 @@ } }, "@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz", - "integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==", + "version": "0.9.0-beta.6", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.6.tgz", + "integrity": "sha512-5kGUUb0d/GQD24eoHeB/4WOloYeMT//Pdch8xmOKUetHk3t62rPtu1ADmitcfkhJUlM9St3fRZsaubPqdE9tvg==", "requires": { "@authorizerdev/authorizer-js": "^0.4.0-beta.0", "final-form": "^4.20.2", diff --git a/app/package.json b/app/package.json index ce6d03e..2076745 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.3", + "@authorizerdev/authorizer-react": "0.9.0-beta.6", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/src/App.tsx b/app/src/App.tsx index 284e3ad..1131b59 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -2,10 +2,33 @@ import React from 'react'; import { BrowserRouter } from 'react-router-dom'; import { AuthorizerProvider } from '@authorizerdev/authorizer-react'; import Root from './Root'; +import { createRandomString } from './utils/common'; export default function App() { - // @ts-ignore - const globalState: Record = window['__authorizer__']; + const searchParams = new URLSearchParams(window.location.search); + const state = searchParams.get('state') || createRandomString(); + const scope = searchParams.get('scope') + ? searchParams.get('scope')?.toString().split(' ') + : `openid profile email`; + + const urlProps: Record = { + state, + scope, + }; + + const redirectURL = + searchParams.get('redirect_uri') || searchParams.get('redirectURL'); + if (redirectURL) { + urlProps.redirectURL = redirectURL; + } else { + urlProps.redirectURL = window.location.origin; + } + const globalState: Record = { + // @ts-ignore + ...window['__authorizer__'], + ...urlProps, + }; + return (
- +
diff --git a/app/src/Root.tsx b/app/src/Root.tsx index 42d0699..d62ded8 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -6,14 +6,20 @@ const ResetPassword = lazy(() => import('./pages/rest-password')); const Login = lazy(() => import('./pages/login')); const Dashboard = lazy(() => import('./pages/dashboard')); -export default function Root() { +export default function Root({ + globalState, +}: { + globalState: Record; +}) { const { token, loading, config } = useAuthorizer(); useEffect(() => { if (token) { - console.log({ token }); let redirectURL = config.redirectURL || '/app'; - const params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&refresh_token=${token.refresh_token}`; + let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`; + if (token.refresh_token) { + params += `&refresh_token=${token.refresh_token}`; + } const url = new URL(redirectURL); if (redirectURL.includes('?')) { redirectURL = `${redirectURL}&${params}`; diff --git a/app/src/utils/common.ts b/app/src/utils/common.ts new file mode 100644 index 0000000..278e4dd --- /dev/null +++ b/app/src/utils/common.ts @@ -0,0 +1,22 @@ +export const getCrypto = () => { + //ie 11.x uses msCrypto + return (window.crypto || (window as any).msCrypto) as Crypto; +}; + +export const createRandomString = () => { + const charset = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.'; + let random = ''; + const randomValues = Array.from( + getCrypto().getRandomValues(new Uint8Array(43)) + ); + randomValues.forEach((v) => (random += charset[v % charset.length])); + return random; +}; + +export const createQueryParams = (params: any) => { + return Object.keys(params) + .filter((k) => typeof params[k] !== 'undefined') + .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) + .join('&'); +}; diff --git a/server/email/verification_email.go b/server/email/verification_email.go index 2c10ef4..bb0881f 100644 --- a/server/email/verification_email.go +++ b/server/email/verification_email.go @@ -1,7 +1,7 @@ package email import ( - "fmt" + "log" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/envstore" @@ -107,7 +107,7 @@ func SendVerificationMail(toEmail, token, hostname string) error { err := SendMail(Receiver, Subject, message) if err != nil { - fmt.Println("=> error sending email:", err) + log.Println("=> error sending email:", err) } return err } From dd64aa2e794403966b0ad6e1f4b17ac8d3df31d8 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 11:53:34 +0530 Subject: [PATCH 39/41] feat: add version info --- app/package-lock.json | 30 +++++++++++++------------- app/package.json | 2 +- dashboard/src/components/Menu.tsx | 17 ++++++++++++++- dashboard/src/graphql/queries/index.ts | 9 ++++++++ dashboard/src/layouts/AuthLayout.tsx | 21 +++++++++++++----- dashboard/src/pages/Auth.tsx | 1 - server/graph/generated/generated.go | 9 ++++++++ server/graph/model/models_gen.go | 1 + server/graph/schema.graphqls | 1 + server/graph/schema.resolvers.go | 6 ++---- server/resolvers/signup.go | 3 +++ 11 files changed, 73 insertions(+), 27 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index f903c7f..4f02a7b 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.6", + "@authorizerdev/authorizer-react": "0.9.0-beta.7", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -24,9 +24,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "0.4.0-beta.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz", - "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==", + "version": "0.4.0-beta.3", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.3.tgz", + "integrity": "sha512-OGZc6I6cnpi/WkSotkjVIc3LEzl8pFeiohr8+Db9xWd75/oTfOZqWRuIHTnTc1FC+6Sv2EjTJ9Aa6lrloWG+NQ==", "dependencies": { "node-fetch": "^2.6.1" }, @@ -35,11 +35,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.6", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.6.tgz", - "integrity": "sha512-5kGUUb0d/GQD24eoHeB/4WOloYeMT//Pdch8xmOKUetHk3t62rPtu1ADmitcfkhJUlM9St3fRZsaubPqdE9tvg==", + "version": "0.9.0-beta.7", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.7.tgz", + "integrity": "sha512-hCGsVionKMZNk+uD0CLtMIkUzhQqpHbVntko3rY+O7ouOrTrikY/WQVPbo1bqX1cu/6/cHE4RVU3cZ7V5xnxVg==", "dependencies": { - "@authorizerdev/authorizer-js": "^0.4.0-beta.0", + "@authorizerdev/authorizer-js": "^0.4.0-beta.3", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" @@ -829,19 +829,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "0.4.0-beta.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz", - "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==", + "version": "0.4.0-beta.3", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.3.tgz", + "integrity": "sha512-OGZc6I6cnpi/WkSotkjVIc3LEzl8pFeiohr8+Db9xWd75/oTfOZqWRuIHTnTc1FC+6Sv2EjTJ9Aa6lrloWG+NQ==", "requires": { "node-fetch": "^2.6.1" } }, "@authorizerdev/authorizer-react": { - "version": "0.9.0-beta.6", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.6.tgz", - "integrity": "sha512-5kGUUb0d/GQD24eoHeB/4WOloYeMT//Pdch8xmOKUetHk3t62rPtu1ADmitcfkhJUlM9St3fRZsaubPqdE9tvg==", + "version": "0.9.0-beta.7", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.7.tgz", + "integrity": "sha512-hCGsVionKMZNk+uD0CLtMIkUzhQqpHbVntko3rY+O7ouOrTrikY/WQVPbo1bqX1cu/6/cHE4RVU3cZ7V5xnxVg==", "requires": { - "@authorizerdev/authorizer-js": "^0.4.0-beta.0", + "@authorizerdev/authorizer-js": "^0.4.0-beta.3", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" diff --git a/app/package.json b/app/package.json index 2076745..951d93f 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.6", + "@authorizerdev/authorizer-react": "0.9.0-beta.7", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/dashboard/src/components/Menu.tsx b/dashboard/src/components/Menu.tsx index 342e2c2..a8e2fdd 100644 --- a/dashboard/src/components/Menu.tsx +++ b/dashboard/src/components/Menu.tsx @@ -29,10 +29,11 @@ import { } from 'react-icons/fi'; import { IconType } from 'react-icons'; import { ReactText } from 'react'; -import { useMutation } from 'urql'; +import { useMutation, useQuery } from 'urql'; import { NavLink, useNavigate, useLocation } from 'react-router-dom'; import { useAuthContext } from '../contexts/AuthContext'; import { AdminLogout } from '../graphql/mutation'; +import { MetaQuery } from '../graphql/queries'; interface LinkItemProps { name: string; @@ -51,6 +52,7 @@ interface SidebarProps extends BoxProps { export const Sidebar = ({ onClose, ...rest }: SidebarProps) => { const { pathname } = useLocation(); + const [{ fetching, data }] = useQuery({ query: MetaQuery }); return ( { > API Playground + + {data?.meta?.version && ( + + Current Version: {data.meta.version} + + )} ); }; diff --git a/dashboard/src/graphql/queries/index.ts b/dashboard/src/graphql/queries/index.ts index b4efc70..8528f3f 100644 --- a/dashboard/src/graphql/queries/index.ts +++ b/dashboard/src/graphql/queries/index.ts @@ -1,3 +1,12 @@ +export const MetaQuery = ` + query MetaQuery { + meta { + version + client_id + } + } +`; + export const AdminSessionQuery = ` query { _admin_session{ diff --git a/dashboard/src/layouts/AuthLayout.tsx b/dashboard/src/layouts/AuthLayout.tsx index f115b38..dbe51ff 100644 --- a/dashboard/src/layouts/AuthLayout.tsx +++ b/dashboard/src/layouts/AuthLayout.tsx @@ -1,8 +1,10 @@ -import { Box, Center, Flex, Image, Text } from '@chakra-ui/react'; +import { Box, Flex, Image, Text, Spinner } from '@chakra-ui/react'; import React from 'react'; -import { LOGO_URL } from '../constants'; +import { useQuery } from 'urql'; +import { MetaQuery } from '../graphql/queries'; export function AuthLayout({ children }: { children: React.ReactNode }) { + const [{ fetching, data }] = useQuery({ query: MetaQuery }); return ( - - {children} - + {fetching ? ( + + ) : ( + <> + + {children} + + + Current Version: {data.meta.version} + + + )} ); } diff --git a/dashboard/src/pages/Auth.tsx b/dashboard/src/pages/Auth.tsx index be11ab8..2610dd1 100644 --- a/dashboard/src/pages/Auth.tsx +++ b/dashboard/src/pages/Auth.tsx @@ -6,7 +6,6 @@ import { useToast, VStack, Text, - Divider, } from '@chakra-ui/react'; import React, { useEffect } from 'react'; import { useMutation } from 'urql'; diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 3327538..817dd79 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -1343,6 +1343,7 @@ input SignUpInput { password: String! confirm_password: String! roles: [String!] + scope: [String!] } input LoginInput { @@ -7298,6 +7299,14 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i if err != nil { return it, err } + case "scope": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope")) + it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } } } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 9f4cdd9..ea069e5 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -153,6 +153,7 @@ type SignUpInput struct { Password string `json:"password"` ConfirmPassword string `json:"confirm_password"` Roles []string `json:"roles"` + Scope []string `json:"scope"` } type UpdateEnvInput struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index cf76429..18a727c 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -181,6 +181,7 @@ input SignUpInput { password: String! confirm_password: String! roles: [String!] + scope: [String!] } input LoginInput { diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 8830195..245d7d8 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -109,7 +109,5 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type ( - mutationResolver struct{ *Resolver } - queryResolver struct{ *Resolver } -) +type mutationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 2f15f15..308d284 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -151,6 +151,9 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR } } else { scope := []string{"openid", "email", "profile"} + if params.Scope != nil && len(scope) > 0 { + scope = params.Scope + } authToken, err := token.CreateAuthToken(gc, user, roles, scope) if err != nil { From 776c0fba8baa7571fc5945a76c739a4b75d6563e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 17:21:55 +0530 Subject: [PATCH 40/41] chore: app dependencies --- app/package-lock.json | 2 +- app/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 4f02a7b..08d19ca 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.7", + "@authorizerdev/authorizer-react": "latest", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/package.json b/app/package.json index 951d93f..cd974d6 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "0.9.0-beta.7", + "@authorizerdev/authorizer-react": "latest", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", From 2bf6b8f91d968c35c7c17e6f4e3cc1957cf322fd Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 9 Mar 2022 17:24:53 +0530 Subject: [PATCH 41/41] fix: remove log --- server/token/jwt.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/token/jwt.go b/server/token/jwt.go index f3e74c8..90f6333 100644 --- a/server/token/jwt.go +++ b/server/token/jwt.go @@ -2,7 +2,6 @@ package token import ( "errors" - "fmt" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/crypto" @@ -92,7 +91,6 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error return claims, errors.New("invalid audience") } - fmt.Println("claims:", claims["nonce"], nonce, claims["nonce"] == nonce) if claims["nonce"] != nonce { return claims, errors.New("invalid nonce") }