Compare commits

..

3 Commits

Author SHA1 Message Date
Lakhan Samani
57bc091499 fix state management 2022-03-07 23:44:19 +05:30
Lakhan Samani
128a2a8f75 feat: add support for response mode 2022-03-07 18:49:18 +05:30
Lakhan Samani
7b09a8817c fix: env encryption 2022-03-07 16:16:54 +05:30
8 changed files with 182 additions and 119 deletions

14
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "0.7.0", "@authorizerdev/authorizer-react": "0.8.0",
"@types/react": "^17.0.15", "@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17", "esbuild": "^0.12.17",
@@ -35,9 +35,9 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-react": { "node_modules/@authorizerdev/authorizer-react": {
"version": "0.7.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz",
"integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": "^0.3.0", "@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2", "final-form": "^4.20.2",
@@ -837,9 +837,9 @@
} }
}, },
"@authorizerdev/authorizer-react": { "@authorizerdev/authorizer-react": {
"version": "0.7.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz",
"integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==", "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==",
"requires": { "requires": {
"@authorizerdev/authorizer-js": "^0.3.0", "@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2", "final-form": "^4.20.2",

View File

@@ -6,6 +6,9 @@ import Root from './Root';
export default function App() { export default function App() {
// @ts-ignore // @ts-ignore
const globalState: Record<string, string> = window['__authorizer__']; const globalState: Record<string, string> = window['__authorizer__'];
if (globalState.state) {
sessionStorage.setItem('authorizer_state', globalState.state);
}
return ( return (
<div <div
style={{ style={{

View File

@@ -11,9 +11,14 @@ export default function Root() {
useEffect(() => { useEffect(() => {
if (token) { if (token) {
const state = sessionStorage.getItem('authorizer_state')?.trim();
const url = new URL(config.redirectURL || '/app'); const url = new URL(config.redirectURL || '/app');
if (url.origin !== window.location.origin) { if (url.origin !== window.location.origin) {
window.location.href = config.redirectURL || '/app'; console.log({ x: `${config.redirectURL || '/app'}?state=${state}` });
sessionStorage.removeItem('authorizer_state');
window.location.replace(
`${config.redirectURL || '/app'}?state=${state}`
);
} }
} }
return () => {}; return () => {};

View File

@@ -94,12 +94,13 @@ func EncryptEnvData(data envstore.Store) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
encryptedConfig, err := EncryptAESEnv(configData) encryptedConfig, err := EncryptAESEnv(configData)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(encryptedConfig), nil return EncryptB64(string(encryptedConfig)), nil
} }
// EncryptPassword is used for encrypting password // EncryptPassword is used for encrypting password

View File

@@ -112,7 +112,7 @@ func PersistEnv() error {
for key, value := range storeData.StringEnv { for key, value := range storeData.StringEnv {
// don't override unexposed envs // don't override unexposed envs
if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyClientSecret && key != constants.EnvKeyJWK { if key != constants.EnvKeyEncryptionKey {
// check only for derivative keys // check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json // as we have removed it from json

View File

@@ -18,6 +18,7 @@ import (
type State struct { type State struct {
AuthorizerURL string `json:"authorizerURL"` AuthorizerURL string `json:"authorizerURL"`
RedirectURL string `json:"redirectURL"` RedirectURL string `json:"redirectURL"`
State string `json:"state"`
} }
// AppHandler is the handler for the /app route // AppHandler is the handler for the /app route
@@ -80,6 +81,7 @@ func AppHandler() gin.HandlerFunc {
"data": map[string]string{ "data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL, "authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL, "redirectURL": stateObj.RedirectURL,
"state": stateObj.State,
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), "organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
}, },

View File

@@ -6,10 +6,12 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -17,6 +19,7 @@ import (
// AuthorizeHandler is the handler for the /authorize route // AuthorizeHandler is the handler for the /authorize route
// required params // required params
// ?redirect_uri = redirect url // ?redirect_uri = redirect url
// ?response_mode = to decide if result should be html or re-direct
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory) // state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
// code_challenge = to prevent CSRF attack // code_challenge = to prevent CSRF attack
// code_challenge_method = to prevent CSRF attack [only sh256 is supported] // code_challenge_method = to prevent CSRF attack [only sh256 is supported]
@@ -31,56 +34,74 @@ func AuthorizeHandler() gin.HandlerFunc {
scopeString := strings.TrimSpace(gc.Query("scope")) scopeString := strings.TrimSpace(gc.Query("scope"))
clientID := strings.TrimSpace(gc.Query("client_id")) clientID := strings.TrimSpace(gc.Query("client_id"))
template := "authorize.tmpl" template := "authorize.tmpl"
responseMode := strings.TrimSpace(gc.Query("response_mode"))
if responseMode == "" {
responseMode = "query"
}
if responseMode != "query" && responseMode != "web_message" {
gc.JSON(400, gin.H{"error": "invalid response mode"})
}
if redirectURI == "" {
redirectURI = "/app"
}
isQuery := responseMode == "query"
hostname := utils.GetHost(gc)
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`)
loginURL := "/app?state=" + loginRedirectState
if clientID == "" { if clientID == "" {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "client_id is required", "authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "client_id is required",
},
}, },
}, })
}) }
return return
} }
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) { if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "invalid_client_id", "authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "invalid_client_id",
},
}, },
}, })
}) }
return
}
if redirectURI == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "redirect_uri is required",
},
},
})
return return
} }
if state == "" { if state == "" {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "state is required", "authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "state is required",
},
}, },
}, })
}) }
return return
} }
@@ -99,76 +120,96 @@ func AuthorizeHandler() gin.HandlerFunc {
isResponseTypeToken := responseType == "token" isResponseTypeToken := responseType == "token"
if !isResponseTypeCode && !isResponseTypeToken { if !isResponseTypeCode && !isResponseTypeToken {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "response_type is invalid", "authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "response_type is invalid",
},
}, },
}, })
}) }
return return
} }
if isResponseTypeCode { if isResponseTypeCode {
if codeChallenge == "" { if codeChallenge == "" {
gc.HTML(http.StatusBadRequest, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusBadRequest, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "code_challenge is required", "authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "code_challenge is required",
},
}, },
}, })
}) }
return return
} }
} }
sessionToken, err := cookie.GetSession(gc) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "login_required", "authorization_response": map[string]interface{}{
"error_description": "Login is required", "type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
}, },
}, })
}) }
return return
} }
// get session from cookie // get session from cookie
claims, err := token.ValidateBrowserSession(gc, sessionToken) claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "login_required", "authorization_response": map[string]interface{}{
"error_description": "Login is required", "type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
}, },
}, })
}) }
return return
} }
userID := claims.Subject userID := claims.Subject
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "signup_required", "authorization_response": map[string]interface{}{
"error_description": "Sign up required", "type": "authorization_response",
"response": map[string]string{
"error": "signup_required",
"error_description": "Sign up required",
},
}, },
}, })
}) }
return return
} }
@@ -180,16 +221,20 @@ func AuthorizeHandler() gin.HandlerFunc {
nonce := uuid.New().String() nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "login_required", "authorization_response": map[string]interface{}{
"error_description": "Login is required", "type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
}, },
}, })
}) }
return return
} }
@@ -214,16 +259,20 @@ func AuthorizeHandler() gin.HandlerFunc {
// rollover the session for security // rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope) authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ if isQuery {
"target_origin": redirectURI, gc.Redirect(http.StatusFound, loginURL)
"authorization_response": map[string]interface{}{ } else {
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "login_required", "authorization_response": map[string]interface{}{
"error_description": "Login is required", "type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
}, },
}, })
}) }
return return
} }
sessionstore.RemoveState(sessionToken) sessionstore.RemoveState(sessionToken)
@@ -256,16 +305,20 @@ func AuthorizeHandler() gin.HandlerFunc {
return return
} }
// by default return with error if isQuery {
gc.HTML(http.StatusOK, template, gin.H{ gc.Redirect(http.StatusFound, loginURL)
"target_origin": redirectURI, } else {
"authorization_response": map[string]interface{}{ // by default return with error
"type": "authorization_response", gc.HTML(http.StatusOK, template, gin.H{
"response": map[string]string{ "target_origin": redirectURI,
"error": "login_required", "authorization_response": map[string]interface{}{
"error_description": "Login is required", "type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
}, },
}, })
}) }
} }
} }

View File

@@ -8,7 +8,6 @@
(function (window, document) { (function (window, document) {
var targetOrigin = {{.target_origin}}; var targetOrigin = {{.target_origin}};
var authorizationResponse = {{.authorization_response}}; var authorizationResponse = {{.authorization_response}};
console.log({targetOrigin})
window.parent.postMessage(authorizationResponse, targetOrigin); window.parent.postMessage(authorizationResponse, targetOrigin);
})(this, this.document); })(this, this.document);
</script> </script>