feat: add token endpoint
This commit is contained in:
parent
2946428ab8
commit
0787a3b494
|
@ -2,6 +2,7 @@ package cookie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
@ -56,5 +57,9 @@ func GetSession(gc *gin.Context) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookie.Value, nil
|
decodedValue, err := url.PathUnescape(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return decodedValue, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -27,6 +26,8 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
responseType := strings.TrimSpace(gc.Query("response_type"))
|
responseType := strings.TrimSpace(gc.Query("response_type"))
|
||||||
state := strings.TrimSpace(gc.Query("state"))
|
state := strings.TrimSpace(gc.Query("state"))
|
||||||
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
|
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
|
||||||
|
scopeString := strings.TrimSpace(gc.Query("scope"))
|
||||||
|
scope := []string{}
|
||||||
template := "authorize.tmpl"
|
template := "authorize.tmpl"
|
||||||
|
|
||||||
if redirectURI == "" {
|
if redirectURI == "" {
|
||||||
|
@ -59,6 +60,10 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
responseType = "token"
|
responseType = "token"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scopeString == "" {
|
||||||
|
scope = []string{"openid", "profile", "email"}
|
||||||
|
}
|
||||||
|
|
||||||
isResponseTypeCode := responseType == "code"
|
isResponseTypeCode := responseType == "code"
|
||||||
isResponseTypeToken := responseType == "token"
|
isResponseTypeToken := responseType == "token"
|
||||||
|
|
||||||
|
@ -142,7 +147,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
// rollover the session for security
|
// rollover the session for security
|
||||||
sessionstore.RemoveState(sessionToken)
|
sessionstore.RemoveState(sessionToken)
|
||||||
nonce := uuid.New().String()
|
nonce := uuid.New().String()
|
||||||
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, claims.Scope)
|
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
"target_origin": nil,
|
"target_origin": nil,
|
||||||
|
@ -160,7 +165,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
|
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
|
||||||
cookie.SetSession(gc, newSessionToken)
|
cookie.SetSession(gc, newSessionToken)
|
||||||
code := uuid.New().String()
|
code := uuid.New().String()
|
||||||
sessionstore.SetState("code_challenge_"+codeChallenge, code)
|
sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
"target_origin": redirectURI,
|
"target_origin": redirectURI,
|
||||||
"authorization_response": map[string]string{
|
"authorization_response": map[string]string{
|
||||||
|
@ -173,7 +178,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
|
|
||||||
if isResponseTypeToken {
|
if isResponseTypeToken {
|
||||||
// rollover the session for security
|
// rollover the session for security
|
||||||
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
|
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
"target_origin": nil,
|
"target_origin": nil,
|
||||||
|
@ -191,20 +196,28 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := int64(1800)
|
||||||
|
res := map[string]interface{}{
|
||||||
|
"access_token": authToken.AccessToken.Token,
|
||||||
|
"id_token": authToken.IDToken.Token,
|
||||||
|
"state": state,
|
||||||
|
"scope": scope,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res["refresh_token"] = authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
"target_origin": redirectURI,
|
"target_origin": redirectURI,
|
||||||
"authorization_response": map[string]interface{}{
|
"authorization_response": res,
|
||||||
"access_token": authToken.AccessToken.Token,
|
|
||||||
"id_token": authToken.IDToken.Token,
|
|
||||||
"state": state,
|
|
||||||
"scope": claims.Scope,
|
|
||||||
"expires_in": expiresIn,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("=> returning from here...")
|
|
||||||
|
|
||||||
// by default return with error
|
// by default return with error
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
|
126
server/handlers/token.go
Normal file
126
server/handlers/token.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TokenHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
var reqBody map[string]string
|
||||||
|
if err := gc.BindJSON(&reqBody); err != nil {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "error_binding_json",
|
||||||
|
"error_description": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
|
||||||
|
code := strings.TrimSpace(reqBody["code"])
|
||||||
|
redirectURI := strings.TrimSpace(reqBody["redirect_uri"])
|
||||||
|
|
||||||
|
if codeVerifier == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code",
|
||||||
|
"error_description": "The code is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectURI == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_redirect_uri",
|
||||||
|
"error_description": "The redirect URI is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(codeVerifier))
|
||||||
|
encryptedCode := strings.TrimSuffix(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "=")
|
||||||
|
sessionData := sessionstore.GetState(encryptedCode)
|
||||||
|
if sessionData == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// split session data
|
||||||
|
// it contains code@sessiontoken
|
||||||
|
sessionDataSplit := strings.Split(sessionData, "@")
|
||||||
|
|
||||||
|
if sessionDataSplit[0] != code {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate session
|
||||||
|
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "Invalid session data",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := claims.Subject
|
||||||
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "User not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// rollover the session for security
|
||||||
|
sessionstore.RemoveState(sessionDataSplit[1])
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "User not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
|
expiresIn := int64(1800)
|
||||||
|
res := map[string]interface{}{
|
||||||
|
"access_token": authToken.AccessToken.Token,
|
||||||
|
"id_token": authToken.IDToken.Token,
|
||||||
|
"scope": claims.Scope,
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res["refresh_token"] = authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.JSON(http.StatusOK, res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ func InitRouter() *gin.Engine {
|
||||||
router.GET("/authorize", handlers.AuthorizeHandler())
|
router.GET("/authorize", handlers.AuthorizeHandler())
|
||||||
router.GET("/userinfo", handlers.UserInfoHandler())
|
router.GET("/userinfo", handlers.UserInfoHandler())
|
||||||
router.GET("/logout", handlers.LogoutHandler())
|
router.GET("/logout", handlers.LogoutHandler())
|
||||||
|
router.POST("/token", handlers.TokenHandler())
|
||||||
|
|
||||||
router.LoadHTMLGlob("templates/*")
|
router.LoadHTMLGlob("templates/*")
|
||||||
// login page app related routes.
|
// login page app related routes.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user