OAuth: Add custom unauthorized message option in configuration (#93717)

* read custom message from config

* Read error key from bootdata

* oopsie

* Remove console.log

* Update docs and sample/default inis

* Add default key value to the config
This commit is contained in:
Tobias Skarhed 2024-09-27 12:11:27 +02:00 committed by GitHub
parent 08dab3f816
commit f49b4d35f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 32 additions and 5 deletions

View File

@ -582,6 +582,9 @@ oauth_auto_login = false
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds. # OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
oauth_state_cookie_max_age = 600 oauth_state_cookie_max_age = 600
# Sets a custom oAuth error message. This is useful if you need to point the users to a specific location for support.
oauth_login_error_message = oauth.login.error
# Minimum wait time in milliseconds for the server lock retry mechanism. # Minimum wait time in milliseconds for the server lock retry mechanism.
# The server lock retry mechanism is used to prevent multiple Grafana instances from # The server lock retry mechanism is used to prevent multiple Grafana instances from
# simultaneously refreshing OAuth tokens. This mechanism waits at least this amount # simultaneously refreshing OAuth tokens. This mechanism waits at least this amount

View File

@ -583,6 +583,9 @@
# Deprecated, use auto_login option for specific provider instead. # Deprecated, use auto_login option for specific provider instead.
;oauth_auto_login = false ;oauth_auto_login = false
# Sets a custom oAuth error message. This is useful if you need to point the users to a specific location for support.
;oauth_login_error_message = oauth.login.error
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds. # OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
;oauth_state_cookie_max_age = 600 ;oauth_state_cookie_max_age = 600

View File

@ -964,6 +964,10 @@ This setting is ignored if multiple OAuth providers are configured. Default is `
How many seconds the OAuth state cookie lives before being deleted. Default is `600` (seconds) How many seconds the OAuth state cookie lives before being deleted. Default is `600` (seconds)
Administrators can increase this if they experience OAuth login state mismatch errors. Administrators can increase this if they experience OAuth login state mismatch errors.
### oauth_login_error_message
A custom error message for when users are unauthorized. Default is a key for an internationalized phrase in the frontend, `Login provider denied login request`.
### oauth_refresh_token_server_lock_min_wait_ms ### oauth_refresh_token_server_lock_min_wait_ms
Minimum wait time in milliseconds for the server lock retry mechanism. Default is `1000` (milliseconds). The server lock retry mechanism is used to prevent multiple Grafana instances from simultaneously refreshing OAuth tokens. This mechanism waits at least this amount of time before retrying to acquire the server lock. Minimum wait time in milliseconds for the server lock retry mechanism. Default is `1000` (milliseconds). The server lock retry mechanism is used to prevent multiple Grafana instances from simultaneously refreshing OAuth tokens. This mechanism waits at least this amount of time before retrying to acquire the server lock.

View File

@ -1,8 +1,7 @@
package api package api
import ( import (
"errors" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -21,8 +20,7 @@ func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) {
if errorParam := reqCtx.Query("error"); errorParam != "" { if errorParam := reqCtx.Query("error"); errorParam != "" {
errorDesc := reqCtx.Query("error_description") errorDesc := reqCtx.Query("error_description")
hs.log.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc) hs.log.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
hs.redirectWithError(reqCtx, errutil.Unauthorized("oauth.login", errutil.WithPublicMessage(hs.Cfg.OAuthLoginErrorMessage)).Errorf("Login provider denied login request"))
hs.redirectWithError(reqCtx, errors.New("login provider denied login request"), "error", errorParam, "errorDesc", errorDesc)
return return
} }

View File

@ -263,6 +263,7 @@ type Cfg struct {
// OAuth // OAuth
OAuthAutoLogin bool OAuthAutoLogin bool
OAuthLoginErrorMessage string
OAuthCookieMaxAge int OAuthCookieMaxAge int
OAuthAllowInsecureEmailLookup bool OAuthAllowInsecureEmailLookup bool
OAuthRefreshTokenServerLockMinWaitMs int64 OAuthRefreshTokenServerLockMinWaitMs int64
@ -1621,6 +1622,8 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
cfg.Logger.Warn("[Deprecated] The oauth_auto_login configuration setting is deprecated. Please use auto_login inside auth provider section instead.") cfg.Logger.Warn("[Deprecated] The oauth_auto_login configuration setting is deprecated. Please use auto_login inside auth provider section instead.")
} }
// Default to the translation key used in the frontend
cfg.OAuthLoginErrorMessage = valueAsString(auth, "oauth_login_error_message", "oauth.login.error")
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600) cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600)
cfg.OAuthRefreshTokenServerLockMinWaitMs = auth.Key("oauth_refresh_token_server_lock_min_wait_ms").MustInt64(1000) cfg.OAuthRefreshTokenServerLockMinWaitMs = auth.Key("oauth_refresh_token_server_lock_min_wait_ms").MustInt64(1000)
cfg.SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "") cfg.SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")

View File

@ -51,7 +51,8 @@ export class LoginCtrl extends PureComponent<Props, State> {
isLoggingIn: false, isLoggingIn: false,
isChangingPassword: false, isChangingPassword: false,
showDefaultPasswordWarning: false, showDefaultPasswordWarning: false,
loginErrorMessage: config.loginError, // oAuth unauthorized sets the redirect error message in the bootdata, hence we need to check the key here
loginErrorMessage: getBootDataErrMessage(config.loginError),
}; };
} }
@ -178,3 +179,12 @@ function getErrorMessage(err: FetchError<undefined | { messageId?: string; messa
return err.data?.message; return err.data?.message;
} }
} }
function getBootDataErrMessage(str?: string) {
switch (str) {
case 'oauth.login.error':
return t('oauth.login.error', 'Login provider denied login request');
default:
return str;
}
}

View File

@ -1845,6 +1845,9 @@
"server-discovery-modal-close": "Close", "server-discovery-modal-close": "Close",
"server-discovery-modal-loading": "Loading...", "server-discovery-modal-loading": "Loading...",
"server-discovery-modal-submit": "Submit" "server-discovery-modal-submit": "Submit"
},
"login": {
"error": "Login provider denied login request"
} }
}, },
"panel": { "panel": {

View File

@ -1845,6 +1845,9 @@
"server-discovery-modal-close": "Cľőşę", "server-discovery-modal-close": "Cľőşę",
"server-discovery-modal-loading": "Ŀőäđįʼnģ...", "server-discovery-modal-loading": "Ŀőäđįʼnģ...",
"server-discovery-modal-submit": "Ŝūþmįŧ" "server-discovery-modal-submit": "Ŝūþmįŧ"
},
"login": {
"error": "Ŀőģįʼn přővįđęř đęʼnįęđ ľőģįʼn řęqūęşŧ"
} }
}, },
"panel": { "panel": {