2022-02-28 15:56:49 +00:00
package handlers
import (
"net/http"
"strings"
2022-03-07 03:01:39 +00:00
"github.com/authorizerdev/authorizer/server/constants"
2022-03-03 19:06:27 +00:00
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
2022-03-07 03:01:39 +00:00
"github.com/authorizerdev/authorizer/server/envstore"
2022-03-03 19:06:27 +00:00
"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-03-04 07:26:11 +00:00
scopeString := strings . TrimSpace ( gc . Query ( "scope" ) )
2022-03-07 03:01:39 +00:00
clientID := strings . TrimSpace ( gc . Query ( "client_id" ) )
2022-02-28 15:56:49 +00:00
template := "authorize.tmpl"
2022-03-07 03:01:39 +00:00
if 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 clientID != envstore . EnvStoreObj . GetStringStoreEnvVariable ( constants . EnvKeyClientID ) {
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
}
2022-02-28 15:56:49 +00:00
if redirectURI == "" {
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusOK , template , gin . H {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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-07 03:01:39 +00:00
var scope [ ] string
2022-03-04 07:26:11 +00:00
if scopeString == "" {
scope = [ ] string { "openid" , "profile" , "email" }
2022-03-07 03:01:39 +00:00
} else {
scope = strings . Split ( scopeString , " " )
2022-03-04 07:26:11 +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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 ( )
2022-03-04 07:26:11 +00:00
newSessionTokenData , newSessionToken , err := token . CreateSessionToken ( user , nonce , claims . Roles , scope )
2022-03-03 19:06:27 +00:00
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 ( )
2022-03-04 07:26:11 +00:00
sessionstore . SetState ( codeChallenge , code + "@" + newSessionToken )
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusOK , template , gin . H {
"target_origin" : redirectURI ,
2022-03-07 03:01:39 +00:00
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : map [ string ] string {
"code" : code ,
"state" : state ,
} ,
2022-03-03 19:06:27 +00:00
} ,
} )
return
}
if isResponseTypeToken {
// rollover the session for security
2022-03-04 07:26:11 +00:00
authToken , err := token . CreateAuthToken ( gc , user , claims . Roles , scope )
2022-03-03 19:06:27 +00:00
if err != nil {
gc . HTML ( http . StatusOK , template , gin . H {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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 )
2022-03-04 07:26:11 +00:00
2022-03-03 19:06:27 +00:00
expiresIn := int64 ( 1800 )
2022-03-04 07:26:11 +00:00
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 )
}
2022-03-03 19:06:27 +00:00
gc . HTML ( http . StatusOK , template , gin . H {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
"authorization_response" : map [ string ] interface { } {
"type" : "authorization_response" ,
"response" : res ,
} ,
2022-03-03 19:06:27 +00:00
} )
return
2022-02-28 15:56:49 +00:00
}
2022-03-03 19:06:27 +00:00
// by default return with error
gc . HTML ( http . StatusOK , template , gin . H {
2022-03-07 03:01:39 +00:00
"target_origin" : redirectURI ,
2022-03-03 19:06:27 +00:00
"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
}
}