From 0115128ee72dc4bc8d3c977996937ac766dfe8de Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 9 Oct 2022 19:48:13 +0530 Subject: [PATCH 01/32] fix(server): authorizer as oauth provider --- server/constants/oauth2.go | 17 +++ server/handlers/authorize.go | 185 +++++------------------ server/memorystore/memory_store.go | 2 +- server/memorystore/required_env_store.go | 4 +- 4 files changed, 61 insertions(+), 147 deletions(-) create mode 100644 server/constants/oauth2.go diff --git a/server/constants/oauth2.go b/server/constants/oauth2.go new file mode 100644 index 0000000..d3ff253 --- /dev/null +++ b/server/constants/oauth2.go @@ -0,0 +1,17 @@ +package constants + +const ( + // - query: for Authorization Code grant. 302 Found triggers redirect. + ResponseModeQuery = "query" + // - fragment: for Implicit grant. 302 Found triggers redirect. + ResponseModeFragment = "fragment" + // - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters. + ResponseModeFormPost = "form_post" + // - web_message: For Silent Authentication. Uses HTML5 web messaging. + ResponseModeWebMessage = "web_message" + + // For the Authorization Code grant, use response_type=code to include the authorization code. + ResponseTypeCode = "code" + // For the Implicit grant, use response_type=token to include an access token. + ResponseTypeToken = "token" +) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index fd2372c..b3db022 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "strconv" "strings" @@ -45,176 +46,42 @@ func AuthorizeHandler() gin.HandlerFunc { } if responseMode == "" { - responseMode = "query" - } - - if responseMode != "query" && responseMode != "web_message" { - log.Debug("Invalid response_mode: ", responseMode) - gc.JSON(400, gin.H{"error": "invalid response mode"}) + responseMode = constants.ResponseModeQuery } if redirectURI == "" { redirectURI = "/app" } - isQuery := responseMode == "query" - - loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI - - if clientID == "" { - if isQuery { - gc.Redirect(http.StatusFound, loginURL) - } else { - log.Debug("Failed to get client_id: ", 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", - }, - }, - }) - } - return - } - - if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil { - if isQuery { - gc.Redirect(http.StatusFound, loginURL) - } else { - log.Debug("Invalid client_id: ", clientID) - 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 state == "" { - if isQuery { - gc.Redirect(http.StatusFound, loginURL) - } else { - log.Debug("Failed to get state: ", 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", - }, - }, - }) - } - return - } - if responseType == "" { responseType = "token" } - isResponseTypeCode := responseType == "code" - isResponseTypeToken := responseType == "token" - - if !isResponseTypeCode && !isResponseTypeToken { - if isQuery { - gc.Redirect(http.StatusFound, loginURL) - } else { - log.Debug("Invalid response_type: ", responseType) - 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 err := validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge); err != nil { + log.Debug("invalid authorization request: ", err) + gc.JSON(http.StatusBadRequest, gin.H{"error": err}) return } - if isResponseTypeCode { - if codeChallenge == "" { - if isQuery { - gc.Redirect(http.StatusFound, loginURL) - } else { - log.Debug("Failed to get code_challenge: ", 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", - }, - }, - }) - } - return - } - } - sessionToken, err := cookie.GetSession(gc) if err != nil { - 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", - }, - }, - }) - } + log.Debug("GetSession failed: ", err) + gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("login required. %v", err)}) return } // get session from cookie claims, err := token.ValidateBrowserSession(gc, sessionToken) if err != nil { - 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", - }, - }, - }) - } + log.Debug("ValidateBrowserSession failed: ", err) + gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("login required. %v", err)}) return } userID := claims.Subject user, err := db.Provider.GetUserByID(gc, userID) if err != nil { - 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", - }, - }, - }) - } + log.Debug("GetUserByID failed: ", err) + gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("sign up required. %v", err)}) return } @@ -223,6 +90,12 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + loginURL := "/app?" + loginState + if responseMode == constants.ResponseModeFragment { + loginURL = "/app#" + loginState + } + // if user is logged in // based on the response type code, generate the response if isResponseTypeCode { @@ -349,3 +222,27 @@ func AuthorizeHandler() gin.HandlerFunc { } } } + +func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error { + if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken { + return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode) + } + + if responseMode != constants.ResponseModeQuery && responseMode != constants.ResponseModeWebMessage && responseMode != constants.ResponseModeFragment && responseMode != constants.ResponseModeFormPost { + return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode") + } + + if responseType == constants.ResponseTypeCode && strings.TrimSpace(codeChallenge) == "" { + return fmt.Errorf("code_challenge is required for %s '%s'", responseType, constants.ResponseTypeCode) + } + + if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil { + return fmt.Errorf("invalid client_id %s", clientID) + } + + if strings.TrimSpace(state) == "" { + return fmt.Errorf("state is required") + } + + return nil +} diff --git a/server/memorystore/memory_store.go b/server/memorystore/memory_store.go index c112c01..15b7248 100644 --- a/server/memorystore/memory_store.go +++ b/server/memorystore/memory_store.go @@ -57,7 +57,7 @@ func InitMemStore() error { } redisURL := requiredEnvs.RedisURL - if redisURL != "" && !requiredEnvs.disableRedisForEnv { + if redisURL != "" && !requiredEnvs.DisableRedisForEnv { log.Info("Initializing Redis memory store") Provider, err = redis.NewRedisProvider(redisURL) if err != nil { diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go index a5f3a81..b81cf56 100644 --- a/server/memorystore/required_env_store.go +++ b/server/memorystore/required_env_store.go @@ -27,7 +27,7 @@ type RequiredEnv struct { DatabaseCertKey string `json:"DATABASE_CERT_KEY"` DatabaseCACert string `json:"DATABASE_CA_CERT"` RedisURL string `json:"REDIS_URL"` - disableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"` + DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"` } // RequiredEnvObj is a simple in-memory store for sessions. @@ -138,7 +138,7 @@ func InitRequiredEnv() error { DatabaseCertKey: dbCertKey, DatabaseCACert: dbCACert, RedisURL: redisURL, - disableRedisForEnv: disableRedisForEnv, + DisableRedisForEnv: disableRedisForEnv, } RequiredEnvStoreObj = &RequiredEnvStore{ From ff805e3ef2720f1b659572fa16fdd2949efdbeb6 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 12 Oct 2022 13:10:24 +0530 Subject: [PATCH 02/32] fix: add comments --- server/handlers/authorize.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index b3db022..4f1df0c 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -90,17 +90,19 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } + // used for response mode query or fragment loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { loginURL = "/app#" + loginState } + // rollover the session for security + go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) + // if user is logged in // based on the response type code, generate the response if isResponseTypeCode { - // rollover the session for security - go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) if err != nil { @@ -159,7 +161,6 @@ func AuthorizeHandler() gin.HandlerFunc { return } - go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) cookie.SetSession(gc, authToken.FingerPrintHash) From 2bd92d60286d2fe811dd101fe07455fb794efa09 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 16 Oct 2022 20:46:54 +0530 Subject: [PATCH 03/32] feat: add form_post method --- server/handlers/authorize.go | 180 ++++++++++-------- server/resolvers/forgot_password.go | 2 +- templates/authorize_form_post.tmpl | 13 ++ ...horize.tmpl => authorize_web_message.tmpl} | 0 4 files changed, 118 insertions(+), 77 deletions(-) create mode 100644 templates/authorize_form_post.tmpl rename templates/{authorize.tmpl => authorize_web_message.tmpl} (100%) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 4f1df0c..9337ce9 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -27,6 +27,12 @@ import ( // 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. + +const ( + authorizeWebMessageTemplate = "authorize_web_message.tmpl" + authorizeFormPostTemplate = "authorize_form_post.tmpl" +) + func AuthorizeHandler() gin.HandlerFunc { return func(gc *gin.Context) { redirectURI := strings.TrimSpace(gc.Query("redirect_uri")) @@ -35,7 +41,6 @@ func AuthorizeHandler() gin.HandlerFunc { codeChallenge := strings.TrimSpace(gc.Query("code_challenge")) scopeString := strings.TrimSpace(gc.Query("scope")) clientID := strings.TrimSpace(gc.Query("client_id")) - template := "authorize.tmpl" responseMode := strings.TrimSpace(gc.Query("response_mode")) var scope []string @@ -63,10 +68,22 @@ func AuthorizeHandler() gin.HandlerFunc { return } + // used for response mode query or fragment + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + loginURL := "/app?" + loginState + if responseMode == constants.ResponseModeFragment { + loginURL = "/app#" + loginState + } + + loginError := map[string]interface{}{ + "error": "login_required", + "error_description": "Login is required", + } + sessionToken, err := cookie.GetSession(gc) if err != nil { log.Debug("GetSession failed: ", err) - gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("login required. %v", err)}) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } @@ -74,14 +91,17 @@ func AuthorizeHandler() gin.HandlerFunc { claims, err := token.ValidateBrowserSession(gc, sessionToken) if err != nil { log.Debug("ValidateBrowserSession failed: ", err) - gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("login required. %v", err)}) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } userID := claims.Subject user, err := db.Provider.GetUserByID(gc, userID) if err != nil { log.Debug("GetUserByID failed: ", err) - gc.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("sign up required. %v", err)}) + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "error": "signup_required", + "error_description": "Sign up required", + }, http.StatusOK) return } @@ -90,44 +110,34 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } - // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI - loginURL := "/app?" + loginState - if responseMode == constants.ResponseModeFragment { - loginURL = "/app#" + loginState - } - // rollover the session for security go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) - - // if user is logged in - // based on the response type code, generate the response - if isResponseTypeCode { + if responseType == constants.ResponseTypeCode { nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) if err != nil { - 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", - }, - }, - }) - } + log.Debug("CreateSessionToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken); err != nil { + log.Debug("SetUserSession failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken) cookie.SetSession(gc, newSessionToken) code := uuid.New().String() - memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) - gc.HTML(http.StatusOK, template, gin.H{ + if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { + log.Debug("SetState failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + // in case, response type is code and user is already logged in send the code and state + // and cookie session will already be rolled over and set + gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ "target_origin": redirectURI, "authorization_response": map[string]interface{}{ "type": "authorization_response", @@ -140,29 +150,27 @@ func AuthorizeHandler() gin.HandlerFunc { return } - if isResponseTypeToken { + if responseType == constants.ResponseTypeToken { // rollover the session for security authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod) if err != nil { - 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", - }, - }, - }) - } + log.Debug("CreateAuthToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash); err != nil { + log.Debug("SetUserSession failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token); err != nil { + log.Debug("SetUserSession failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) - memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) cookie.SetSession(gc, authToken.FingerPrintHash) expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() @@ -188,39 +196,28 @@ func AuthorizeHandler() gin.HandlerFunc { memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } - if isQuery { + if responseMode == constants.ResponseModeQuery { if strings.Contains(redirectURI, "?") { - gc.Redirect(http.StatusFound, redirectURI+"&"+params) + redirectURI = redirectURI + "&" + params } else { - gc.Redirect(http.StatusFound, redirectURI+"?"+params) + redirectURI = redirectURI + "?" + params + } + } else if responseMode == constants.ResponseModeFragment { + if strings.Contains(redirectURI, "#") { + redirectURI = redirectURI + "&" + params + } else { + redirectURI = redirectURI + "#" + params } - } else { - gc.HTML(http.StatusOK, template, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": res, - }, - }) } + + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "type": "authorization_response", + "response": res, + }, http.StatusOK) return } - 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", - }, - }, - }) - } + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) } } @@ -230,7 +227,7 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC } if responseMode != constants.ResponseModeQuery && responseMode != constants.ResponseModeWebMessage && responseMode != constants.ResponseModeFragment && responseMode != constants.ResponseModeFormPost { - return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode") + return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode", responseMode) } if responseType == constants.ResponseTypeCode && strings.TrimSpace(codeChallenge) == "" { @@ -247,3 +244,34 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC return nil } + +func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { + isAuthenticationRequired := false + if val, ok := data["error"]; ok { + if val == "login_required" || val == "signup_required" { + isAuthenticationRequired = true + } + } + + switch responseMode { + case constants.ResponseModeQuery, constants.ResponseModeFragment: + if isAuthenticationRequired { + gc.Redirect(http.StatusFound, loginURI) + } else { + gc.Redirect(http.StatusFound, redirectURI) + } + return + case constants.ResponseModeWebMessage: + gc.HTML(httpStatusCode, authorizeWebMessageTemplate, gin.H{ + "target_origin": redirectURI, + "authorization_response": data, + }) + return + case constants.ResponseModeFormPost: + gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{ + "target_origin": redirectURI, + "authorization_response": data, + }) + return + } +} diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index c04bf1c..5f58ef3 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -85,7 +85,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu return res, err } - // exec it as go routine so that we can reduce the api latency + // execute it as go routine so that we can reduce the api latency go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{ "user": user.ToMap(), "organization": utils.GetOrganization(), diff --git a/templates/authorize_form_post.tmpl b/templates/authorize_form_post.tmpl new file mode 100644 index 0000000..b3c94bf --- /dev/null +++ b/templates/authorize_form_post.tmpl @@ -0,0 +1,13 @@ + + + + Authorization Response + + +
+ {{ range $key, $val := .authorization_response }} + + {{ end }} +
+ + diff --git a/templates/authorize.tmpl b/templates/authorize_web_message.tmpl similarity index 100% rename from templates/authorize.tmpl rename to templates/authorize_web_message.tmpl From 3cd99fe5f62b58e66b3bbcbd2be7236c37b10b18 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 16 Oct 2022 21:03:37 +0530 Subject: [PATCH 04/32] fix: open id config --- server/constants/oauth2.go | 2 ++ server/handlers/authorize.go | 36 ++++++++++++++++++++++---------- server/handlers/openid_config.go | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/server/constants/oauth2.go b/server/constants/oauth2.go index d3ff253..f3e0a67 100644 --- a/server/constants/oauth2.go +++ b/server/constants/oauth2.go @@ -14,4 +14,6 @@ const ( ResponseTypeCode = "code" // For the Implicit grant, use response_type=token to include an access token. ResponseTypeToken = "token" + // For the Implicit grant of id_token, use response_type=id_token to include an identifier token. + ResponseTypeIDToken = "id_token" ) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 9337ce9..ad26576 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -137,20 +137,34 @@ func AuthorizeHandler() gin.HandlerFunc { // in case, response type is code and user is already logged in send the code and state // and cookie session will already be rolled over and set - gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "code": code, - "state": state, + if responseMode == constants.ResponseModeFormPost { + gc.HTML(http.StatusOK, authorizeFormPostTemplate, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "code": code, + "state": state, + }, }, - }, - }) + }) + } else { + gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "code": code, + "state": state, + }, + }, + }) + } + return } - if responseType == constants.ResponseTypeToken { + if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { // rollover the session for security authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod) if err != nil { @@ -222,7 +236,7 @@ func AuthorizeHandler() gin.HandlerFunc { } func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error { - if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken { + if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken { return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode) } diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index 781caf1..db3a52f 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -22,7 +22,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { "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"}, + "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"}, "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"}, }) From 346c8e5a479eec9aae6d09bad7ca8ab2fec85058 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 16 Oct 2022 22:16:37 +0530 Subject: [PATCH 05/32] fix: handle response --- server/handlers/authorize.go | 65 +++++++++++++++++++++----------- server/handlers/openid_config.go | 2 +- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index ad26576..19f5101 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -137,30 +137,51 @@ func AuthorizeHandler() gin.HandlerFunc { // in case, response type is code and user is already logged in send the code and state // and cookie session will already be rolled over and set - if responseMode == constants.ResponseModeFormPost { - gc.HTML(http.StatusOK, authorizeFormPostTemplate, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "code": code, - "state": state, - }, - }, - }) - } else { - gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "code": code, - "state": state, - }, - }, - }) + // if responseMode == constants.ResponseModeFormPost { + // gc.HTML(http.StatusOK, authorizeFormPostTemplate, gin.H{ + // "target_origin": redirectURI, + // "authorization_response": map[string]interface{}{ + // "type": "authorization_response", + // "response": map[string]string{ + // "code": code, + // "state": state, + // }, + // }, + // }) + // } else { + // gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ + // "target_origin": redirectURI, + // "authorization_response": map[string]interface{}{ + // "type": "authorization_response", + // "response": map[string]string{ + // "code": code, + // "state": state, + // }, + // }, + // }) + // } + + params := "code=" + code + "&state=" + state + + if responseMode == constants.ResponseModeQuery { + if strings.Contains(redirectURI, "?") { + redirectURI = redirectURI + "&" + params + } else { + redirectURI = redirectURI + "?" + params + } + } else if responseMode == constants.ResponseModeFragment { + if strings.Contains(redirectURI, "#") { + redirectURI = redirectURI + "&" + params + } else { + redirectURI = redirectURI + "#" + params + } } + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "code": code, + "state": state, + }, http.StatusOK) + return } diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index db3a52f..c2a95c4 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -20,7 +20,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { "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"}, + "response_types_supported": []string{"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", "web_message"}, "id_token_signing_alg_values_supported": []string{jwtType}, From 9a411e673c2a9be931b66040c96ef165a6352cbe Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 21:08:53 +0530 Subject: [PATCH 06/32] fix: reponse --- server/handlers/authorize.go | 100 +++++++++++++++++------------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 19f5101..d93847d 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -68,6 +68,15 @@ func AuthorizeHandler() gin.HandlerFunc { return } + log := log.WithFields(log.Fields{ + "response_mode": responseMode, + "response_type": responseType, + "state": state, + "code_challenge": codeChallenge, + "scope": scope, + "redirect_uri": redirectURI, + }) + // used for response mode query or fragment loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI loginURL := "/app?" + loginState @@ -76,8 +85,11 @@ func AuthorizeHandler() gin.HandlerFunc { } loginError := map[string]interface{}{ - "error": "login_required", - "error_description": "Login is required", + "type": "authorization_response", + "response": map[string]string{ + "error": "login_required", + "error_description": "Login is required", + }, } sessionToken, err := cookie.GetSession(gc) @@ -99,8 +111,11 @@ func AuthorizeHandler() gin.HandlerFunc { if err != nil { log.Debug("GetUserByID failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ - "error": "signup_required", - "error_description": "Sign up required", + "type": "authorization_response", + "response": map[string]string{ + "error": "signup_required", + "error_description": "Sign up required", + }, }, http.StatusOK) return } @@ -137,50 +152,37 @@ func AuthorizeHandler() gin.HandlerFunc { // in case, response type is code and user is already logged in send the code and state // and cookie session will already be rolled over and set - // if responseMode == constants.ResponseModeFormPost { - // gc.HTML(http.StatusOK, authorizeFormPostTemplate, gin.H{ - // "target_origin": redirectURI, - // "authorization_response": map[string]interface{}{ - // "type": "authorization_response", - // "response": map[string]string{ - // "code": code, - // "state": state, - // }, - // }, - // }) - // } else { - // gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ - // "target_origin": redirectURI, - // "authorization_response": map[string]interface{}{ - // "type": "authorization_response", - // "response": map[string]string{ - // "code": code, - // "state": state, - // }, - // }, - // }) + gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ + "target_origin": redirectURI, + "authorization_response": map[string]interface{}{ + "type": "authorization_response", + "response": map[string]string{ + "code": code, + "state": state, + }, + }, + }) + + // params := "code=" + code + "&state=" + state + + // if responseMode == constants.ResponseModeQuery { + // if strings.Contains(redirectURI, "?") { + // redirectURI = redirectURI + "&" + params + // } else { + // redirectURI = redirectURI + "?" + params + // } + // } else if responseMode == constants.ResponseModeFragment { + // if strings.Contains(redirectURI, "#") { + // redirectURI = redirectURI + "&" + params + // } else { + // redirectURI = redirectURI + "#" + params + // } // } - params := "code=" + code + "&state=" + state - - if responseMode == constants.ResponseModeQuery { - if strings.Contains(redirectURI, "?") { - redirectURI = redirectURI + "&" + params - } else { - redirectURI = redirectURI + "?" + params - } - } else if responseMode == constants.ResponseModeFragment { - if strings.Contains(redirectURI, "#") { - redirectURI = redirectURI + "&" + params - } else { - redirectURI = redirectURI + "#" + params - } - } - - handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ - "code": code, - "state": state, - }, http.StatusOK) + // handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + // "code": code, + // "state": state, + // }, http.StatusOK) return } @@ -282,10 +284,8 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { isAuthenticationRequired := false - if val, ok := data["error"]; ok { - if val == "login_required" || val == "signup_required" { - isAuthenticationRequired = true - } + if _, ok := data["error"]; ok { + isAuthenticationRequired = true } switch responseMode { From 8e655bcb5bc9cfb017076d83717599d2b9975390 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 21:29:09 +0530 Subject: [PATCH 07/32] fix: authorize response --- server/handlers/authorize.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index d93847d..e2e74ab 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -152,16 +152,16 @@ func AuthorizeHandler() gin.HandlerFunc { // in case, response type is code and user is already logged in send the code and state // and cookie session will already be rolled over and set - gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ - "target_origin": redirectURI, - "authorization_response": map[string]interface{}{ - "type": "authorization_response", - "response": map[string]string{ - "code": code, - "state": state, - }, - }, - }) + // gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{ + // "target_origin": redirectURI, + // "authorization_response": map[string]interface{}{ + // "type": "authorization_response", + // "response": map[string]string{ + // "code": code, + // "state": state, + // }, + // }, + // }) // params := "code=" + code + "&state=" + state @@ -179,10 +179,10 @@ func AuthorizeHandler() gin.HandlerFunc { // } // } - // handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ - // "code": code, - // "state": state, - // }, http.StatusOK) + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "code": code, + "state": state, + }, http.StatusOK) return } From cddfe1e0887566c7a8323f1f6ce573d267f9a00c Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 21:46:37 +0530 Subject: [PATCH 08/32] fix: response --- server/handlers/authorize.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index e2e74ab..2d95458 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -180,8 +180,11 @@ func AuthorizeHandler() gin.HandlerFunc { // } handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ - "code": code, - "state": state, + "type": "authorization_response", + "response": map[string]string{ + "code": code, + "state": state, + }, }, http.StatusOK) return From 253128ca0c130ac74c2d51ec0c0772069fc11371 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 22:00:54 +0530 Subject: [PATCH 09/32] fix: query params for code response --- server/handlers/authorize.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 2d95458..805912d 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -163,21 +163,20 @@ func AuthorizeHandler() gin.HandlerFunc { // }, // }) - // params := "code=" + code + "&state=" + state - - // if responseMode == constants.ResponseModeQuery { - // if strings.Contains(redirectURI, "?") { - // redirectURI = redirectURI + "&" + params - // } else { - // redirectURI = redirectURI + "?" + params - // } - // } else if responseMode == constants.ResponseModeFragment { - // if strings.Contains(redirectURI, "#") { - // redirectURI = redirectURI + "&" + params - // } else { - // redirectURI = redirectURI + "#" + params - // } - // } + params := "code=" + code + "&state=" + state + if responseMode == constants.ResponseModeQuery { + if strings.Contains(redirectURI, "?") { + redirectURI = redirectURI + "&" + params + } else { + redirectURI = redirectURI + "?" + params + } + } else if responseMode == constants.ResponseModeFragment { + if strings.Contains(redirectURI, "#") { + redirectURI = redirectURI + "&" + params + } else { + redirectURI = redirectURI + "#" + params + } + } handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ "type": "authorization_response", From eaa10ec5bc0b6d071fcdd16c09fc313b334b8a23 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 22:34:57 +0530 Subject: [PATCH 10/32] fix: error detection --- server/handlers/authorize.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 805912d..a550a06 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -106,6 +106,7 @@ func AuthorizeHandler() gin.HandlerFunc { handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } + userID := claims.Subject user, err := db.Provider.GetUserByID(gc, userID) if err != nil { @@ -286,7 +287,7 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { isAuthenticationRequired := false - if _, ok := data["error"]; ok { + if _, ok := data["response"].(map[string]string)["error"]; ok { isAuthenticationRequired = true } From 7c2693b086f79fab7ef61249e07dcb88abf94471 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 23:03:52 +0530 Subject: [PATCH 11/32] fix: form post template --- server/handlers/authorize.go | 10 +++++----- templates/authorize_form_post.tmpl | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index a550a06..0a8ee75 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -86,7 +86,7 @@ func AuthorizeHandler() gin.HandlerFunc { loginError := map[string]interface{}{ "type": "authorization_response", - "response": map[string]string{ + "response": map[string]interface{}{ "error": "login_required", "error_description": "Login is required", }, @@ -113,7 +113,7 @@ func AuthorizeHandler() gin.HandlerFunc { log.Debug("GetUserByID failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ "type": "authorization_response", - "response": map[string]string{ + "response": map[string]interface{}{ "error": "signup_required", "error_description": "Sign up required", }, @@ -181,7 +181,7 @@ func AuthorizeHandler() gin.HandlerFunc { handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ "type": "authorization_response", - "response": map[string]string{ + "response": map[string]interface{}{ "code": code, "state": state, }, @@ -287,7 +287,7 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { isAuthenticationRequired := false - if _, ok := data["response"].(map[string]string)["error"]; ok { + if _, ok := data["response"].(map[string]interface{})["error"]; ok { isAuthenticationRequired = true } @@ -308,7 +308,7 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, case constants.ResponseModeFormPost: gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{ "target_origin": redirectURI, - "authorization_response": data, + "authorization_response": data["response"], }) return } diff --git a/templates/authorize_form_post.tmpl b/templates/authorize_form_post.tmpl index b3c94bf..3098636 100644 --- a/templates/authorize_form_post.tmpl +++ b/templates/authorize_form_post.tmpl @@ -4,9 +4,9 @@ Authorization Response -
+ {{ range $key, $val := .authorization_response }} - + {{ end }}
From 252cd1fa2d25cf5b50907582dddb54afdacd1dcb Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 23:14:24 +0530 Subject: [PATCH 12/32] fix: make code_challenge optional --- server/handlers/authorize.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 0a8ee75..5ececd2 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -64,7 +64,7 @@ func AuthorizeHandler() gin.HandlerFunc { if err := validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge); err != nil { log.Debug("invalid authorization request: ", err) - gc.JSON(http.StatusBadRequest, gin.H{"error": err}) + gc.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -270,10 +270,6 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode", responseMode) } - if responseType == constants.ResponseTypeCode && strings.TrimSpace(codeChallenge) == "" { - return fmt.Errorf("code_challenge is required for %s '%s'", responseType, constants.ResponseTypeCode) - } - if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil { return fmt.Errorf("invalid client_id %s", clientID) } From c716638725118ed0a3fa0a3d61855b44ea6caf5f Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Oct 2022 23:24:19 +0530 Subject: [PATCH 13/32] fix(server): revert the state & code_challenge validation --- server/handlers/authorize.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 5ececd2..ba745f5 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -80,10 +80,32 @@ func AuthorizeHandler() gin.HandlerFunc { // used for response mode query or fragment loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI loginURL := "/app?" + loginState + if responseMode == constants.ResponseModeFragment { loginURL = "/app#" + loginState } + if state == "" { + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "type": "authorization_response", + "response": map[string]interface{}{ + "error": "state_required", + "error_description": "state is required", + }, + }, http.StatusOK) + return + } + + if responseType == constants.ResponseTypeCode && codeChallenge == "" { + handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ + "type": "authorization_response", + "response": map[string]interface{}{ + "error": "code_challenge_required", + "error_description": "code challenge is required", + }, + }, http.StatusOK) + } + loginError := map[string]interface{}{ "type": "authorization_response", "response": map[string]interface{}{ @@ -91,7 +113,6 @@ func AuthorizeHandler() gin.HandlerFunc { "error_description": "Login is required", }, } - sessionToken, err := cookie.GetSession(gc) if err != nil { log.Debug("GetSession failed: ", err) @@ -274,10 +295,6 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC return fmt.Errorf("invalid client_id %s", clientID) } - if strings.TrimSpace(state) == "" { - return fmt.Errorf("state is required") - } - return nil } From 2b52932e986ce5f57a077609797eb63eba4b1699 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 09:03:00 +0530 Subject: [PATCH 14/32] fix: add code to other response methods --- server/handlers/authorize.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index ba745f5..1b3d51c 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -147,17 +147,24 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } + nonce := uuid.New().String() + newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) + if err != nil { + log.Debug("CreateSessionToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + code := uuid.New().String() + if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { + log.Debug("SetState failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + // rollover the session for security go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) if responseType == constants.ResponseTypeCode { - nonce := uuid.New().String() - newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) - if err != nil { - log.Debug("CreateSessionToken failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) @@ -165,12 +172,6 @@ func AuthorizeHandler() gin.HandlerFunc { } cookie.SetSession(gc, newSessionToken) - code := uuid.New().String() - if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { - log.Debug("SetState failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } // in case, response type is code and user is already logged in send the code and state // and cookie session will already be rolled over and set @@ -249,6 +250,7 @@ func AuthorizeHandler() gin.HandlerFunc { "scope": scope, "token_type": "Bearer", "expires_in": expiresIn, + "code": code, } if authToken.RefreshToken != nil { From 7ff3b3018ac4d041142469ba5c7d5b1bd63eaa59 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 11:29:49 +0530 Subject: [PATCH 15/32] fix: add code to query params --- server/handlers/authorize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 1b3d51c..53c357f 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -241,7 +241,7 @@ func AuthorizeHandler() gin.HandlerFunc { } // 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 + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, From cc23784df884790fc4b8c8ceefbb8f2593030afa Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 12:01:34 +0530 Subject: [PATCH 16/32] fix: add code to login query params --- server/handlers/authorize.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 53c357f..d9cc5a5 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -77,8 +77,11 @@ func AuthorizeHandler() gin.HandlerFunc { "redirect_uri": redirectURI, }) + code := uuid.New().String() + memorystore.Provider.SetState(codeChallenge, code) + // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { @@ -155,7 +158,6 @@ func AuthorizeHandler() gin.HandlerFunc { return } - code := uuid.New().String() if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { log.Debug("SetState failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) From 89f08b6d317b6284d6b49cbd96c619bb6adeb628 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 12:20:22 +0530 Subject: [PATCH 17/32] fix: redirect from app --- app/src/Root.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/Root.tsx b/app/src/Root.tsx index 88cec21..189f261 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -38,6 +38,7 @@ export default function Root({ const scope = searchParams.get('scope') ? searchParams.get('scope')?.toString().split(' ') : ['openid', 'profile', 'email']; + const code = searchParams.get('code') || createRandomString() const urlProps: Record = { state, @@ -57,7 +58,7 @@ export default function Root({ useEffect(() => { if (token) { let redirectURL = config.redirectURL || '/app'; - let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`; + let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}&code=`+code; if (token.refresh_token) { params += `&refresh_token=${token.refresh_token}`; } From a916b8c32cbd3185decb68785b0eb58e7beddc12 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 19:04:15 +0530 Subject: [PATCH 18/32] fix: add nonce --- app/src/Root.tsx | 11 ++++++++++- server/handlers/authorize.go | 9 +++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/Root.tsx b/app/src/Root.tsx index 189f261..b3fee57 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -38,7 +38,8 @@ export default function Root({ const scope = searchParams.get('scope') ? searchParams.get('scope')?.toString().split(' ') : ['openid', 'profile', 'email']; - const code = searchParams.get('code') || createRandomString() + const code = searchParams.get('code') || '' + const nonce = searchParams.get('nonce') || '' const urlProps: Record = { state, @@ -59,9 +60,17 @@ export default function Root({ if (token) { let redirectURL = config.redirectURL || '/app'; let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}&code=`+code; + + if (code !== '') { + params += `&code=${code}` + } + if (nonce !== '') { + params += `&nonce=${nonce}` + } if (token.refresh_token) { params += `&refresh_token=${token.refresh_token}`; } + const url = new URL(redirectURL); if (redirectURL.includes('?')) { redirectURL = `${redirectURL}&${params}`; diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index d9cc5a5..1ffcabf 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -78,10 +78,11 @@ func AuthorizeHandler() gin.HandlerFunc { }) code := uuid.New().String() + nonce := uuid.New().String() memorystore.Provider.SetState(codeChallenge, code) // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + "&nonce=" + nonce loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { @@ -150,7 +151,6 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } - nonce := uuid.New().String() newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) if err != nil { log.Debug("CreateSessionToken failed: ", err) @@ -188,7 +188,7 @@ func AuthorizeHandler() gin.HandlerFunc { // }, // }) - params := "code=" + code + "&state=" + state + params := "code=" + code + "&state=" + state + "&nonce=" + nonce if responseMode == constants.ResponseModeQuery { if strings.Contains(redirectURI, "?") { redirectURI = redirectURI + "&" + params @@ -243,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc { } // 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 + "&code=" + code + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code + "&nonce=" + nonce res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, @@ -253,6 +253,7 @@ func AuthorizeHandler() gin.HandlerFunc { "token_type": "Bearer", "expires_in": expiresIn, "code": code, + "nonce": nonce, } if authToken.RefreshToken != nil { From de4381261e7b00eba7b437df1a914cdcc68b834a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 23:17:13 +0530 Subject: [PATCH 19/32] fix: add nonce to supported claims --- app/src/Root.tsx | 7 ++----- server/handlers/authorize.go | 5 ++--- server/handlers/openid_config.go | 2 +- server/handlers/token.go | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/Root.tsx b/app/src/Root.tsx index b3fee57..cf1041e 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -39,7 +39,6 @@ export default function Root({ ? searchParams.get('scope')?.toString().split(' ') : ['openid', 'profile', 'email']; const code = searchParams.get('code') || '' - const nonce = searchParams.get('nonce') || '' const urlProps: Record = { state, @@ -59,14 +58,12 @@ export default function Root({ useEffect(() => { if (token) { let redirectURL = config.redirectURL || '/app'; - let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}&code=`+code; + let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`; if (code !== '') { params += `&code=${code}` } - if (nonce !== '') { - params += `&nonce=${nonce}` - } + if (token.refresh_token) { params += `&refresh_token=${token.refresh_token}`; } diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 1ffcabf..397aac6 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -188,7 +188,7 @@ func AuthorizeHandler() gin.HandlerFunc { // }, // }) - params := "code=" + code + "&state=" + state + "&nonce=" + nonce + params := "code=" + code + "&state=" + state if responseMode == constants.ResponseModeQuery { if strings.Contains(redirectURI, "?") { redirectURI = redirectURI + "&" + params @@ -243,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc { } // 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 + "&code=" + code + "&nonce=" + nonce + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, @@ -253,7 +253,6 @@ func AuthorizeHandler() gin.HandlerFunc { "token_type": "Bearer", "expires_in": expiresIn, "code": code, - "nonce": nonce, } if authToken.RefreshToken != nil { diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index c2a95c4..8138d42 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -24,7 +24,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { "scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"}, "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"}, "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"}, + "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", "nonce"}, }) } } diff --git a/server/handlers/token.go b/server/handlers/token.go index da72969..97bce00 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -22,7 +22,7 @@ import ( func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { var reqBody map[string]string - if err := gc.BindJSON(&reqBody); err != nil { + if err := gc.Bind(&reqBody); err != nil { log.Debug("Error binding JSON: ", err) gc.JSON(http.StatusBadRequest, gin.H{ "error": "error_binding_json", From fedc3173fefa98344ab3d5c1ce70a75a39cfed3f Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 23:36:33 +0530 Subject: [PATCH 20/32] fix: get nonce from query request if possible --- server/handlers/authorize.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 397aac6..8dfcbc9 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -42,6 +42,7 @@ func AuthorizeHandler() gin.HandlerFunc { scopeString := strings.TrimSpace(gc.Query("scope")) clientID := strings.TrimSpace(gc.Query("client_id")) responseMode := strings.TrimSpace(gc.Query("response_mode")) + nonce := strings.TrimSpace(gc.Query("nonce")) var scope []string if scopeString == "" { @@ -78,11 +79,13 @@ func AuthorizeHandler() gin.HandlerFunc { }) code := uuid.New().String() - nonce := uuid.New().String() + if nonce == "" { + nonce = uuid.New().String() + } memorystore.Provider.SetState(codeChallenge, code) // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + "&nonce=" + nonce + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { From 74b858ac24afc63400e879dfc865bc6b299cd757 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 23:39:48 +0530 Subject: [PATCH 21/32] fix: binding --- server/handlers/token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers/token.go b/server/handlers/token.go index 97bce00..da72969 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -22,7 +22,7 @@ import ( func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { var reqBody map[string]string - if err := gc.Bind(&reqBody); err != nil { + if err := gc.BindJSON(&reqBody); err != nil { log.Debug("Error binding JSON: ", err) gc.JSON(http.StatusBadRequest, gin.H{ "error": "error_binding_json", From 2c867b0314929e573afca9224789e2cd7b1fc454 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 23:41:08 +0530 Subject: [PATCH 22/32] fix: issuer token endpoint --- server/handlers/openid_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index 8138d42..8785437 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 + "/token", + "token_endpoint": issuer + "/oauth/token", "userinfo_endpoint": issuer + "/userinfo", "jwks_uri": issuer + "/.well-known/jwks.json", "response_types_supported": []string{"code", "token", "id_token"}, From a68876a6f43edb86d47355aa1245aa005af62f97 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 19 Oct 2022 23:55:47 +0530 Subject: [PATCH 23/32] fix: comment --- server/parsers/url.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/parsers/url.go b/server/parsers/url.go index c98ad54..48c2c79 100644 --- a/server/parsers/url.go +++ b/server/parsers/url.go @@ -91,7 +91,7 @@ func GetDomainName(uri string) string { return host } -// GetAppURL to get /app/ url if not configured by user +// GetAppURL to get /app url if not configured by user func GetAppURL(gc *gin.Context) string { envAppURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppURL) if envAppURL == "" || err != nil { From b2e0a3371f97d3677703ab1773e4daa9f3fe38d4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 20 Oct 2022 00:14:06 +0530 Subject: [PATCH 24/32] fix: revert nonce --- .github/workflows/release.yaml | 10 +++++----- app/src/Root.tsx | 5 +++++ server/handlers/authorize.go | 7 ++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e30808b..2fd77a8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,17 +2,17 @@ on: workflow_dispatch: inputs: logLevel: - description: 'Log level' + description: 'Log level' required: true - default: 'warning' + default: 'warning' type: choice options: - info - warning - - debug + - debug tags: description: 'Tags' - required: false + required: false type: boolean release: types: [created] @@ -28,7 +28,7 @@ jobs: node-version: '16' - uses: actions/setup-go@v2 with: - go-version: '^1.17.3' + go-version: '^1.19.1' - name: Install dependencies run: | sudo apt-get install build-essential wget zip gcc-mingw-w64 && \ diff --git a/app/src/Root.tsx b/app/src/Root.tsx index cf1041e..522e4ca 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -39,6 +39,7 @@ export default function Root({ ? searchParams.get('scope')?.toString().split(' ') : ['openid', 'profile', 'email']; const code = searchParams.get('code') || '' + const nonce = searchParams.get('nonce') || '' const urlProps: Record = { state, @@ -64,6 +65,10 @@ export default function Root({ params += `&code=${code}` } + if (nonce !== '') { + params += `&nonce=${nonce}` + } + if (token.refresh_token) { params += `&refresh_token=${token.refresh_token}`; } diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 8dfcbc9..c99cba8 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -85,7 +85,7 @@ func AuthorizeHandler() gin.HandlerFunc { memorystore.Provider.SetState(codeChallenge, code) // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + "&nonce=" + nonce loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { @@ -191,7 +191,7 @@ func AuthorizeHandler() gin.HandlerFunc { // }, // }) - params := "code=" + code + "&state=" + state + params := "code=" + code + "&state=" + state + "&nonce=" + nonce if responseMode == constants.ResponseModeQuery { if strings.Contains(redirectURI, "?") { redirectURI = redirectURI + "&" + params @@ -246,7 +246,7 @@ func AuthorizeHandler() gin.HandlerFunc { } // 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 + "&code=" + code + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code + "&nonce=" + nonce res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, @@ -256,6 +256,7 @@ func AuthorizeHandler() gin.HandlerFunc { "token_type": "Bearer", "expires_in": expiresIn, "code": code, + "nonce": nonce, } if authToken.RefreshToken != nil { From c6019e650b8e5f0c0fbb90d73c55d854338fbba7 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 20 Oct 2022 15:35:26 +0530 Subject: [PATCH 25/32] fix: add manual code generation --- server/handlers/authorize.go | 94 +++++++++++++++++++++----------- server/handlers/openid_config.go | 2 +- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index c99cba8..373e1ba 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -15,7 +15,9 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" ) // AuthorizeHandler is the handler for the /authorize route @@ -69,6 +71,11 @@ func AuthorizeHandler() gin.HandlerFunc { return } + code := uuid.New().String() + if nonce == "" { + nonce = uuid.New().String() + } + log := log.WithFields(log.Fields{ "response_mode": responseMode, "response_type": responseType, @@ -76,12 +83,10 @@ func AuthorizeHandler() gin.HandlerFunc { "code_challenge": codeChallenge, "scope": scope, "redirect_uri": redirectURI, + "nonce": nonce, + "code": code, }) - code := uuid.New().String() - if nonce == "" { - nonce = uuid.New().String() - } memorystore.Provider.SetState(codeChallenge, code) // used for response mode query or fragment @@ -154,22 +159,22 @@ func AuthorizeHandler() gin.HandlerFunc { sessionKey = claims.LoginMethod + ":" + user.ID } - newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) - if err != nil { - log.Debug("CreateSessionToken failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - - if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { - log.Debug("SetState failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - // rollover the session for security go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) if responseType == constants.ResponseTypeCode { + newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) + if err != nil { + log.Debug("CreateSessionToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + + if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { + log.Debug("SetState failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) @@ -218,39 +223,60 @@ func AuthorizeHandler() gin.HandlerFunc { } if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { - // rollover the session for security - authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod) + hostname := parsers.GetHost(gc) + nonce := uuid.New().String() + _, fingerPrintHash, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) if err != nil { - log.Debug("CreateAuthToken failed: ", err) + log.Debug("CreateSessionToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + accessToken, accessTokenExpiresAt, err := token.CreateAccessToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod) + if err != nil { + log.Debug("CreateAccessToken failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash); err != nil { + idToken, _, err := token.CreateIDToken(user, claims.Roles, hostname, nonce, claims.LoginMethod) + if err != nil { + log.Debug("CreateIDToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + // rollover the session for security + // authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod) + // if err != nil { + // log.Debug("CreateAuthToken failed: ", err) + // handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + // return + // } + + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, fingerPrintHash); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token); err != nil { + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, accessToken); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - cookie.SetSession(gc, authToken.FingerPrintHash) + cookie.SetSession(gc, fingerPrintHash) - expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() + expiresIn := accessTokenExpiresAt - time.Now().Unix() if expiresIn <= 0 { expiresIn = 1 } // 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 + "&code=" + code + "&nonce=" + nonce + params := "access_token=" + accessToken + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + idToken + "&code=" + code + "&nonce=" + nonce res := map[string]interface{}{ - "access_token": authToken.AccessToken.Token, - "id_token": authToken.IDToken.Token, + "access_token": accessToken, + "id_token": idToken, "state": state, "scope": scope, "token_type": "Bearer", @@ -259,10 +285,16 @@ func AuthorizeHandler() gin.HandlerFunc { "nonce": nonce, } - if authToken.RefreshToken != nil { - res["refresh_token"] = authToken.RefreshToken.Token - params += "&refresh_token=" + authToken.RefreshToken.Token - memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) + if utils.StringSliceContains(scope, "offline_access") { + refreshToken, _, err := token.CreateRefreshToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod) + if err != nil { + log.Debug("SetUserSession failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } + res["refresh_token"] = refreshToken + params += "&refresh_token=" + refreshToken + memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce, refreshToken) } if responseMode == constants.ResponseModeQuery { diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index 8785437..33ad090 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -24,7 +24,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { "scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"}, "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"}, "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", "nonce"}, + "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"}, }) } } From 274909b7c992c71ec4bd7789ae6c2319f5551a77 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Oct 2022 21:08:08 +0530 Subject: [PATCH 26/32] feat: add nonce variable to create auth token --- server/handlers/authorize.go | 53 +++++++------------------- server/handlers/oauth_callback.go | 4 +- server/handlers/token.go | 4 +- server/handlers/verify_email.go | 5 ++- server/resolvers/login.go | 4 +- server/resolvers/session.go | 4 +- server/resolvers/signup.go | 4 +- server/resolvers/verify_email.go | 4 +- server/resolvers/verify_otp.go | 4 +- server/test/validate_jwt_token_test.go | 3 +- server/token/auth_token.go | 12 +++--- 11 files changed, 46 insertions(+), 55 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 373e1ba..f57c98d 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -5,7 +5,6 @@ import ( "net/http" "strconv" "strings" - "time" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -224,65 +223,39 @@ func AuthorizeHandler() gin.HandlerFunc { if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { hostname := parsers.GetHost(gc) - nonce := uuid.New().String() - _, fingerPrintHash, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) - if err != nil { - log.Debug("CreateSessionToken failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - accessToken, accessTokenExpiresAt, err := token.CreateAccessToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod) - if err != nil { - log.Debug("CreateAccessToken failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - - idToken, _, err := token.CreateIDToken(user, claims.Roles, hostname, nonce, claims.LoginMethod) - if err != nil { - log.Debug("CreateIDToken failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } // rollover the session for security - // authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod) - // if err != nil { - // log.Debug("CreateAuthToken failed: ", err) - // handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - // return - // } + authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce) + if err != nil { + log.Debug("CreateAuthToken failed: ", err) + handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + return + } - if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, fingerPrintHash); err != nil { + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, accessToken); err != nil { + if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.FingerPrintHash); err != nil { log.Debug("SetUserSession failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return } - cookie.SetSession(gc, fingerPrintHash) - - expiresIn := accessTokenExpiresAt - time.Now().Unix() - if expiresIn <= 0 { - expiresIn = 1 - } + cookie.SetSession(gc, authToken.FingerPrintHash) // used of query mode - params := "access_token=" + accessToken + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + idToken + "&code=" + code + "&nonce=" + nonce + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code res := map[string]interface{}{ - "access_token": accessToken, - "id_token": idToken, + "access_token": authToken.AccessToken.Token, + "id_token": authToken.IDToken.Token, "state": state, "scope": scope, "token_type": "Bearer", - "expires_in": expiresIn, + "expires_in": authToken.AccessToken.ExpiresAt, "code": code, - "nonce": nonce, } if utils.StringSliceContains(scope, "offline_access") { diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 02c284c..20e256f 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -13,6 +13,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" @@ -196,7 +197,8 @@ func OAuthCallbackHandler() gin.HandlerFunc { } } - authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) ctx.JSON(500, gin.H{"error": err.Error()}) diff --git a/server/handlers/token.go b/server/handlers/token.go index da72969..5240b30 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -8,6 +8,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -202,7 +203,8 @@ func TokenHandler() gin.HandlerFunc { return } - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) if err != nil { log.Debug("Error creating auth token: ", err) gc.JSON(http.StatusUnauthorized, gin.H{ diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index 10d4fad..c0f0283 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -98,7 +99,9 @@ func VerifyEmailHandler() gin.HandlerFunc { if verificationRequest.Identifier == constants.VerificationTypeMagicLinkLogin { loginMethod = constants.AuthRecipeMethodMagicLinkLogin } - authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod) + + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce) if err != nil { log.Debug("Error creating auth token: ", err) errorRes["error_description"] = err.Error() diff --git a/server/resolvers/login.go b/server/resolvers/login.go index b597ada..7683932 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" @@ -140,7 +141,8 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes }, nil } - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) if err != nil { log.Debug("Failed to create auth token", err) return res, err diff --git a/server/resolvers/session.go b/server/resolvers/session.go index dfa4e3d..7bc0e72 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -70,7 +71,8 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod scope = params.Scope } - authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index d5cd071..3365cd6 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -242,7 +243,8 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR scope = params.Scope } - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 624d08a..37cd7b2 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/authorizerdev/authorizer/server/constants" @@ -84,7 +85,8 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index b792adb..6aeea5f 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -14,6 +14,7 @@ import ( "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" + "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -57,7 +58,8 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/test/validate_jwt_token_test.go b/server/test/validate_jwt_token_test.go index 0d5358a..db46fab 100644 --- a/server/test/validate_jwt_token_test.go +++ b/server/test/validate_jwt_token_test.go @@ -51,7 +51,8 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { gc, err := utils.GinContextFromContext(ctx) assert.NoError(t, err) sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth) + nonce := uuid.New().String() + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 4572b6b..152f5b4 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -10,7 +10,6 @@ import ( "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" - "github.com/google/uuid" "github.com/robertkrimen/otto" "github.com/authorizerdev/authorizer/server/constants" @@ -68,9 +67,8 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string, l } // CreateAuthToken creates a new auth token when userlogs in -func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod string) (*Token, error) { +func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string) (*Token, error) { hostname := parsers.GetHost(gc) - nonce := uuid.New().String() _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod) if err != nil { return nil, err @@ -317,6 +315,8 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD // CreateIDToken util to create JWT token, based on // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT +// For response_type (code) / authorization_code grant nonce should be empty +// for implicit flow it should be present to verify with actual state func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMethod string) (string, int64, error) { expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime) if err != nil { @@ -344,9 +344,9 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMetho return "", 0, err } customClaims := jwt.MapClaims{ - "iss": hostname, - "aud": clientID, - "nonce": nonce, + "iss": hostname, + "aud": clientID, + // "nonce": nonce, "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), From 49556b1709862fd13f48e52584bbf917412155a5 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 12 Nov 2022 23:54:37 +0530 Subject: [PATCH 27/32] fix: openid flow --- app/.prettierrc.json | 2 +- app/package-lock.json | 30 +- app/package.json | 4 +- app/src/Root.tsx | 11 +- dashboard/.prettierrc.json | 2 +- server/go.mod | 4 +- server/go.sum | 40 -- server/graph/generated/generated.go | 726 +++++++++++++++------------- server/graph/model/models_gen.go | 5 +- server/graph/schema.graphqls | 708 ++++++++++++++------------- server/handlers/authorize.go | 41 +- server/handlers/token.go | 110 +++-- server/resolvers/login.go | 31 ++ server/resolvers/signup.go | 14 + server/resolvers/update_user.go | 1 - server/token/auth_token.go | 101 +++- server/utils/gin_context.go | 2 - 17 files changed, 1004 insertions(+), 828 deletions(-) diff --git a/app/.prettierrc.json b/app/.prettierrc.json index c9de62a..8eb506e 100644 --- a/app/.prettierrc.json +++ b/app/.prettierrc.json @@ -2,5 +2,5 @@ "tabWidth": 2, "singleQuote": true, "trailingComma": "all", - "useTabs": false + "useTabs": true } diff --git a/app/package-lock.json b/app/package-lock.json index 657df4a..42e0140 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": "^1.1.2", + "@authorizerdev/authorizer-react": "^1.1.3-beta.1", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -27,9 +27,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz", - "integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==", + "version": "1.1.2-beta.1", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz", + "integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==", "dependencies": { "cross-fetch": "^3.1.5" }, @@ -38,11 +38,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz", - "integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==", + "version": "1.1.3-beta.1", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz", + "integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==", "dependencies": { - "@authorizerdev/authorizer-js": "^1.1.0", + "@authorizerdev/authorizer-js": "^1.1.2-beta.1", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" @@ -876,19 +876,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz", - "integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==", + "version": "1.1.2-beta.1", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz", + "integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==", "requires": { "cross-fetch": "^3.1.5" } }, "@authorizerdev/authorizer-react": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz", - "integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==", + "version": "1.1.3-beta.1", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz", + "integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==", "requires": { - "@authorizerdev/authorizer-js": "^1.1.0", + "@authorizerdev/authorizer-js": "^1.1.2-beta.1", "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 07f4063..14c520d 100644 --- a/app/package.json +++ b/app/package.json @@ -6,13 +6,13 @@ "scripts": { "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js", "start": "NODE_ENV=development node ./esbuild.config.js", - "format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'" + "format": "prettier --write 'src/**/*.(ts|tsx|js|jsx)'" }, "keywords": [], "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.2", + "@authorizerdev/authorizer-react": "^1.1.3-beta.1", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/src/Root.tsx b/app/src/Root.tsx index 522e4ca..abffef6 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -38,8 +38,8 @@ export default function Root({ const scope = searchParams.get('scope') ? searchParams.get('scope')?.toString().split(' ') : ['openid', 'profile', 'email']; - const code = searchParams.get('code') || '' - const nonce = searchParams.get('nonce') || '' + const code = searchParams.get('code') || ''; + const nonce = searchParams.get('nonce') || ''; const urlProps: Record = { state, @@ -57,16 +57,17 @@ export default function Root({ urlProps.redirect_uri = urlProps.redirectURL; useEffect(() => { + console.log(config); if (token) { let redirectURL = config.redirectURL || '/app'; let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`; if (code !== '') { - params += `&code=${code}` + params += `&code=${code}`; } if (nonce !== '') { - params += `&nonce=${nonce}` + params += `&nonce=${nonce}`; } if (token.refresh_token) { @@ -86,7 +87,7 @@ export default function Root({ } } return () => {}; - }, [token]); + }, [token, config]); if (loading) { return

Loading...

; diff --git a/dashboard/.prettierrc.json b/dashboard/.prettierrc.json index c9de62a..8eb506e 100644 --- a/dashboard/.prettierrc.json +++ b/dashboard/.prettierrc.json @@ -2,5 +2,5 @@ "tabWidth": 2, "singleQuote": true, "trailingComma": "all", - "useTabs": false + "useTabs": true } diff --git a/server/go.mod b/server/go.mod index ca7f949..fbcf5ab 100644 --- a/server/go.mod +++ b/server/go.mod @@ -7,9 +7,8 @@ require ( github.com/arangodb/go-driver v1.2.1 github.com/aws/aws-sdk-go v1.44.109 github.com/coreos/go-oidc/v3 v3.1.0 - github.com/denisenkom/go-mssqldb v0.11.0 // indirect github.com/gin-gonic/gin v1.8.1 - github.com/glebarez/sqlite v1.5.0 // indirect + github.com/glebarez/sqlite v1.5.0 github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/go-redis/redis/v8 v8.11.0 github.com/goccy/go-json v0.9.11 // indirect @@ -19,7 +18,6 @@ require ( github.com/google/uuid v1.3.0 github.com/guregu/dynamo v1.16.0 github.com/joho/godotenv v1.3.0 - github.com/mitchellh/gox v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f diff --git a/server/go.sum b/server/go.sum index 3a9f6d3..f1a0545 100644 --- a/server/go.sum +++ b/server/go.sum @@ -85,8 +85,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/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= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= @@ -141,7 +139,6 @@ github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -213,8 +210,6 @@ github.com/guregu/dynamo v1.16.0 h1:gmI8oi1VHwYQtq7+RPBeOiSssVLgxH/Az2t+NtDtL2c= github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -232,8 +227,6 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= -github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= @@ -252,8 +245,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= @@ -262,28 +253,20 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= -github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= -github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI= -github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -332,16 +315,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= -github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= -github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= -github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -457,7 +433,6 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -472,8 +447,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -814,26 +787,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU= -gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k= gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= -gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4= -gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY= -gorm.io/driver/sqlserver v1.2.1 h1:KhGOjvPX7JZ5hPyQICTJfMuTz88zgJ2lk9bWiHVNHd8= -gorm.io/driver/sqlserver v1.2.1/go.mod h1:nixq0OB3iLXZDiPv6JSOjWuPgpyaRpOIIevYtA4Ulb4= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= -gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM= -gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index e3286c2..c841b05 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -1971,501 +1971,513 @@ scalar Map scalar Any type Pagination { - limit: Int64! - page: Int64! - offset: Int64! - total: Int64! + limit: Int64! + page: Int64! + offset: Int64! + total: Int64! } type Meta { - version: String! - client_id: String! - is_google_login_enabled: Boolean! - is_facebook_login_enabled: Boolean! - is_github_login_enabled: Boolean! - is_linkedin_login_enabled: Boolean! - is_apple_login_enabled: Boolean! - is_twitter_login_enabled: Boolean! - is_email_verification_enabled: Boolean! - is_basic_authentication_enabled: Boolean! - is_magic_link_login_enabled: Boolean! - is_sign_up_enabled: Boolean! - is_strong_password_enabled: Boolean! - is_multi_factor_auth_enabled: Boolean! + version: String! + client_id: String! + is_google_login_enabled: Boolean! + is_facebook_login_enabled: Boolean! + is_github_login_enabled: Boolean! + is_linkedin_login_enabled: Boolean! + is_apple_login_enabled: Boolean! + is_twitter_login_enabled: Boolean! + is_email_verification_enabled: Boolean! + is_basic_authentication_enabled: Boolean! + is_magic_link_login_enabled: Boolean! + is_sign_up_enabled: Boolean! + is_strong_password_enabled: Boolean! + is_multi_factor_auth_enabled: Boolean! } type User { - id: ID! - email: String! - email_verified: Boolean! - signup_methods: String! - given_name: String - family_name: String - middle_name: String - nickname: String - # defaults to email - preferred_username: String - gender: String - birthdate: String - phone_number: String - phone_number_verified: Boolean - picture: String - roles: [String!]! - created_at: Int64 - updated_at: Int64 - revoked_timestamp: Int64 - is_multi_factor_auth_enabled: Boolean + id: ID! + email: String! + email_verified: Boolean! + signup_methods: String! + given_name: String + family_name: String + middle_name: String + nickname: String + # defaults to email + preferred_username: String + gender: String + birthdate: String + phone_number: String + phone_number_verified: Boolean + picture: String + roles: [String!]! + created_at: Int64 + updated_at: Int64 + revoked_timestamp: Int64 + is_multi_factor_auth_enabled: Boolean } type Users { - pagination: Pagination! - users: [User!]! + pagination: Pagination! + users: [User!]! } type VerificationRequest { - id: ID! - identifier: String - token: String - email: String - expires: Int64 - created_at: Int64 - updated_at: Int64 - nonce: String - redirect_uri: String + id: ID! + identifier: String + token: String + email: String + expires: Int64 + created_at: Int64 + updated_at: Int64 + nonce: String + redirect_uri: String } type VerificationRequests { - pagination: Pagination! - verification_requests: [VerificationRequest!]! + pagination: Pagination! + verification_requests: [VerificationRequest!]! } type Error { - message: String! - reason: String! + message: String! + reason: String! } type AuthResponse { - message: String! - should_show_otp_screen: Boolean - access_token: String - id_token: String - refresh_token: String - expires_in: Int64 - user: User + message: String! + should_show_otp_screen: Boolean + access_token: String + id_token: String + refresh_token: String + expires_in: Int64 + user: User } type Response { - message: String! + message: String! } type Env { - ACCESS_TOKEN_EXPIRY_TIME: String - ADMIN_SECRET: String - DATABASE_NAME: String - DATABASE_URL: String - DATABASE_TYPE: String - DATABASE_USERNAME: String - DATABASE_PASSWORD: String - DATABASE_HOST: String - DATABASE_PORT: String - CLIENT_ID: String! - CLIENT_SECRET: String! - CUSTOM_ACCESS_TOKEN_SCRIPT: String - SMTP_HOST: String - SMTP_PORT: String - SMTP_USERNAME: String - SMTP_PASSWORD: String - SMTP_LOCAL_NAME: String - SENDER_EMAIL: String - JWT_TYPE: String - JWT_SECRET: String - JWT_PRIVATE_KEY: String - JWT_PUBLIC_KEY: String - ALLOWED_ORIGINS: [String!] - APP_URL: String - REDIS_URL: String - RESET_PASSWORD_URL: String - DISABLE_EMAIL_VERIFICATION: Boolean! - DISABLE_BASIC_AUTHENTICATION: Boolean! - DISABLE_MAGIC_LINK_LOGIN: Boolean! - DISABLE_LOGIN_PAGE: Boolean! - DISABLE_SIGN_UP: Boolean! - DISABLE_REDIS_FOR_ENV: Boolean! - DISABLE_STRONG_PASSWORD: Boolean! - DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean! - ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean! - ROLES: [String!] - PROTECTED_ROLES: [String!] - DEFAULT_ROLES: [String!] - JWT_ROLE_CLAIM: String - GOOGLE_CLIENT_ID: String - GOOGLE_CLIENT_SECRET: String - GITHUB_CLIENT_ID: String - GITHUB_CLIENT_SECRET: String - FACEBOOK_CLIENT_ID: String - FACEBOOK_CLIENT_SECRET: String - LINKEDIN_CLIENT_ID: String - LINKEDIN_CLIENT_SECRET: String - APPLE_CLIENT_ID: String - APPLE_CLIENT_SECRET: String - TWITTER_CLIENT_ID: String - TWITTER_CLIENT_SECRET: String - ORGANIZATION_NAME: String - ORGANIZATION_LOGO: String - APP_COOKIE_SECURE: Boolean! - ADMIN_COOKIE_SECURE: Boolean! + ACCESS_TOKEN_EXPIRY_TIME: String + ADMIN_SECRET: String + DATABASE_NAME: String + DATABASE_URL: String + DATABASE_TYPE: String + DATABASE_USERNAME: String + DATABASE_PASSWORD: String + DATABASE_HOST: String + DATABASE_PORT: String + CLIENT_ID: String! + CLIENT_SECRET: String! + CUSTOM_ACCESS_TOKEN_SCRIPT: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SMTP_LOCAL_NAME: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + JWT_PRIVATE_KEY: String + JWT_PUBLIC_KEY: String + ALLOWED_ORIGINS: [String!] + APP_URL: String + REDIS_URL: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean! + DISABLE_BASIC_AUTHENTICATION: Boolean! + DISABLE_MAGIC_LINK_LOGIN: Boolean! + DISABLE_LOGIN_PAGE: Boolean! + DISABLE_SIGN_UP: Boolean! + DISABLE_REDIS_FOR_ENV: Boolean! + DISABLE_STRONG_PASSWORD: Boolean! + DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean! + ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean! + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + LINKEDIN_CLIENT_ID: String + LINKEDIN_CLIENT_SECRET: String + APPLE_CLIENT_ID: String + APPLE_CLIENT_SECRET: String + TWITTER_CLIENT_ID: String + TWITTER_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String + APP_COOKIE_SECURE: Boolean! + ADMIN_COOKIE_SECURE: Boolean! } type ValidateJWTTokenResponse { - is_valid: Boolean! - claims: Map + is_valid: Boolean! + claims: Map } type GenerateJWTKeysResponse { - secret: String - public_key: String - private_key: String + secret: String + public_key: String + private_key: String } type Webhook { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map - created_at: Int64 - updated_at: Int64 + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map + created_at: Int64 + updated_at: Int64 } type Webhooks { - pagination: Pagination! - webhooks: [Webhook!]! + pagination: Pagination! + webhooks: [Webhook!]! } type WebhookLog { - id: ID! - http_status: Int64 - response: String - request: String - webhook_id: ID - created_at: Int64 - updated_at: Int64 + id: ID! + http_status: Int64 + response: String + request: String + webhook_id: ID + created_at: Int64 + updated_at: Int64 } type TestEndpointResponse { - http_status: Int64 - response: String + http_status: Int64 + response: String } type WebhookLogs { - pagination: Pagination! - webhook_logs: [WebhookLog!]! + pagination: Pagination! + webhook_logs: [WebhookLog!]! } type EmailTemplate { - id: ID! - event_name: String! - template: String! - design: String! - subject: String! - created_at: Int64 - updated_at: Int64 + id: ID! + event_name: String! + template: String! + design: String! + subject: String! + created_at: Int64 + updated_at: Int64 } type EmailTemplates { - pagination: Pagination! - email_templates: [EmailTemplate!]! + pagination: Pagination! + email_templates: [EmailTemplate!]! } input UpdateEnvInput { - ACCESS_TOKEN_EXPIRY_TIME: String - ADMIN_SECRET: String - CUSTOM_ACCESS_TOKEN_SCRIPT: String - OLD_ADMIN_SECRET: String - SMTP_HOST: String - SMTP_PORT: String - SMTP_USERNAME: String - SMTP_PASSWORD: String - SMTP_LOCAL_NAME: String - SENDER_EMAIL: String - JWT_TYPE: String - JWT_SECRET: String - JWT_PRIVATE_KEY: String - JWT_PUBLIC_KEY: String - ALLOWED_ORIGINS: [String!] - APP_URL: String - RESET_PASSWORD_URL: String - APP_COOKIE_SECURE: Boolean - ADMIN_COOKIE_SECURE: Boolean - DISABLE_EMAIL_VERIFICATION: Boolean - DISABLE_BASIC_AUTHENTICATION: Boolean - DISABLE_MAGIC_LINK_LOGIN: Boolean - DISABLE_LOGIN_PAGE: Boolean - DISABLE_SIGN_UP: Boolean - DISABLE_REDIS_FOR_ENV: Boolean - DISABLE_STRONG_PASSWORD: Boolean - DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean - ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean - ROLES: [String!] - PROTECTED_ROLES: [String!] - DEFAULT_ROLES: [String!] - JWT_ROLE_CLAIM: String - GOOGLE_CLIENT_ID: String - GOOGLE_CLIENT_SECRET: String - GITHUB_CLIENT_ID: String - GITHUB_CLIENT_SECRET: String - FACEBOOK_CLIENT_ID: String - FACEBOOK_CLIENT_SECRET: String - LINKEDIN_CLIENT_ID: String - LINKEDIN_CLIENT_SECRET: String - APPLE_CLIENT_ID: String - APPLE_CLIENT_SECRET: String - TWITTER_CLIENT_ID: String - TWITTER_CLIENT_SECRET: String - ORGANIZATION_NAME: String - ORGANIZATION_LOGO: String + ACCESS_TOKEN_EXPIRY_TIME: String + ADMIN_SECRET: String + CUSTOM_ACCESS_TOKEN_SCRIPT: String + OLD_ADMIN_SECRET: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SMTP_LOCAL_NAME: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + JWT_PRIVATE_KEY: String + JWT_PUBLIC_KEY: String + ALLOWED_ORIGINS: [String!] + APP_URL: String + RESET_PASSWORD_URL: String + APP_COOKIE_SECURE: Boolean + ADMIN_COOKIE_SECURE: Boolean + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + DISABLE_SIGN_UP: Boolean + DISABLE_REDIS_FOR_ENV: Boolean + DISABLE_STRONG_PASSWORD: Boolean + DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean + ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + LINKEDIN_CLIENT_ID: String + LINKEDIN_CLIENT_SECRET: String + APPLE_CLIENT_ID: String + APPLE_CLIENT_SECRET: String + TWITTER_CLIENT_ID: String + TWITTER_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String } input AdminLoginInput { - admin_secret: String! + admin_secret: String! } input AdminSignupInput { - admin_secret: String! + admin_secret: String! } input SignUpInput { - email: String! - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - password: String! - confirm_password: String! - roles: [String!] - scope: [String!] - redirect_uri: String - is_multi_factor_auth_enabled: Boolean + email: String! + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + password: String! + confirm_password: String! + roles: [String!] + scope: [String!] + redirect_uri: String + is_multi_factor_auth_enabled: Boolean + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } input LoginInput { - email: String! - password: String! - roles: [String!] - scope: [String!] + email: String! + password: String! + roles: [String!] + scope: [String!] + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } input VerifyEmailInput { - token: String! + token: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } input ResendVerifyEmailInput { - email: String! - identifier: String! + email: String! + identifier: String! } input UpdateProfileInput { - old_password: String - new_password: String - confirm_new_password: String - email: String - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - is_multi_factor_auth_enabled: Boolean + old_password: String + new_password: String + confirm_new_password: String + email: String + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + is_multi_factor_auth_enabled: Boolean } input UpdateUserInput { - id: ID! - email: String - email_verified: Boolean - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - roles: [String] - is_multi_factor_auth_enabled: Boolean + id: ID! + email: String + email_verified: Boolean + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + roles: [String] + is_multi_factor_auth_enabled: Boolean } input ForgotPasswordInput { - email: String! - state: String - redirect_uri: String + email: String! + state: String + redirect_uri: String } input ResetPasswordInput { - token: String! - password: String! - confirm_password: String! + token: String! + password: String! + confirm_password: String! } input DeleteUserInput { - email: String! + email: String! } input MagicLinkLoginInput { - email: String! - roles: [String!] - scope: [String!] - state: String - redirect_uri: String + email: String! + roles: [String!] + scope: [String!] + state: String + redirect_uri: String } input SessionQueryInput { - roles: [String!] - scope: [String!] + roles: [String!] + scope: [String!] } input PaginationInput { - limit: Int64 - page: Int64 + limit: Int64 + page: Int64 } input PaginatedInput { - pagination: PaginationInput + pagination: PaginationInput } input OAuthRevokeInput { - refresh_token: String! + refresh_token: String! } input InviteMemberInput { - emails: [String!]! - redirect_uri: String + emails: [String!]! + redirect_uri: String } input UpdateAccessInput { - user_id: String! + user_id: String! } input ValidateJWTTokenInput { - token_type: String! - token: String! - roles: [String!] + token_type: String! + token: String! + roles: [String!] } input GenerateJWTKeysInput { - type: String! + type: String! } input ListWebhookLogRequest { - pagination: PaginationInput - webhook_id: String + pagination: PaginationInput + webhook_id: String } input AddWebhookRequest { - event_name: String! - endpoint: String! - enabled: Boolean! - headers: Map + event_name: String! + endpoint: String! + enabled: Boolean! + headers: Map } input UpdateWebhookRequest { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map } input WebhookRequest { - id: ID! + id: ID! } input TestEndpointRequest { - endpoint: String! - event_name: String! - headers: Map + endpoint: String! + event_name: String! + headers: Map } input AddEmailTemplateRequest { - event_name: String! - subject: String! - template: String! - design: String! + event_name: String! + subject: String! + template: String! + design: String! } input UpdateEmailTemplateRequest { - id: ID! - event_name: String - template: String - subject: String - design: String + id: ID! + event_name: String + template: String + subject: String + design: String } input DeleteEmailTemplateRequest { - id: ID! + id: ID! } input VerifyOTPRequest { - email: String! - otp: String! + email: String! + otp: String! } input ResendOTPRequest { - email: String! + email: String! } type Mutation { - signup(params: SignUpInput!): AuthResponse! - login(params: LoginInput!): AuthResponse! - magic_link_login(params: MagicLinkLoginInput!): Response! - logout: Response! - update_profile(params: UpdateProfileInput!): Response! - verify_email(params: VerifyEmailInput!): AuthResponse! - resend_verify_email(params: ResendVerifyEmailInput!): Response! - forgot_password(params: ForgotPasswordInput!): Response! - reset_password(params: ResetPasswordInput!): Response! - revoke(params: OAuthRevokeInput!): Response! - verify_otp(params: VerifyOTPRequest!): AuthResponse! - resend_otp(params: ResendOTPRequest!): Response! - # admin only apis - _delete_user(params: DeleteUserInput!): Response! - _update_user(params: UpdateUserInput!): User! - _admin_signup(params: AdminSignupInput!): Response! - _admin_login(params: AdminLoginInput!): Response! - _admin_logout: Response! - _update_env(params: UpdateEnvInput!): Response! - _invite_members(params: InviteMemberInput!): Response! - _revoke_access(param: UpdateAccessInput!): Response! - _enable_access(param: UpdateAccessInput!): Response! - _generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse! - _add_webhook(params: AddWebhookRequest!): Response! - _update_webhook(params: UpdateWebhookRequest!): Response! - _delete_webhook(params: WebhookRequest!): Response! - _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! - _add_email_template(params: AddEmailTemplateRequest!): Response! - _update_email_template(params: UpdateEmailTemplateRequest!): Response! - _delete_email_template(params: DeleteEmailTemplateRequest!): Response! + signup(params: SignUpInput!): AuthResponse! + login(params: LoginInput!): AuthResponse! + magic_link_login(params: MagicLinkLoginInput!): Response! + logout: Response! + update_profile(params: UpdateProfileInput!): Response! + verify_email(params: VerifyEmailInput!): AuthResponse! + resend_verify_email(params: ResendVerifyEmailInput!): Response! + forgot_password(params: ForgotPasswordInput!): Response! + reset_password(params: ResetPasswordInput!): Response! + revoke(params: OAuthRevokeInput!): Response! + verify_otp(params: VerifyOTPRequest!): AuthResponse! + resend_otp(params: ResendOTPRequest!): Response! + # admin only apis + _delete_user(params: DeleteUserInput!): Response! + _update_user(params: UpdateUserInput!): User! + _admin_signup(params: AdminSignupInput!): Response! + _admin_login(params: AdminLoginInput!): Response! + _admin_logout: Response! + _update_env(params: UpdateEnvInput!): Response! + _invite_members(params: InviteMemberInput!): Response! + _revoke_access(param: UpdateAccessInput!): Response! + _enable_access(param: UpdateAccessInput!): Response! + _generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse! + _add_webhook(params: AddWebhookRequest!): Response! + _update_webhook(params: UpdateWebhookRequest!): Response! + _delete_webhook(params: WebhookRequest!): Response! + _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! + _add_email_template(params: AddEmailTemplateRequest!): Response! + _update_email_template(params: UpdateEmailTemplateRequest!): Response! + _delete_email_template(params: DeleteEmailTemplateRequest!): Response! } type Query { - meta: Meta! - session(params: SessionQueryInput): AuthResponse! - profile: User! - validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! - # admin only apis - _users(params: PaginatedInput): Users! - _verification_requests(params: PaginatedInput): VerificationRequests! - _admin_session: Response! - _env: Env! - _webhook(params: WebhookRequest!): Webhook! - _webhooks(params: PaginatedInput): Webhooks! - _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! - _email_templates(params: PaginatedInput): EmailTemplates! + meta: Meta! + session(params: SessionQueryInput): AuthResponse! + profile: User! + validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! + # admin only apis + _users(params: PaginatedInput): Users! + _verification_requests(params: PaginatedInput): VerificationRequests! + _admin_session: Response! + _env: Env! + _webhook(params: WebhookRequest!): Webhook! + _webhooks(params: PaginatedInput): Webhooks! + _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! + _email_templates(params: PaginatedInput): EmailTemplates! } `, BuiltIn: false}, } @@ -14455,7 +14467,7 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in asMap[k] = v } - fieldsInOrder := [...]string{"email", "password", "roles", "scope"} + fieldsInOrder := [...]string{"email", "password", "roles", "scope", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -14494,6 +14506,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in 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 + } } } @@ -14803,7 +14823,7 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i asMap[k] = v } - fieldsInOrder := [...]string{"email", "given_name", "family_name", "middle_name", "nickname", "gender", "birthdate", "phone_number", "picture", "password", "confirm_password", "roles", "scope", "redirect_uri", "is_multi_factor_auth_enabled"} + fieldsInOrder := [...]string{"email", "given_name", "family_name", "middle_name", "nickname", "gender", "birthdate", "phone_number", "picture", "password", "confirm_password", "roles", "scope", "redirect_uri", "is_multi_factor_auth_enabled", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -14930,6 +14950,14 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i 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 + } } } @@ -15815,7 +15843,7 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"token"} + fieldsInOrder := [...]string{"token", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -15830,6 +15858,14 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, 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 + } } } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index b4e33fb..1fe5db6 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -151,6 +151,7 @@ type LoginInput struct { Password string `json:"password"` Roles []string `json:"roles"` Scope []string `json:"scope"` + State *string `json:"state"` } type MagicLinkLoginInput struct { @@ -238,6 +239,7 @@ type SignUpInput struct { Scope []string `json:"scope"` RedirectURI *string `json:"redirect_uri"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` + State *string `json:"state"` } type TestEndpointRequest struct { @@ -408,7 +410,8 @@ type VerificationRequests struct { } type VerifyEmailInput struct { - Token string `json:"token"` + Token string `json:"token"` + State *string `json:"state"` } type VerifyOTPRequest struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index b73df14..135c7e8 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -6,499 +6,523 @@ scalar Map scalar Any type Pagination { - limit: Int64! - page: Int64! - offset: Int64! - total: Int64! + limit: Int64! + page: Int64! + offset: Int64! + total: Int64! } type Meta { - version: String! - client_id: String! - is_google_login_enabled: Boolean! - is_facebook_login_enabled: Boolean! - is_github_login_enabled: Boolean! - is_linkedin_login_enabled: Boolean! - is_apple_login_enabled: Boolean! - is_twitter_login_enabled: Boolean! - is_email_verification_enabled: Boolean! - is_basic_authentication_enabled: Boolean! - is_magic_link_login_enabled: Boolean! - is_sign_up_enabled: Boolean! - is_strong_password_enabled: Boolean! - is_multi_factor_auth_enabled: Boolean! + version: String! + client_id: String! + is_google_login_enabled: Boolean! + is_facebook_login_enabled: Boolean! + is_github_login_enabled: Boolean! + is_linkedin_login_enabled: Boolean! + is_apple_login_enabled: Boolean! + is_twitter_login_enabled: Boolean! + is_email_verification_enabled: Boolean! + is_basic_authentication_enabled: Boolean! + is_magic_link_login_enabled: Boolean! + is_sign_up_enabled: Boolean! + is_strong_password_enabled: Boolean! + is_multi_factor_auth_enabled: Boolean! } type User { - id: ID! - email: String! - email_verified: Boolean! - signup_methods: String! - given_name: String - family_name: String - middle_name: String - nickname: String - # defaults to email - preferred_username: String - gender: String - birthdate: String - phone_number: String - phone_number_verified: Boolean - picture: String - roles: [String!]! - created_at: Int64 - updated_at: Int64 - revoked_timestamp: Int64 - is_multi_factor_auth_enabled: Boolean + id: ID! + email: String! + email_verified: Boolean! + signup_methods: String! + given_name: String + family_name: String + middle_name: String + nickname: String + # defaults to email + preferred_username: String + gender: String + birthdate: String + phone_number: String + phone_number_verified: Boolean + picture: String + roles: [String!]! + created_at: Int64 + updated_at: Int64 + revoked_timestamp: Int64 + is_multi_factor_auth_enabled: Boolean } type Users { - pagination: Pagination! - users: [User!]! + pagination: Pagination! + users: [User!]! } type VerificationRequest { - id: ID! - identifier: String - token: String - email: String - expires: Int64 - created_at: Int64 - updated_at: Int64 - nonce: String - redirect_uri: String + id: ID! + identifier: String + token: String + email: String + expires: Int64 + created_at: Int64 + updated_at: Int64 + nonce: String + redirect_uri: String } type VerificationRequests { - pagination: Pagination! - verification_requests: [VerificationRequest!]! + pagination: Pagination! + verification_requests: [VerificationRequest!]! } type Error { - message: String! - reason: String! + message: String! + reason: String! } type AuthResponse { - message: String! - should_show_otp_screen: Boolean - access_token: String - id_token: String - refresh_token: String - expires_in: Int64 - user: User + message: String! + should_show_otp_screen: Boolean + access_token: String + id_token: String + refresh_token: String + expires_in: Int64 + user: User } type Response { - message: String! + message: String! } type Env { - ACCESS_TOKEN_EXPIRY_TIME: String - ADMIN_SECRET: String - DATABASE_NAME: String - DATABASE_URL: String - DATABASE_TYPE: String - DATABASE_USERNAME: String - DATABASE_PASSWORD: String - DATABASE_HOST: String - DATABASE_PORT: String - CLIENT_ID: String! - CLIENT_SECRET: String! - CUSTOM_ACCESS_TOKEN_SCRIPT: String - SMTP_HOST: String - SMTP_PORT: String - SMTP_USERNAME: String - SMTP_PASSWORD: String - SMTP_LOCAL_NAME: String - SENDER_EMAIL: String - JWT_TYPE: String - JWT_SECRET: String - JWT_PRIVATE_KEY: String - JWT_PUBLIC_KEY: String - ALLOWED_ORIGINS: [String!] - APP_URL: String - REDIS_URL: String - RESET_PASSWORD_URL: String - DISABLE_EMAIL_VERIFICATION: Boolean! - DISABLE_BASIC_AUTHENTICATION: Boolean! - DISABLE_MAGIC_LINK_LOGIN: Boolean! - DISABLE_LOGIN_PAGE: Boolean! - DISABLE_SIGN_UP: Boolean! - DISABLE_REDIS_FOR_ENV: Boolean! - DISABLE_STRONG_PASSWORD: Boolean! - DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean! - ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean! - ROLES: [String!] - PROTECTED_ROLES: [String!] - DEFAULT_ROLES: [String!] - JWT_ROLE_CLAIM: String - GOOGLE_CLIENT_ID: String - GOOGLE_CLIENT_SECRET: String - GITHUB_CLIENT_ID: String - GITHUB_CLIENT_SECRET: String - FACEBOOK_CLIENT_ID: String - FACEBOOK_CLIENT_SECRET: String - LINKEDIN_CLIENT_ID: String - LINKEDIN_CLIENT_SECRET: String - APPLE_CLIENT_ID: String - APPLE_CLIENT_SECRET: String - TWITTER_CLIENT_ID: String - TWITTER_CLIENT_SECRET: String - ORGANIZATION_NAME: String - ORGANIZATION_LOGO: String - APP_COOKIE_SECURE: Boolean! - ADMIN_COOKIE_SECURE: Boolean! + ACCESS_TOKEN_EXPIRY_TIME: String + ADMIN_SECRET: String + DATABASE_NAME: String + DATABASE_URL: String + DATABASE_TYPE: String + DATABASE_USERNAME: String + DATABASE_PASSWORD: String + DATABASE_HOST: String + DATABASE_PORT: String + CLIENT_ID: String! + CLIENT_SECRET: String! + CUSTOM_ACCESS_TOKEN_SCRIPT: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SMTP_LOCAL_NAME: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + JWT_PRIVATE_KEY: String + JWT_PUBLIC_KEY: String + ALLOWED_ORIGINS: [String!] + APP_URL: String + REDIS_URL: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean! + DISABLE_BASIC_AUTHENTICATION: Boolean! + DISABLE_MAGIC_LINK_LOGIN: Boolean! + DISABLE_LOGIN_PAGE: Boolean! + DISABLE_SIGN_UP: Boolean! + DISABLE_REDIS_FOR_ENV: Boolean! + DISABLE_STRONG_PASSWORD: Boolean! + DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean! + ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean! + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + LINKEDIN_CLIENT_ID: String + LINKEDIN_CLIENT_SECRET: String + APPLE_CLIENT_ID: String + APPLE_CLIENT_SECRET: String + TWITTER_CLIENT_ID: String + TWITTER_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String + APP_COOKIE_SECURE: Boolean! + ADMIN_COOKIE_SECURE: Boolean! } type ValidateJWTTokenResponse { - is_valid: Boolean! - claims: Map + is_valid: Boolean! + claims: Map } type GenerateJWTKeysResponse { - secret: String - public_key: String - private_key: String + secret: String + public_key: String + private_key: String } type Webhook { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map - created_at: Int64 - updated_at: Int64 + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map + created_at: Int64 + updated_at: Int64 } type Webhooks { - pagination: Pagination! - webhooks: [Webhook!]! + pagination: Pagination! + webhooks: [Webhook!]! } type WebhookLog { - id: ID! - http_status: Int64 - response: String - request: String - webhook_id: ID - created_at: Int64 - updated_at: Int64 + id: ID! + http_status: Int64 + response: String + request: String + webhook_id: ID + created_at: Int64 + updated_at: Int64 } type TestEndpointResponse { - http_status: Int64 - response: String + http_status: Int64 + response: String } type WebhookLogs { - pagination: Pagination! - webhook_logs: [WebhookLog!]! + pagination: Pagination! + webhook_logs: [WebhookLog!]! } type EmailTemplate { - id: ID! - event_name: String! - template: String! - design: String! - subject: String! - created_at: Int64 - updated_at: Int64 + id: ID! + event_name: String! + template: String! + design: String! + subject: String! + created_at: Int64 + updated_at: Int64 } type EmailTemplates { - pagination: Pagination! - email_templates: [EmailTemplate!]! + pagination: Pagination! + email_templates: [EmailTemplate!]! } input UpdateEnvInput { - ACCESS_TOKEN_EXPIRY_TIME: String - ADMIN_SECRET: String - CUSTOM_ACCESS_TOKEN_SCRIPT: String - OLD_ADMIN_SECRET: String - SMTP_HOST: String - SMTP_PORT: String - SMTP_USERNAME: String - SMTP_PASSWORD: String - SMTP_LOCAL_NAME: String - SENDER_EMAIL: String - JWT_TYPE: String - JWT_SECRET: String - JWT_PRIVATE_KEY: String - JWT_PUBLIC_KEY: String - ALLOWED_ORIGINS: [String!] - APP_URL: String - RESET_PASSWORD_URL: String - APP_COOKIE_SECURE: Boolean - ADMIN_COOKIE_SECURE: Boolean - DISABLE_EMAIL_VERIFICATION: Boolean - DISABLE_BASIC_AUTHENTICATION: Boolean - DISABLE_MAGIC_LINK_LOGIN: Boolean - DISABLE_LOGIN_PAGE: Boolean - DISABLE_SIGN_UP: Boolean - DISABLE_REDIS_FOR_ENV: Boolean - DISABLE_STRONG_PASSWORD: Boolean - DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean - ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean - ROLES: [String!] - PROTECTED_ROLES: [String!] - DEFAULT_ROLES: [String!] - JWT_ROLE_CLAIM: String - GOOGLE_CLIENT_ID: String - GOOGLE_CLIENT_SECRET: String - GITHUB_CLIENT_ID: String - GITHUB_CLIENT_SECRET: String - FACEBOOK_CLIENT_ID: String - FACEBOOK_CLIENT_SECRET: String - LINKEDIN_CLIENT_ID: String - LINKEDIN_CLIENT_SECRET: String - APPLE_CLIENT_ID: String - APPLE_CLIENT_SECRET: String - TWITTER_CLIENT_ID: String - TWITTER_CLIENT_SECRET: String - ORGANIZATION_NAME: String - ORGANIZATION_LOGO: String + ACCESS_TOKEN_EXPIRY_TIME: String + ADMIN_SECRET: String + CUSTOM_ACCESS_TOKEN_SCRIPT: String + OLD_ADMIN_SECRET: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SMTP_LOCAL_NAME: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + JWT_PRIVATE_KEY: String + JWT_PUBLIC_KEY: String + ALLOWED_ORIGINS: [String!] + APP_URL: String + RESET_PASSWORD_URL: String + APP_COOKIE_SECURE: Boolean + ADMIN_COOKIE_SECURE: Boolean + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + DISABLE_SIGN_UP: Boolean + DISABLE_REDIS_FOR_ENV: Boolean + DISABLE_STRONG_PASSWORD: Boolean + DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean + ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + LINKEDIN_CLIENT_ID: String + LINKEDIN_CLIENT_SECRET: String + APPLE_CLIENT_ID: String + APPLE_CLIENT_SECRET: String + TWITTER_CLIENT_ID: String + TWITTER_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String } input AdminLoginInput { - admin_secret: String! + admin_secret: String! } input AdminSignupInput { - admin_secret: String! + admin_secret: String! } input SignUpInput { - email: String! - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - password: String! - confirm_password: String! - roles: [String!] - scope: [String!] - redirect_uri: String - is_multi_factor_auth_enabled: Boolean + email: String! + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + password: String! + confirm_password: String! + roles: [String!] + scope: [String!] + redirect_uri: String + is_multi_factor_auth_enabled: Boolean + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } input LoginInput { - email: String! - password: String! - roles: [String!] - scope: [String!] + email: String! + password: String! + roles: [String!] + scope: [String!] + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } input VerifyEmailInput { - token: String! + token: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } input ResendVerifyEmailInput { - email: String! - identifier: String! + email: String! + identifier: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } input UpdateProfileInput { - old_password: String - new_password: String - confirm_new_password: String - email: String - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - is_multi_factor_auth_enabled: Boolean + old_password: String + new_password: String + confirm_new_password: String + email: String + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + is_multi_factor_auth_enabled: Boolean } input UpdateUserInput { - id: ID! - email: String - email_verified: Boolean - given_name: String - family_name: String - middle_name: String - nickname: String - gender: String - birthdate: String - phone_number: String - picture: String - roles: [String] - is_multi_factor_auth_enabled: Boolean + id: ID! + email: String + email_verified: Boolean + given_name: String + family_name: String + middle_name: String + nickname: String + gender: String + birthdate: String + phone_number: String + picture: String + roles: [String] + is_multi_factor_auth_enabled: Boolean } input ForgotPasswordInput { - email: String! - state: String - redirect_uri: String + email: String! + state: String + redirect_uri: String } input ResetPasswordInput { - token: String! - password: String! - confirm_password: String! + token: String! + password: String! + confirm_password: String! } input DeleteUserInput { - email: String! + email: String! } input MagicLinkLoginInput { - email: String! - roles: [String!] - scope: [String!] - state: String - redirect_uri: String + email: String! + roles: [String!] + scope: [String!] + state: String + redirect_uri: String } input SessionQueryInput { - roles: [String!] - scope: [String!] + roles: [String!] + scope: [String!] } input PaginationInput { - limit: Int64 - page: Int64 + limit: Int64 + page: Int64 } input PaginatedInput { - pagination: PaginationInput + pagination: PaginationInput } input OAuthRevokeInput { - refresh_token: String! + refresh_token: String! } input InviteMemberInput { - emails: [String!]! - redirect_uri: String + emails: [String!]! + redirect_uri: String } input UpdateAccessInput { - user_id: String! + user_id: String! } input ValidateJWTTokenInput { - token_type: String! - token: String! - roles: [String!] + token_type: String! + token: String! + roles: [String!] } input GenerateJWTKeysInput { - type: String! + type: String! } input ListWebhookLogRequest { - pagination: PaginationInput - webhook_id: String + pagination: PaginationInput + webhook_id: String } input AddWebhookRequest { - event_name: String! - endpoint: String! - enabled: Boolean! - headers: Map + event_name: String! + endpoint: String! + enabled: Boolean! + headers: Map } input UpdateWebhookRequest { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map } input WebhookRequest { - id: ID! + id: ID! } input TestEndpointRequest { - endpoint: String! - event_name: String! - headers: Map + endpoint: String! + event_name: String! + headers: Map } input AddEmailTemplateRequest { - event_name: String! - subject: String! - template: String! - design: String! + event_name: String! + subject: String! + template: String! + design: String! } input UpdateEmailTemplateRequest { - id: ID! - event_name: String - template: String - subject: String - design: String + id: ID! + event_name: String + template: String + subject: String + design: String } input DeleteEmailTemplateRequest { - id: ID! + id: ID! } input VerifyOTPRequest { - email: String! - otp: String! + email: String! + otp: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } input ResendOTPRequest { - email: String! + email: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting `c_hash` in id_token + state: String } type Mutation { - signup(params: SignUpInput!): AuthResponse! - login(params: LoginInput!): AuthResponse! - magic_link_login(params: MagicLinkLoginInput!): Response! - logout: Response! - update_profile(params: UpdateProfileInput!): Response! - verify_email(params: VerifyEmailInput!): AuthResponse! - resend_verify_email(params: ResendVerifyEmailInput!): Response! - forgot_password(params: ForgotPasswordInput!): Response! - reset_password(params: ResetPasswordInput!): Response! - revoke(params: OAuthRevokeInput!): Response! - verify_otp(params: VerifyOTPRequest!): AuthResponse! - resend_otp(params: ResendOTPRequest!): Response! - # admin only apis - _delete_user(params: DeleteUserInput!): Response! - _update_user(params: UpdateUserInput!): User! - _admin_signup(params: AdminSignupInput!): Response! - _admin_login(params: AdminLoginInput!): Response! - _admin_logout: Response! - _update_env(params: UpdateEnvInput!): Response! - _invite_members(params: InviteMemberInput!): Response! - _revoke_access(param: UpdateAccessInput!): Response! - _enable_access(param: UpdateAccessInput!): Response! - _generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse! - _add_webhook(params: AddWebhookRequest!): Response! - _update_webhook(params: UpdateWebhookRequest!): Response! - _delete_webhook(params: WebhookRequest!): Response! - _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! - _add_email_template(params: AddEmailTemplateRequest!): Response! - _update_email_template(params: UpdateEmailTemplateRequest!): Response! - _delete_email_template(params: DeleteEmailTemplateRequest!): Response! + signup(params: SignUpInput!): AuthResponse! + login(params: LoginInput!): AuthResponse! + magic_link_login(params: MagicLinkLoginInput!): Response! + logout: Response! + update_profile(params: UpdateProfileInput!): Response! + verify_email(params: VerifyEmailInput!): AuthResponse! + resend_verify_email(params: ResendVerifyEmailInput!): Response! + forgot_password(params: ForgotPasswordInput!): Response! + reset_password(params: ResetPasswordInput!): Response! + revoke(params: OAuthRevokeInput!): Response! + verify_otp(params: VerifyOTPRequest!): AuthResponse! + resend_otp(params: ResendOTPRequest!): Response! + # admin only apis + _delete_user(params: DeleteUserInput!): Response! + _update_user(params: UpdateUserInput!): User! + _admin_signup(params: AdminSignupInput!): Response! + _admin_login(params: AdminLoginInput!): Response! + _admin_logout: Response! + _update_env(params: UpdateEnvInput!): Response! + _invite_members(params: InviteMemberInput!): Response! + _revoke_access(param: UpdateAccessInput!): Response! + _enable_access(param: UpdateAccessInput!): Response! + _generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse! + _add_webhook(params: AddWebhookRequest!): Response! + _update_webhook(params: UpdateWebhookRequest!): Response! + _delete_webhook(params: WebhookRequest!): Response! + _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! + _add_email_template(params: AddEmailTemplateRequest!): Response! + _update_email_template(params: UpdateEmailTemplateRequest!): Response! + _delete_email_template(params: DeleteEmailTemplateRequest!): Response! } type Query { - meta: Meta! - session(params: SessionQueryInput): AuthResponse! - profile: User! - validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! - # admin only apis - _users(params: PaginatedInput): Users! - _verification_requests(params: PaginatedInput): VerificationRequests! - _admin_session: Response! - _env: Env! - _webhook(params: WebhookRequest!): Webhook! - _webhooks(params: PaginatedInput): Webhooks! - _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! - _email_templates(params: PaginatedInput): EmailTemplates! + meta: Meta! + session(params: SessionQueryInput): AuthResponse! + profile: User! + validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! + # admin only apis + _users(params: PaginatedInput): Users! + _verification_requests(params: PaginatedInput): VerificationRequests! + _admin_session: Response! + _env: Env! + _webhook(params: WebhookRequest!): Webhook! + _webhooks(params: PaginatedInput): Webhooks! + _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! + _email_templates(params: PaginatedInput): EmailTemplates! } diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index f57c98d..43360b8 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -86,10 +86,23 @@ func AuthorizeHandler() gin.HandlerFunc { "code": code, }) - memorystore.Provider.SetState(codeChallenge, code) + // memorystore.Provider.SetState(codeChallenge, code) + // TODO add state with timeout // used for response mode query or fragment - loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + "&nonce=" + nonce + loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + if responseType == constants.ResponseTypeCode { + loginState += "&code=" + code + if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil { + log.Debug("Error setting temp code", err) + } + } else { + loginState += "&nonce=" + nonce + if err := memorystore.Provider.SetState(state, nonce); err != nil { + log.Debug("Error setting temp code", err) + } + } + loginURL := "/app?" + loginState if responseMode == constants.ResponseModeFragment { @@ -168,7 +181,15 @@ func AuthorizeHandler() gin.HandlerFunc { return } - if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { + // TODO: add state with timeout + // if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { + // log.Debug("SetState failed: ", err) + // handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) + // return + // } + + // TODO: add state with timeout + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil { log.Debug("SetState failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) return @@ -317,13 +338,15 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, isAuthenticationRequired = true } + if isAuthenticationRequired { + gc.Redirect(http.StatusFound, loginURI) + return + } + switch responseMode { case constants.ResponseModeQuery, constants.ResponseModeFragment: - if isAuthenticationRequired { - gc.Redirect(http.StatusFound, loginURI) - } else { - gc.Redirect(http.StatusFound, redirectURI) - } + + gc.Redirect(http.StatusFound, redirectURI) return case constants.ResponseModeWebMessage: gc.HTML(httpStatusCode, authorizeWebMessageTemplate, gin.H{ @@ -332,6 +355,8 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, }) return case constants.ResponseModeFormPost: + fmt.Println("=> trying tof orm post") + fmt.Printf("=> %+v \n", data["response"]) gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{ "target_origin": redirectURI, "authorization_response": data["response"], diff --git a/server/handlers/token.go b/server/handlers/token.go index b9976a8..9a9ca1f 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -3,6 +3,7 @@ package handlers import ( "crypto/sha256" "encoding/base64" + "fmt" "net/http" "strings" "time" @@ -18,12 +19,26 @@ import ( "github.com/authorizerdev/authorizer/server/token" ) +type RequestBody struct { + CodeVerifier string `form:"code_verifier" json:"code_verifier"` + Code string `form:"code" json:"code"` + ClientID string `form:"client_id" json:"client_id"` + ClientSecret string `form:"client_secret" json:"client_secret"` + GrantType string `form:"grant_type" json:"grant_type"` + RefreshToken string `form:"refresh_token" json:"refresh_token"` + RedirectURI string `form:"redirect_uri" json:"redirect_uri"` +} + // TokenHandler to handle /oauth/token requests // grant type required func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { - var reqBody map[string]string - if err := gc.BindJSON(&reqBody); err != nil { + // body := gc.Request.Body + // x, _ := ioutil.ReadAll(body) + + // fmt.Printf("=> %s \n %s\n", string(x), gc.Request.Header.Get("Content-Type")) + var reqBody RequestBody + if err := gc.Bind(&reqBody); err != nil { log.Debug("Error binding JSON: ", err) gc.JSON(http.StatusBadRequest, gin.H{ "error": "error_binding_json", @@ -32,11 +47,14 @@ func TokenHandler() gin.HandlerFunc { return } - 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"]) + fmt.Printf("=>req body: %+v\n", reqBody) + + codeVerifier := strings.TrimSpace(reqBody.CodeVerifier) + code := strings.TrimSpace(reqBody.Code) + clientID := strings.TrimSpace(reqBody.ClientID) + grantType := strings.TrimSpace(reqBody.GrantType) + refreshToken := strings.TrimSpace(reqBody.RefreshToken) + clientSecret := strings.TrimSpace(reqBody.ClientSecret) if grantType == "" { grantType = "authorization_code" @@ -77,15 +95,6 @@ func TokenHandler() gin.HandlerFunc { sessionKey := "" if isAuthorizationCodeGrant { - if codeVerifier == "" { - log.Debug("Code verifier is empty") - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is required", - }) - return - } - if code == "" { log.Debug("Code is empty") gc.JSON(http.StatusBadRequest, gin.H{ @@ -95,33 +104,55 @@ func TokenHandler() gin.HandlerFunc { 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, err := memorystore.Provider.GetState(encryptedCode) + if codeVerifier == "" && clientSecret == "" { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_dat", + "error_description": "The code verifier or client secret is required", + }) + return + } + // Get state + sessionData, err := memorystore.Provider.GetState(code) if sessionData == "" || err != nil { log.Debug("Session data is empty") gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is invalid", + "error": "invalid_code", + "error_description": "The code is invalid", }) return } - go memorystore.Provider.RemoveState(encryptedCode) - // split session data - // it contains code@sessiontoken - sessionDataSplit := strings.Split(sessionData, "@") + // [0] -> code_challenge + // [1] -> session cookie + sessionDataSplit := strings.Split(sessionData, "@@") + fmt.Println("=> sessionDataSplit:", sessionDataSplit) - if sessionDataSplit[0] != code { - log.Debug("Invalid code verifier. Unable to split session data") - gc.JSON(http.StatusBadRequest, gin.H{ - "error": "invalid_code_verifier", - "error_description": "The code verifier is invalid", - }) - return + go memorystore.Provider.RemoveState(code) + + if codeVerifier != "" { + hash := sha256.New() + hash.Write([]byte(codeVerifier)) + encryptedCode := strings.ReplaceAll(base64.RawURLEncoding.EncodeToString(hash.Sum(nil)), "+", "-") + encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") + encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") + fmt.Println("=> encryptedCode", encryptedCode) + if encryptedCode != sessionDataSplit[0] { + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_code_verifier", + "error_description": "The code verifier is invalid", + }) + return + } + + } else { + if clientHash, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientSecret); clientSecret != clientHash || err != nil { + log.Debug("Client Secret is invalid: ", clientID) + gc.JSON(http.StatusBadRequest, gin.H{ + "error": "invalid_client_secret", + "error_description": "The client secret is invalid", + }) + return + } } // validate session @@ -135,6 +166,8 @@ func TokenHandler() gin.HandlerFunc { return } + fmt.Printf("=>claims: %+v\n", &claims) + userID = claims.Subject roles = claims.Roles scope = claims.Scope @@ -147,6 +180,7 @@ func TokenHandler() gin.HandlerFunc { } go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce) + } else { // validate refresh token if refreshToken == "" { @@ -207,7 +241,11 @@ func TokenHandler() gin.HandlerFunc { return } - nonce := uuid.New().String() + nonce := uuid.New().String() + "@@" + code + + fmt.Println("=> code", code) + fmt.Println("=> nonce", nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) if err != nil { log.Debug("Error creating auth token: ", err) diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 7683932..99b26f2 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -142,6 +142,28 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } nonce := uuid.New().String() + fmt.Println("=> state", refs.StringValue(params.State)) + code := "" + codeChallenge := "" + if params.State != nil { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + codeChallenge = authorizeStateSplit[1] + + fmt.Println("=> code info", authorizeStateSplit) + nonce = nonce + "@@" + code + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(refs.StringValue(params.State)) + } + + } + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) if err != nil { log.Debug("Failed to create auth token", err) @@ -165,6 +187,15 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + // Code challenge could be optional if PKCE flow is not used + + if code != "" { + fmt.Println("=> setting the state here....") + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + log.Debug("SetState failed: ", err) + return res, err + } + } if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 3365cd6..06a477c 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -34,6 +34,16 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } + code := "" + if params.State != nil { + // Get state from store + code, err = memorystore.Provider.GetState(*params.State) + if err != nil { + log.Debug("Invalid Error State:", err) + return res, fmt.Errorf("invalid_state: %s", err.Error()) + } + } + isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) if err != nil { log.Debug("Error getting signup disabled: ", err) @@ -244,6 +254,10 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR } nonce := uuid.New().String() + if code != "" { + nonce = nonce + "@@" + code + } + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) if err != nil { log.Debug("Failed to create auth token: ", err) diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index e2f6618..057b797 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -125,7 +125,6 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod return res, fmt.Errorf("user with this email address already exists") } - // TODO figure out how to do this go memorystore.Provider.DeleteAllUserSessions(user.ID) hostname := parsers.GetHost(gc) diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 0d3114e..1833db3 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -1,6 +1,8 @@ package token import ( + "crypto/sha256" + "encoding/base64" "encoding/json" "fmt" "strings" @@ -46,28 +48,22 @@ type SessionData struct { LoginMethod string `json:"login_method"` } -// CreateSessionToken creates a new session token -func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) { - fingerPrintMap := &SessionData{ - Nonce: nonce, - Roles: roles, - Subject: user.ID, - Scope: scope, - LoginMethod: loginMethod, - 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(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string) (*Token, error) { + + code := "" + nonceSplit := strings.Split(nonce, "@@") + fingerPrint := nonce + fmt.Println("=> nonce split", nonceSplit) + if len(nonceSplit) > 1 { + code = nonceSplit[1] + // use original nonce for session token and access token + nonce = nonceSplit[0] + fingerPrint = nonce + } + + fmt.Println("=> original nonce:", nonce) + hostname := parsers.GetHost(gc) _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod) if err != nil { @@ -78,13 +74,31 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l return nil, err } - idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, loginMethod) + atHash := sha256.New() + atHash.Write([]byte(accessToken)) + atHashBytes := atHash.Sum(nil) + // hashedToken := string(bs) + atHashDigest := atHashBytes[0 : len(atHashBytes)/2] + atHashString := base64.RawURLEncoding.EncodeToString(atHashDigest) + + codeHashString := "" + if code != "" { + fmt.Println("=> atHash", atHashString) + codeHash := sha256.New() + codeHash.Write([]byte(code)) + codeHashBytes := codeHash.Sum(nil) + codeHashDigest := codeHashBytes[0 : len(codeHashBytes)/2] + codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest) + } + + fmt.Println("=> at hash nonce", nonce) + idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod) if err != nil { return nil, err } res := &Token{ - FingerPrint: nonce, + FingerPrint: fingerPrint, FingerPrintHash: fingerPrintHash, AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt}, IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt}, @@ -102,6 +116,27 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l return res, nil } +// CreateSessionToken creates a new session token +func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) { + fingerPrintMap := &SessionData{ + Nonce: nonce, + Roles: roles, + Subject: user.ID, + Scope: scope, + LoginMethod: loginMethod, + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(), + } + fmt.Printf("=> session data %+v\n", fingerPrintMap) + fingerPrintBytes, _ := json.Marshal(fingerPrintMap) + fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes)) + if err != nil { + return nil, "", err + } + + return fingerPrintMap, fingerPrintHash, nil +} + // CreateRefreshToken util to create JWT token func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) { // expires in 1 year @@ -318,7 +353,7 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT // For response_type (code) / authorization_code grant nonce should be empty // for implicit flow it should be present to verify with actual state -func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMethod string) (string, int64, error) { +func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cHash, loginMethod string) (string, int64, error) { expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime) if err != nil { return "", 0, err @@ -344,10 +379,10 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMetho if err != nil { return "", 0, err } + customClaims := jwt.MapClaims{ - "iss": hostname, - "aud": clientID, - // "nonce": nonce, + "iss": hostname, + "aud": clientID, "sub": user.ID, "exp": expiresAt, "iat": time.Now().Unix(), @@ -357,6 +392,20 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMetho claimKey: roles, } + fmt.Println("=> nonce", nonce) + + // split nonce to see if its authorization code grant method + + if cHash != "" { + customClaims["at_hash"] = atHash + customClaims["c_hash"] = cHash + } else { + customClaims["nonce"] = nonce + customClaims["at_hash"] = atHash + } + + fmt.Println("custom_claims", customClaims) + for k, v := range userMap { if k != "roles" { customClaims[k] = v diff --git a/server/utils/gin_context.go b/server/utils/gin_context.go index 7e3ced6..0491cbe 100644 --- a/server/utils/gin_context.go +++ b/server/utils/gin_context.go @@ -7,8 +7,6 @@ import ( "github.com/gin-gonic/gin" ) -// TODO re-name GinContextKey -> GinContext - // GinContext to get gin context from context func GinContextFromContext(ctx context.Context) (*gin.Context, error) { ginContext := ctx.Value("GinContextKey") From c09558043eb5414b9cac1c761a84ef636d6b6469 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 13 Nov 2022 00:16:22 +0530 Subject: [PATCH 28/32] fix(server): spacing --- server/resolvers/login.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 99b26f2..5e59bc6 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -161,7 +161,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } go memorystore.Provider.RemoveState(refs.StringValue(params.State)) } - } authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) From 9320f1cb07c2ee50819a667193426af5bc59f8dd Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 13 Nov 2022 00:40:28 +0530 Subject: [PATCH 29/32] fix(server): add flow comment --- server/handlers/authorize.go | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 43360b8..7bf97b2 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -1,5 +1,36 @@ package handlers +/** +LOGIC TO REMEMBER THE AUTHORIZE FLOW + + +jargons +`at_hash` -> access_token_hash +`c_hash` -> code_hash + + +# ResponseType: Code + with /authorize request + - set state [state, code@@challenge] + - add &code to login redirect url + login resolver has optional param state + -if state found in store, split with @@ + - if len > 1 -> response type is code and has code + challenge + - set `nonce@@code` for createAuthToken request so that `c_hash` can be generated + - do not add `nonce` to id_token in code flow, instead set `c_hash` and `at_hash` + + +# ResponseType: token / id_token + with /authorize request + - set state [state, nonce] + - add &nonce to login redirect url + login resolver has optional param state + - if state found in store, split with @@ + - if len < 1 -> response type is token / id_token and has nonce + - send received nonce for createAuthToken + - set `nonce` and `at_hash` in `id_token` +**/ + import ( "fmt" "net/http" @@ -19,6 +50,15 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) +// 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. + +// Check following docs for understanding request / response params for various types of requests: https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow + +const ( + authorizeWebMessageTemplate = "authorize_web_message.tmpl" + authorizeFormPostTemplate = "authorize_form_post.tmpl" +) + // AuthorizeHandler is the handler for the /authorize route // required params // ?redirect_uri = redirect url @@ -26,14 +66,6 @@ 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. - -const ( - authorizeWebMessageTemplate = "authorize_web_message.tmpl" - authorizeFormPostTemplate = "authorize_form_post.tmpl" -) - func AuthorizeHandler() gin.HandlerFunc { return func(gc *gin.Context) { redirectURI := strings.TrimSpace(gc.Query("redirect_uri")) From 579899c397614cd06a61480c9eca1142c519e4af Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 13 Nov 2022 01:22:21 +0530 Subject: [PATCH 30/32] fix(server): creepy @@ string split logic for auth_token --- server/handlers/authorize.go | 8 ++++---- server/handlers/oauth_callback.go | 5 ++++- server/handlers/token.go | 2 +- server/handlers/verify_email.go | 2 +- server/resolvers/login.go | 5 ++--- server/resolvers/magic_link_login.go | 3 ++- server/resolvers/session.go | 2 +- server/resolvers/signup.go | 2 +- server/resolvers/verify_email.go | 2 +- server/resolvers/verify_otp.go | 2 +- server/test/validate_jwt_token_test.go | 2 +- server/token/auth_token.go | 15 ++------------- 12 files changed, 21 insertions(+), 29 deletions(-) diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 7bf97b2..171e343 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -16,7 +16,7 @@ jargons login resolver has optional param state -if state found in store, split with @@ - if len > 1 -> response type is code and has code + challenge - - set `nonce@@code` for createAuthToken request so that `c_hash` can be generated + - set `nonce, code` for createAuthToken request so that `c_hash` can be generated - do not add `nonce` to id_token in code flow, instead set `c_hash` and `at_hash` @@ -26,8 +26,8 @@ jargons - add &nonce to login redirect url login resolver has optional param state - if state found in store, split with @@ - - if len < 1 -> response type is token / id_token and has nonce - - send received nonce for createAuthToken + - if len < 1 -> response type is token / id_token and value is nonce + - send received nonce for createAuthToken with empty code value - set `nonce` and `at_hash` in `id_token` **/ @@ -277,7 +277,7 @@ func AuthorizeHandler() gin.HandlerFunc { if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { hostname := parsers.GetHost(gc) // rollover the session for security - authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce) + authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "") if err != nil { log.Debug("CreateAuthToken failed: ", err) handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 20e256f..2dfe2c5 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -197,8 +197,11 @@ func OAuthCallbackHandler() gin.HandlerFunc { } } + // TODO + // use stateValue to get code / nonce + // add code / nonce to id_token nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce) + authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, "") if err != nil { log.Debug("Failed to create auth token: ", err) ctx.JSON(500, gin.H{"error": err.Error()}) diff --git a/server/handlers/token.go b/server/handlers/token.go index 9a9ca1f..5e80ada 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -246,7 +246,7 @@ func TokenHandler() gin.HandlerFunc { fmt.Println("=> code", code) fmt.Println("=> nonce", nonce) - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code) if err != nil { log.Debug("Error creating auth token: ", err) gc.JSON(http.StatusUnauthorized, gin.H{ diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index c0f0283..c57d0e2 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -101,7 +101,7 @@ func VerifyEmailHandler() gin.HandlerFunc { } nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce) + authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, "") if err != nil { log.Debug("Error creating auth token: ", err) errorRes["error_description"] = err.Error() diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 5e59bc6..6a724a8 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -155,7 +155,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes codeChallenge = authorizeStateSplit[1] fmt.Println("=> code info", authorizeStateSplit) - nonce = nonce + "@@" + code } else { nonce = authorizeState } @@ -163,7 +162,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } } - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code) if err != nil { log.Debug("Failed to create auth token", err) return res, err @@ -186,8 +185,8 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) + // TODO add to other login options as well // Code challenge could be optional if PKCE flow is not used - if code != "" { fmt.Println("=> setting the state here....") if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index 611c47f..bcc7ce2 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -15,6 +15,7 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/parsers" + "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/validators" @@ -185,7 +186,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu } redirectURLParams := "&roles=" + strings.Join(inputRoles, ",") if params.State != nil { - redirectURLParams = redirectURLParams + "&state=" + *params.State + redirectURLParams = redirectURLParams + "&state=" + refs.StringValue(params.State) } if params.Scope != nil && len(params.Scope) > 0 { redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ") diff --git a/server/resolvers/session.go b/server/resolvers/session.go index 7bc0e72..79ea012 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -72,7 +72,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod } nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod, nonce) + authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod, nonce, "") if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 06a477c..3df520f 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -258,7 +258,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR nonce = nonce + "@@" + code } - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 37cd7b2..7dbf404 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -86,7 +86,7 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index 6aeea5f..fd70a9f 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -59,7 +59,7 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") if err != nil { log.Debug("Failed to create auth token: ", err) return res, err diff --git a/server/test/validate_jwt_token_test.go b/server/test/validate_jwt_token_test.go index e4e3b45..52ce50b 100644 --- a/server/test/validate_jwt_token_test.go +++ b/server/test/validate_jwt_token_test.go @@ -52,7 +52,7 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) { assert.NoError(t, err) sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce) + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, "") memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 1833db3..607f64d 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -49,18 +49,7 @@ type SessionData struct { } // CreateAuthToken creates a new auth token when userlogs in -func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string) (*Token, error) { - - code := "" - nonceSplit := strings.Split(nonce, "@@") - fingerPrint := nonce - fmt.Println("=> nonce split", nonceSplit) - if len(nonceSplit) > 1 { - code = nonceSplit[1] - // use original nonce for session token and access token - nonce = nonceSplit[0] - fingerPrint = nonce - } +func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string, code string) (*Token, error) { fmt.Println("=> original nonce:", nonce) @@ -98,7 +87,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l } res := &Token{ - FingerPrint: fingerPrint, + FingerPrint: nonce, FingerPrintHash: fingerPrintHash, AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt}, IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt}, From 75a547cfe2f55a5a0907bd4166560175a0e81f2a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 15 Nov 2022 21:45:08 +0530 Subject: [PATCH 31/32] fix: other auth recipes for oidc idp + remove logs --- app/src/Root.tsx | 3 +- server/graph/generated/generated.go | 42 +++++++++++++++++++++-- server/graph/model/models_gen.go | 13 ++++--- server/handlers/authorize.go | 53 ++++++++--------------------- server/handlers/oauth_callback.go | 53 +++++++++++++++++++++++------ server/handlers/token.go | 15 -------- server/handlers/verify_email.go | 42 +++++++++++++++++++++-- server/resolvers/login.go | 27 ++++++++------- server/resolvers/signup.go | 41 +++++++++++++++------- server/resolvers/verify_email.go | 34 ++++++++++++++++-- server/resolvers/verify_otp.go | 32 +++++++++++++++-- server/token/auth_token.go | 10 ------ 12 files changed, 248 insertions(+), 117 deletions(-) diff --git a/app/src/Root.tsx b/app/src/Root.tsx index abffef6..61dd2a8 100644 --- a/app/src/Root.tsx +++ b/app/src/Root.tsx @@ -57,7 +57,6 @@ export default function Root({ urlProps.redirect_uri = urlProps.redirectURL; useEffect(() => { - console.log(config); if (token) { let redirectURL = config.redirectURL || '/app'; let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`; @@ -113,7 +112,7 @@ export default function Root({ - + diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index c841b05..fadea64 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -2278,6 +2278,10 @@ input VerifyEmailInput { input ResendVerifyEmailInput { email: String! identifier: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } input UpdateProfileInput { @@ -2425,10 +2429,18 @@ input DeleteEmailTemplateRequest { input VerifyOTPRequest { email: String! otp: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } input ResendOTPRequest { email: String! + # state is used for authorization code grant flow + # it is used to get code for an on-going auth process during login + # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token + state: String } type Mutation { @@ -14679,7 +14691,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"email"} + fieldsInOrder := [...]string{"email", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -14694,6 +14706,14 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context, 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 + } } } @@ -14707,7 +14727,7 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con asMap[k] = v } - fieldsInOrder := [...]string{"email", "identifier"} + fieldsInOrder := [...]string{"email", "identifier", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -14730,6 +14750,14 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con 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 + } } } @@ -15879,7 +15907,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"email", "otp"} + fieldsInOrder := [...]string{"email", "otp", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -15902,6 +15930,14 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, 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 + } } } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 1fe5db6..c1fb6c5 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -200,12 +200,14 @@ type PaginationInput struct { } type ResendOTPRequest struct { - Email string `json:"email"` + Email string `json:"email"` + State *string `json:"state"` } type ResendVerifyEmailInput struct { - Email string `json:"email"` - Identifier string `json:"identifier"` + Email string `json:"email"` + Identifier string `json:"identifier"` + State *string `json:"state"` } type ResetPasswordInput struct { @@ -415,8 +417,9 @@ type VerifyEmailInput struct { } type VerifyOTPRequest struct { - Email string `json:"email"` - Otp string `json:"otp"` + Email string `json:"email"` + Otp string `json:"otp"` + State *string `json:"state"` } type Webhook struct { diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go index 171e343..c3e2e2a 100644 --- a/server/handlers/authorize.go +++ b/server/handlers/authorize.go @@ -45,9 +45,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/memorystore" - "github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/token" - "github.com/authorizerdev/authorizer/server/utils" ) // 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. @@ -108,19 +106,11 @@ func AuthorizeHandler() gin.HandlerFunc { } log := log.WithFields(log.Fields{ - "response_mode": responseMode, - "response_type": responseType, - "state": state, - "code_challenge": codeChallenge, - "scope": scope, - "redirect_uri": redirectURI, - "nonce": nonce, - "code": code, + "response_mode": responseMode, + "response_type": responseType, }) - // memorystore.Provider.SetState(codeChallenge, code) // TODO add state with timeout - // used for response mode query or fragment loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI if responseType == constants.ResponseTypeCode { @@ -141,17 +131,6 @@ func AuthorizeHandler() gin.HandlerFunc { loginURL = "/app#" + loginState } - if state == "" { - handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ - "type": "authorization_response", - "response": map[string]interface{}{ - "error": "state_required", - "error_description": "state is required", - }, - }, http.StatusOK) - return - } - if responseType == constants.ResponseTypeCode && codeChallenge == "" { handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ "type": "authorization_response", @@ -275,7 +254,6 @@ func AuthorizeHandler() gin.HandlerFunc { } if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { - hostname := parsers.GetHost(gc) // rollover the session for security authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "") if err != nil { @@ -299,7 +277,7 @@ func AuthorizeHandler() gin.HandlerFunc { cookie.SetSession(gc, authToken.FingerPrintHash) // used of query mode - params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token res := map[string]interface{}{ "access_token": authToken.AccessToken.Token, @@ -308,19 +286,17 @@ func AuthorizeHandler() gin.HandlerFunc { "scope": scope, "token_type": "Bearer", "expires_in": authToken.AccessToken.ExpiresAt, - "code": code, } - if utils.StringSliceContains(scope, "offline_access") { - refreshToken, _, err := token.CreateRefreshToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod) - if err != nil { - log.Debug("SetUserSession failed: ", err) - handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) - return - } - res["refresh_token"] = refreshToken - params += "&refresh_token=" + refreshToken - memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce, refreshToken) + if nonce != "" { + params += "&nonce=" + nonce + res["nonce"] = nonce + } + + if authToken.RefreshToken != nil { + res["refresh_token"] = authToken.RefreshToken.Token + params += "&refresh_token=" + authToken.RefreshToken.Token + memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } if responseMode == constants.ResponseModeQuery { @@ -349,6 +325,9 @@ func AuthorizeHandler() gin.HandlerFunc { } func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error { + if strings.TrimSpace(state) == "" { + return fmt.Errorf("invalid state. state is required to prevent csrf attack", responseMode) + } if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken { return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode) } @@ -387,8 +366,6 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, }) return case constants.ResponseModeFormPost: - fmt.Println("=> trying tof orm post") - fmt.Printf("=> %+v \n", data["response"]) gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{ "target_origin": redirectURI, "authorization_response": data["response"], diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 2dfe2c5..8bd894c 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -56,20 +56,20 @@ func OAuthCallbackHandler() gin.HandlerFunc { scopes := strings.Split(sessionSplit[3], ",") user := models.User{} - code := ctx.Request.FormValue("code") + oauthCode := ctx.Request.FormValue("code") switch provider { case constants.AuthRecipeMethodGoogle: - user, err = processGoogleUserInfo(code) + user, err = processGoogleUserInfo(oauthCode) case constants.AuthRecipeMethodGithub: - user, err = processGithubUserInfo(code) + user, err = processGithubUserInfo(oauthCode) case constants.AuthRecipeMethodFacebook: - user, err = processFacebookUserInfo(code) + user, err = processFacebookUserInfo(oauthCode) case constants.AuthRecipeMethodLinkedIn: - user, err = processLinkedInUserInfo(code) + user, err = processLinkedInUserInfo(oauthCode) case constants.AuthRecipeMethodApple: - user, err = processAppleUserInfo(code) + user, err = processAppleUserInfo(oauthCode) case constants.AuthRecipeMethodTwitter: - user, err = processTwitterUserInfo(code, sessionState) + user, err = processTwitterUserInfo(oauthCode, sessionState) default: log.Info("Invalid oauth provider") err = fmt.Errorf(`invalid oauth provider`) @@ -200,19 +200,50 @@ func OAuthCallbackHandler() gin.HandlerFunc { // TODO // use stateValue to get code / nonce // add code / nonce to id_token - nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, "") + code := "" + codeChallenge := "" + nonce := "" + if stateValue != "" { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(stateValue) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + codeChallenge = authorizeStateSplit[1] + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(stateValue) + } + } + if nonce == "" { + nonce = uuid.New().String() + } + authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, code) if err != nil { log.Debug("Failed to create auth token: ", err) ctx.JSON(500, gin.H{"error": err.Error()}) } + // Code challenge could be optional if PKCE flow is not used + if code != "" { + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + log.Debug("SetState failed: ", err) + ctx.JSON(500, gin.H{"error": err.Error()}) + } + } + expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { expiresIn = 1 } - params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce + + if code != "" { + params += "&code=" + code + } sessionKey := provider + ":" + user.ID cookie.SetSession(ctx, authToken.FingerPrintHash) @@ -220,7 +251,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) if authToken.RefreshToken != nil { - params = params + `&refresh_token=` + authToken.RefreshToken.Token + params += `&refresh_token=` + authToken.RefreshToken.Token memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token) } diff --git a/server/handlers/token.go b/server/handlers/token.go index 5e80ada..b515dd5 100644 --- a/server/handlers/token.go +++ b/server/handlers/token.go @@ -3,7 +3,6 @@ package handlers import ( "crypto/sha256" "encoding/base64" - "fmt" "net/http" "strings" "time" @@ -33,10 +32,6 @@ type RequestBody struct { // grant type required func TokenHandler() gin.HandlerFunc { return func(gc *gin.Context) { - // body := gc.Request.Body - // x, _ := ioutil.ReadAll(body) - - // fmt.Printf("=> %s \n %s\n", string(x), gc.Request.Header.Get("Content-Type")) var reqBody RequestBody if err := gc.Bind(&reqBody); err != nil { log.Debug("Error binding JSON: ", err) @@ -47,8 +42,6 @@ func TokenHandler() gin.HandlerFunc { return } - fmt.Printf("=>req body: %+v\n", reqBody) - codeVerifier := strings.TrimSpace(reqBody.CodeVerifier) code := strings.TrimSpace(reqBody.Code) clientID := strings.TrimSpace(reqBody.ClientID) @@ -125,7 +118,6 @@ func TokenHandler() gin.HandlerFunc { // [0] -> code_challenge // [1] -> session cookie sessionDataSplit := strings.Split(sessionData, "@@") - fmt.Println("=> sessionDataSplit:", sessionDataSplit) go memorystore.Provider.RemoveState(code) @@ -135,7 +127,6 @@ func TokenHandler() gin.HandlerFunc { encryptedCode := strings.ReplaceAll(base64.RawURLEncoding.EncodeToString(hash.Sum(nil)), "+", "-") encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") - fmt.Println("=> encryptedCode", encryptedCode) if encryptedCode != sessionDataSplit[0] { gc.JSON(http.StatusBadRequest, gin.H{ "error": "invalid_code_verifier", @@ -166,8 +157,6 @@ func TokenHandler() gin.HandlerFunc { return } - fmt.Printf("=>claims: %+v\n", &claims) - userID = claims.Subject roles = claims.Roles scope = claims.Scope @@ -242,10 +231,6 @@ func TokenHandler() gin.HandlerFunc { } nonce := uuid.New().String() + "@@" + code - - fmt.Println("=> code", code) - fmt.Println("=> nonce", nonce) - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code) if err != nil { log.Debug("Error creating auth token: ", err) diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index c57d0e2..cf5ec1a 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -100,8 +100,29 @@ func VerifyEmailHandler() gin.HandlerFunc { loginMethod = constants.AuthRecipeMethodMagicLinkLogin } - nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, "") + code := "" + // Not required as /oauth/token cannot be resumed from other tab + // codeChallenge := "" + nonce := "" + if state != "" { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(state) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + // Not required as /oauth/token cannot be resumed from other tab + // codeChallenge = authorizeStateSplit[1] + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(state) + } + } + if nonce == "" { + nonce = uuid.New().String() + } + authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, code) if err != nil { log.Debug("Error creating auth token: ", err) errorRes["error_description"] = err.Error() @@ -109,12 +130,27 @@ func VerifyEmailHandler() gin.HandlerFunc { return } + // Code challenge could be optional if PKCE flow is not used + // Not required as /oauth/token cannot be resumed from other tab + // if code != "" { + // if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + // log.Debug("Error setting code state ", err) + // errorRes["error_description"] = err.Error() + // c.JSON(500, errorRes) + // return + // } + // } + expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { expiresIn = 1 } - params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce + + if code != "" { + params += "&code=" + code + } sessionKey := loginMethod + ":" + user.ID cookie.SetSession(c, authToken.FingerPrintHash) diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 6a724a8..4bae30a 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -141,10 +141,9 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes }, nil } - nonce := uuid.New().String() - fmt.Println("=> state", refs.StringValue(params.State)) code := "" codeChallenge := "" + nonce := "" if params.State != nil { // Get state from store authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) @@ -153,8 +152,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes if len(authorizeStateSplit) > 1 { code = authorizeStateSplit[0] codeChallenge = authorizeStateSplit[1] - - fmt.Println("=> code info", authorizeStateSplit) } else { nonce = authorizeState } @@ -162,12 +159,25 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } } + if nonce == "" { + nonce = uuid.New().String() + } + authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code) if err != nil { log.Debug("Failed to create auth token", err) return res, err } + // TODO add to other login options as well + // Code challenge could be optional if PKCE flow is not used + if code != "" { + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + log.Debug("SetState failed: ", err) + return res, err + } + } + expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { expiresIn = 1 @@ -185,15 +195,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) - // TODO add to other login options as well - // Code challenge could be optional if PKCE flow is not used - if code != "" { - fmt.Println("=> setting the state here....") - if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { - log.Debug("SetState failed: ", err) - return res, err - } - } if authToken.RefreshToken != nil { res.RefreshToken = &authToken.RefreshToken.Token diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 3df520f..43f2a96 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -34,16 +34,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } - code := "" - if params.State != nil { - // Get state from store - code, err = memorystore.Provider.GetState(*params.State) - if err != nil { - log.Debug("Invalid Error State:", err) - return res, fmt.Errorf("invalid_state: %s", err.Error()) - } - } - isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) if err != nil { log.Debug("Error getting signup disabled: ", err) @@ -253,9 +243,26 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR scope = params.Scope } - nonce := uuid.New().String() - if code != "" { - nonce = nonce + "@@" + code + code := "" + codeChallenge := "" + nonce := "" + if params.State != nil { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + codeChallenge = authorizeStateSplit[1] + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(refs.StringValue(params.State)) + } + } + + if nonce == "" { + nonce = uuid.New().String() } authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code) @@ -264,6 +271,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR return res, err } + // Code challenge could be optional if PKCE flow is not used + if code != "" { + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + log.Debug("SetState failed: ", err) + return res, err + } + } + expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() if expiresIn <= 0 { expiresIn = 1 diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 7dbf404..47b4429 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -16,6 +16,7 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/parsers" + "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" ) @@ -85,13 +86,42 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} - nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") + code := "" + // Not required as /oauth/token cannot be resumed from other tab + // codeChallenge := "" + nonce := "" + if params.State != nil { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + // Not required as /oauth/token cannot be resumed from other tab + // codeChallenge = authorizeStateSplit[1] + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(refs.StringValue(params.State)) + } + } + if nonce == "" { + nonce = uuid.New().String() + } + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err } + // Code challenge could be optional if PKCE flow is not used + // Not required as /oauth/token cannot be resumed from other tab + // if code != "" { + // if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + // log.Debug("SetState failed: ", err) + // return res, err + // } + // } go func() { if isSignUp { utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user) diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index fd70a9f..016678d 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -12,6 +12,7 @@ import ( "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/google/uuid" @@ -58,13 +59,40 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} - nonce := uuid.New().String() - authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") + code := "" + codeChallenge := "" + nonce := "" + if params.State != nil { + // Get state from store + authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) + if authorizeState != "" { + authorizeStateSplit := strings.Split(authorizeState, "@@") + if len(authorizeStateSplit) > 1 { + code = authorizeStateSplit[0] + codeChallenge = authorizeStateSplit[1] + } else { + nonce = authorizeState + } + go memorystore.Provider.RemoveState(refs.StringValue(params.State)) + } + } + if nonce == "" { + nonce = uuid.New().String() + } + authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code) if err != nil { log.Debug("Failed to create auth token: ", err) return res, err } + // Code challenge could be optional if PKCE flow is not used + if code != "" { + if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil { + log.Debug("Failed to set code state: ", err) + return res, err + } + } + go func() { db.Provider.DeleteOTP(gc, otp) if isSignUp { diff --git a/server/token/auth_token.go b/server/token/auth_token.go index 607f64d..32d1085 100644 --- a/server/token/auth_token.go +++ b/server/token/auth_token.go @@ -50,9 +50,6 @@ type SessionData struct { // CreateAuthToken creates a new auth token when userlogs in func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string, code string) (*Token, error) { - - fmt.Println("=> original nonce:", nonce) - hostname := parsers.GetHost(gc) _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod) if err != nil { @@ -72,7 +69,6 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l codeHashString := "" if code != "" { - fmt.Println("=> atHash", atHashString) codeHash := sha256.New() codeHash.Write([]byte(code)) codeHashBytes := codeHash.Sum(nil) @@ -80,7 +76,6 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest) } - fmt.Println("=> at hash nonce", nonce) idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod) if err != nil { return nil, err @@ -116,7 +111,6 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string, l IssuedAt: time.Now().Unix(), ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(), } - fmt.Printf("=> session data %+v\n", fingerPrintMap) fingerPrintBytes, _ := json.Marshal(fingerPrintMap) fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes)) if err != nil { @@ -381,8 +375,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH claimKey: roles, } - fmt.Println("=> nonce", nonce) - // split nonce to see if its authorization code grant method if cHash != "" { @@ -393,8 +385,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH customClaims["at_hash"] = atHash } - fmt.Println("custom_claims", customClaims) - for k, v := range userMap { if k != "roles" { customClaims[k] = v From bb2a42a1db07eb8ca01af148f1892ad3ee7b19ee Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 16 Nov 2022 12:20:32 +0530 Subject: [PATCH 32/32] fix: update app package --- app/package.json | 2 +- server/graph/schema.graphqls | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 14c520d..491f105 100644 --- a/app/package.json +++ b/app/package.json @@ -12,7 +12,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.3-beta.1", + "@authorizerdev/authorizer-react": "^1.1.3", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index bfa371a..457b6b0 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -446,6 +446,8 @@ input AddEmailTemplateRequest { event_name: String! subject: String! template: String! + # Design value is set when editor is used + # If raw HTML is used design value is set to null design: String } @@ -454,6 +456,8 @@ input UpdateEmailTemplateRequest { event_name: String template: String subject: String + # Design value is set when editor is used + # If raw HTML is used design value is set to null design: String }