fix: add token information in redirect url
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -18,7 +16,6 @@ import (
|
||||
type State struct {
|
||||
AuthorizerURL string `json:"authorizerURL"`
|
||||
RedirectURL string `json:"redirectURL"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// AppHandler is the handler for the /app route
|
||||
@@ -30,44 +27,25 @@ func AppHandler() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
state := c.Query("state")
|
||||
redirect_uri := strings.TrimSpace(c.Query("redirect_uri"))
|
||||
state := strings.TrimSpace(c.Query("state"))
|
||||
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||
|
||||
var stateObj State
|
||||
|
||||
if state == "" {
|
||||
stateObj.AuthorizerURL = hostname
|
||||
stateObj.RedirectURL = hostname + "/app"
|
||||
var scope []string
|
||||
if scopeString == "" {
|
||||
scope = []string{"openid", "profile", "email"}
|
||||
} else {
|
||||
decodedState, err := crypto.DecryptB64(state)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(decodedState), &stateObj)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
||||
return
|
||||
}
|
||||
stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/")
|
||||
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
|
||||
scope = strings.Split(scopeString, " ")
|
||||
}
|
||||
|
||||
if redirect_uri == "" {
|
||||
redirect_uri = hostname + "/app"
|
||||
} else {
|
||||
// validate redirect url with allowed origins
|
||||
if !utils.IsValidOrigin(stateObj.RedirectURL) {
|
||||
if !utils.IsValidOrigin(redirect_uri) {
|
||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||
return
|
||||
}
|
||||
|
||||
if stateObj.AuthorizerURL == "" {
|
||||
c.JSON(400, gin.H{"error": "invalid authorizer url"})
|
||||
return
|
||||
}
|
||||
|
||||
// validate host and domain of authorizer url
|
||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
|
||||
c.JSON(400, gin.H{"error": "invalid host url"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// debug the request state
|
||||
@@ -78,10 +56,11 @@ func AppHandler() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
c.HTML(http.StatusOK, "app.tmpl", gin.H{
|
||||
"data": map[string]string{
|
||||
"authorizerURL": stateObj.AuthorizerURL,
|
||||
"redirectURL": stateObj.RedirectURL,
|
||||
"state": stateObj.State,
|
||||
"data": map[string]interface{}{
|
||||
"authorizerURL": hostname,
|
||||
"redirectURL": redirect_uri,
|
||||
"scope": scope,
|
||||
"state": state,
|
||||
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
|
||||
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
|
||||
},
|
||||
|
@@ -2,16 +2,15 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/cookie"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
"github.com/authorizerdev/authorizer/server/token"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -36,6 +35,13 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
template := "authorize.tmpl"
|
||||
responseMode := strings.TrimSpace(gc.Query("response_mode"))
|
||||
|
||||
var scope []string
|
||||
if scopeString == "" {
|
||||
scope = []string{"openid", "profile", "email"}
|
||||
} else {
|
||||
scope = strings.Split(scopeString, " ")
|
||||
}
|
||||
|
||||
if responseMode == "" {
|
||||
responseMode = "query"
|
||||
}
|
||||
@@ -50,9 +56,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
|
||||
isQuery := responseMode == "query"
|
||||
|
||||
hostname := utils.GetHost(gc)
|
||||
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`)
|
||||
loginURL := "/app?state=" + loginRedirectState
|
||||
loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
|
||||
|
||||
if clientID == "" {
|
||||
if isQuery {
|
||||
@@ -109,13 +113,6 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
responseType = "token"
|
||||
}
|
||||
|
||||
var scope []string
|
||||
if scopeString == "" {
|
||||
scope = []string{"openid", "profile", "email"}
|
||||
} else {
|
||||
scope = strings.Split(scopeString, " ")
|
||||
}
|
||||
|
||||
isResponseTypeCode := responseType == "code"
|
||||
isResponseTypeToken := responseType == "token"
|
||||
|
||||
@@ -279,8 +276,11 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||
|
||||
expiresIn := int64(1800)
|
||||
|
||||
// used of query mode
|
||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||
|
||||
res := map[string]interface{}{
|
||||
"access_token": authToken.AccessToken.Token,
|
||||
"id_token": authToken.IDToken.Token,
|
||||
@@ -292,16 +292,25 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
|
||||
if authToken.RefreshToken != nil {
|
||||
res["refresh_token"] = authToken.RefreshToken.Token
|
||||
params += "&refresh_token=" + authToken.RefreshToken.Token
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
}
|
||||
|
||||
gc.HTML(http.StatusOK, template, gin.H{
|
||||
"target_origin": redirectURI,
|
||||
"authorization_response": map[string]interface{}{
|
||||
"type": "authorization_response",
|
||||
"response": res,
|
||||
},
|
||||
})
|
||||
if isQuery {
|
||||
if strings.Contains(redirectURI, "?") {
|
||||
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
|
||||
} else {
|
||||
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
|
||||
}
|
||||
} else {
|
||||
gc.HTML(http.StatusOK, template, gin.H{
|
||||
"target_origin": redirectURI,
|
||||
"authorization_response": map[string]interface{}{
|
||||
"type": "authorization_response",
|
||||
"response": res,
|
||||
},
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@@ -37,16 +37,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||
}
|
||||
sessionstore.GetState(state)
|
||||
// contains random token, redirect url, role
|
||||
sessionSplit := strings.Split(state, "___")
|
||||
sessionSplit := strings.Split(state, "@")
|
||||
|
||||
// TODO validate redirect url
|
||||
if len(sessionSplit) < 2 {
|
||||
if len(sessionSplit) < 3 {
|
||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||
return
|
||||
}
|
||||
|
||||
inputRoles := strings.Split(sessionSplit[2], ",")
|
||||
stateValue := sessionSplit[0]
|
||||
redirectURL := sessionSplit[1]
|
||||
inputRoles := strings.Split(sessionSplit[2], ",")
|
||||
scopes := strings.Split(sessionSplit[3], ",")
|
||||
|
||||
var err error
|
||||
user := models.User{}
|
||||
@@ -145,17 +146,29 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use query param
|
||||
scope := []string{"openid", "email", "profile"}
|
||||
nonce := uuid.New().String()
|
||||
_, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope)
|
||||
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
}
|
||||
expiresIn := int64(1800)
|
||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
||||
|
||||
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
|
||||
if authToken.RefreshToken != nil {
|
||||
params = params + `&refresh_token=${refresh_token}`
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
}
|
||||
|
||||
sessionstore.SetState(newSessionToken, nonce+"@"+user.ID)
|
||||
cookie.SetSession(c, newSessionToken)
|
||||
go utils.SaveSessionInDB(c, user.ID)
|
||||
if strings.Contains(redirectURL, "?") {
|
||||
redirectURL = redirectURL + "&" + params
|
||||
} else {
|
||||
redirectURL = redirectURL + "?" + params
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||
}
|
||||
}
|
||||
|
@@ -10,23 +10,38 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
||||
func OAuthLoginHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
hostname := utils.GetHost(c)
|
||||
redirectURL := c.Query("redirectURL")
|
||||
roles := c.Query("roles")
|
||||
redirectURI := strings.TrimSpace(c.Query("redirectURL"))
|
||||
roles := strings.TrimSpace(c.Query("roles"))
|
||||
state := strings.TrimSpace(c.Query("state"))
|
||||
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||
|
||||
if redirectURL == "" {
|
||||
if redirectURI == "" {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid redirect url",
|
||||
"error": "invalid redirect uri",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if state == "" {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid state",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var scope []string
|
||||
if scopeString == "" {
|
||||
scope = []string{"openid", "profile", "email"}
|
||||
} else {
|
||||
scope = strings.Split(scopeString, " ")
|
||||
}
|
||||
|
||||
if roles != "" {
|
||||
// validate role
|
||||
rolesSplit := strings.Split(roles, ",")
|
||||
@@ -43,8 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||
}
|
||||
|
||||
uuid := uuid.New()
|
||||
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
|
||||
oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",")
|
||||
|
||||
provider := c.Param("oauth_provider")
|
||||
isProviderConfigured := true
|
||||
|
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/token"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// VerifyEmailHandler handles the verify email route.
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func VerifyEmailHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
errorRes := gin.H{
|
||||
"error": "invalid token",
|
||||
"error": "invalid_token",
|
||||
}
|
||||
tokenInQuery := c.Query("token")
|
||||
if tokenInQuery == "" {
|
||||
@@ -29,30 +29,24 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||
|
||||
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
|
||||
if err != nil {
|
||||
errorRes["error_description"] = err.Error()
|
||||
c.JSON(400, errorRes)
|
||||
return
|
||||
}
|
||||
|
||||
// verify if token exists in db
|
||||
hostname := utils.GetHost(c)
|
||||
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
|
||||
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
|
||||
if err != nil {
|
||||
errorRes["error_description"] = err.Error()
|
||||
c.JSON(400, errorRes)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
errorRes["error_description"] = err.Error()
|
||||
c.JSON(400, errorRes)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,21 +59,53 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||
// delete from verification table
|
||||
db.Provider.DeleteVerificationRequest(verificationRequest)
|
||||
|
||||
roles := strings.Split(user.Roles, ",")
|
||||
scope := []string{"openid", "email", "profile"}
|
||||
nonce := uuid.New().String()
|
||||
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope)
|
||||
state := strings.TrimSpace(c.Query("state"))
|
||||
redirectURL := strings.TrimSpace(c.Query("redirect_uri"))
|
||||
rolesString := strings.TrimSpace(c.Query("roles"))
|
||||
var roles []string
|
||||
if rolesString == "" {
|
||||
roles = strings.Split(user.Roles, ",")
|
||||
} else {
|
||||
roles = strings.Split(rolesString, ",")
|
||||
}
|
||||
|
||||
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||
var scope []string
|
||||
if scopeString == "" {
|
||||
scope = []string{"openid", "email", "profile"}
|
||||
} else {
|
||||
scope = strings.Split(scopeString, " ")
|
||||
}
|
||||
authToken, err := token.CreateAuthToken(c, user, roles, scope)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
errorRes["error_description"] = err.Error()
|
||||
c.JSON(500, errorRes)
|
||||
return
|
||||
}
|
||||
sessionstore.SetState(authToken, nonce+"@"+user.ID)
|
||||
cookie.SetSession(c, authToken)
|
||||
expiresIn := int64(1800)
|
||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||
|
||||
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
|
||||
if authToken.RefreshToken != nil {
|
||||
params = params + `&refresh_token=${refresh_token}`
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
}
|
||||
|
||||
if redirectURL == "" {
|
||||
redirectURL = claim["redirect_url"].(string)
|
||||
}
|
||||
|
||||
if strings.Contains(redirectURL, "?") {
|
||||
redirectURL = redirectURL + "&" + params
|
||||
} else {
|
||||
redirectURL = redirectURL + "?" + params
|
||||
}
|
||||
|
||||
go utils.SaveSessionInDB(c, user.ID)
|
||||
|
||||
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
|
||||
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user