fix(server): authorizer as oauth provider

This commit is contained in:
Lakhan Samani 2022-10-09 19:48:13 +05:30
parent d8eceadd7f
commit 0115128ee7
4 changed files with 61 additions and 147 deletions

View File

@ -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"
)

View File

@ -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
}

View File

@ -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 {

View File

@ -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{