2022-02-28 15:56:49 +00:00
package handlers
import (
"fmt"
"net/http"
"strings"
2022-03-03 19:06:27 +00:00
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
2022-02-28 15:56:49 +00:00
"github.com/gin-gonic/gin"
2022-03-03 19:06:27 +00:00
"github.com/google/uuid"
2022-02-28 15:56:49 +00:00
)
// AuthorizeHandler is the handler for the /authorize route
// required params
// ?redirect_uri = redirect url
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
// code_challenge = to prevent CSRF attack
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
2022-03-03 19:06:27 +00:00
// 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.
2022-02-28 15:56:49 +00:00
func AuthorizeHandler ( ) gin . HandlerFunc {
2022-03-03 19:06:27 +00:00
return func ( gc * gin . Context ) {
redirectURI := strings . TrimSpace ( gc . Query ( "redirect_uri" ) )
responseType := strings . TrimSpace ( gc . Query ( "response_type" ) )
state := strings . TrimSpace ( gc . Query ( "state" ) )
codeChallenge := strings . TrimSpace ( gc . Query ( "code_challenge" ) )
2022-02-28 15:56:49 +00:00
template := "authorize.tmpl"
if redirectURI == "" {
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "redirect_uri is required" ,
} ,
} ,
2022-02-28 15:56:49 +00:00
} )
return
}
if state == "" {
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "state is required" ,
} ,
} ,
2022-02-28 15:56:49 +00:00
} )
return
}
if responseType == "" {
2022-03-03 19:06:27 +00:00
responseType = "token"
2022-02-28 15:56:49 +00:00
}
2022-03-03 19:06:27 +00:00
isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token"
2022-02-28 15:56:49 +00:00
2022-03-03 19:06:27 +00:00
if ! isResponseTypeCode && ! isResponseTypeToken {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "response_type is invalid" ,
} ,
} ,
2022-02-28 15:56:49 +00:00
} )
return
}
2022-03-03 19:06:27 +00:00
if isResponseTypeCode {
2022-02-28 15:56:49 +00:00
if codeChallenge == "" {
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusBadRequest , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "code_challenge is required" ,
} ,
} ,
} )
return
}
}
sessionToken , err := cookie . GetSession ( gc )
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "login_required" ,
"error_description" : "Login is required" ,
} ,
} ,
} )
return
}
// get session from cookie
claims , err := token . ValidateBrowserSession ( gc , sessionToken )
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "login_required" ,
"error_description" : "Login is required" ,
} ,
} ,
} )
return
}
userID := claims . Subject
user , err := db . Provider . GetUserByID ( userID )
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "signup_required" ,
"error_description" : "Sign up required" ,
} ,
} ,
} )
return
}
// if user is logged in
// based on the response type, generate the response
if isResponseTypeCode {
// rollover the session for security
sessionstore . RemoveState ( sessionToken )
nonce := uuid . New ( ) . String ( )
newSessionTokenData , newSessionToken , err := token . CreateSessionToken ( user , nonce , claims . Roles , claims . Scope )
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "login_required" ,
"error_description" : "Login is required" ,
} ,
} ,
} )
return
}
sessionstore . SetState ( newSessionToken , newSessionTokenData . Nonce + "@" + user . ID )
cookie . SetSession ( gc , newSessionToken )
code := uuid . New ( ) . String ( )
sessionstore . SetState ( "code_challenge_" + codeChallenge , code )
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : redirectURI ,
"authorization_response" : map [ string ] string {
"code" : code ,
"state" : state ,
} ,
} )
return
}
if isResponseTypeToken {
// rollover the session for security
authToken , err := token . CreateAuthToken ( gc , user , claims . Roles , claims . Scope )
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "login_required" ,
"error_description" : "Login is required" ,
} ,
} ,
2022-02-28 15:56:49 +00:00
} )
return
}
2022-03-03 19:06:27 +00:00
sessionstore . RemoveState ( sessionToken )
sessionstore . SetState ( authToken . FingerPrintHash , authToken . FingerPrint + "@" + user . ID )
sessionstore . SetState ( authToken . AccessToken . Token , authToken . FingerPrint + "@" + user . ID )
cookie . SetSession ( gc , authToken . FingerPrintHash )
expiresIn := int64 ( 1800 )
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : redirectURI ,
"authorization_response" : map [ string ] interface { } {
"access_token" : authToken . AccessToken . Token ,
"id_token" : authToken . IDToken . Token ,
"state" : state ,
"scope" : claims . Scope ,
"expires_in" : expiresIn ,
} ,
} )
return
2022-02-28 15:56:49 +00:00
}
2022-03-03 19:06:27 +00:00
fmt . Println ( "=> returning from here..." )
// by default return with error
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : nil ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"error" : "login_required" ,
"error_description" : "Login is required" ,
} ,
} ,
} )
2022-02-28 15:56:49 +00:00
}
}