diff --git a/.vscode/launch.json b/.vscode/launch.json index d375395..eb97bae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,7 +23,9 @@ "HBOX_LOG_LEVEL": "debug", "HBOX_DEBUG_ENABLED": "true", "HBOX_STORAGE_DATA": "${workspaceRoot}/backend/.data", - "HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1" + "HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1", + "HBOX_OPTIONS_HEADER_SSO_ENABLED": "true", + "HBOX_OPTIONS_HEADER_SSO_ALLOWED_IP": "127.0.0.1", }, }, { diff --git a/backend/app/api/handlers/v1/controller.go b/backend/app/api/handlers/v1/controller.go index bf14a9d..88d934a 100644 --- a/backend/app/api/handlers/v1/controller.go +++ b/backend/app/api/handlers/v1/controller.go @@ -43,12 +43,26 @@ func WithRegistration(allowRegistration bool) func(*V1Controller) { } } +func WithHeaderSSO(headerSSOEnabled bool) func(*V1Controller) { + return func(ctrl *V1Controller) { + ctrl.headerSSOEnabled = headerSSOEnabled + } +} + +func WithHeaderSSOAllowedIP(headerSSOAllowedIP string) func(*V1Controller) { + return func(ctrl *V1Controller) { + ctrl.headerSSOAllowedIP = headerSSOAllowedIP + } +} + type V1Controller struct { - repo *repo.AllRepos - svc *services.AllServices - maxUploadSize int64 - isDemo bool - allowRegistration bool + repo *repo.AllRepos + svc *services.AllServices + maxUploadSize int64 + isDemo bool + allowRegistration bool + headerSSOEnabled bool + headerSSOAllowedIP string } type ( diff --git a/backend/app/api/handlers/v1/v1_ctrl_auth.go b/backend/app/api/handlers/v1/v1_ctrl_auth.go index 22b0e2a..a11db5c 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_auth.go +++ b/backend/app/api/handlers/v1/v1_ctrl_auth.go @@ -91,13 +91,25 @@ func (ctrl *V1Controller) HandleAuthLogin() errchain.HandlerFunc { func (ctrl *V1Controller) HandleSsoHeaderLogin() server.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { - var username = r.Header.Get("Remote-Email") - - if username == "" { - return validate.NewRequestError(errors.New("authentication failed. not SSO header found"), http.StatusInternalServerError) + log.Info().Msg("Header SSO Login Attempt") + if !ctrl.headerSSOEnabled { + return validate.NewRequestError(errors.New("authentication failed. Header SSO is disaled"), http.StatusInternalServerError) + } + { + t := strings.Split(r.RemoteAddr, ":") + if t[0] != ctrl.headerSSOAllowedIP { + return validate.NewRequestError(errors.New("authentication failed. Header SSO not allowed for this remote IP"), http.StatusInternalServerError) + } + log.Info().Msgf("Header SSO Login Attempt allowed from IP '%s'", t[0]) } - newToken, err := ctrl.svc.User.LoginWithoutPassword(r.Context(), strings.ToLower(username)) + email := r.Header.Get("Remote-Email") + + if email == "" { + return validate.NewRequestError(errors.New("authentication failed. not SSO header found or empty"), http.StatusInternalServerError) + } + + newToken, err := ctrl.svc.User.LoginWithoutPassword(r.Context(), strings.ToLower(email)) if err != nil { return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError) diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index f62400e..f7aeccd 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -54,6 +54,8 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize), v1.WithRegistration(a.conf.Options.AllowRegistration), v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode + v1.WithHeaderSSO(a.conf.Options.HeaderSSOEnabled), + v1.WithHeaderSSOAllowedIP(a.conf.Options.HeaderSSOAllowedIP), ) r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{ diff --git a/backend/internal/sys/config/conf.go b/backend/internal/sys/config/conf.go index 6ece6c5..009b59e 100644 --- a/backend/internal/sys/config/conf.go +++ b/backend/internal/sys/config/conf.go @@ -28,6 +28,8 @@ type Config struct { type Options struct { AllowRegistration bool `yaml:"disable_registration" conf:"default:true"` AutoIncrementAssetID bool `yaml:"auto_increment_asset_id" conf:"default:true"` + HeaderSSOEnabled bool `yaml:"header_sso_enabled" conf:"default:false"` + HeaderSSOAllowedIP string `yaml:"header_sso_allowed_ip" conf:"default:0.0.0.0"` } type DebugConf struct { diff --git a/docs/docs/quick-start.md b/docs/docs/quick-start.md index e0ad87b..0e107fd 100644 --- a/docs/docs/quick-start.md +++ b/docs/docs/quick-start.md @@ -47,6 +47,8 @@ volumes: | HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this | | HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves | | HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto increments the asset_id field for new items | +| HBOX_OPTIONS_HEADER_SSO_ENABLED | false | allow login via trusted SSO HTTP headers | +| HBOX_OPTIONS_HEADER_SSO_ALLOWED_IP | | request IP being allowed to send trusted SSO HTTP headers | | HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB | | HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker | | HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, in you're using docker do not change this | @@ -87,6 +89,8 @@ volumes: --debug-port/$HBOX_DEBUG_PORT (default: 4000) --options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION (default: true) --options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID (default: true) + --options-header-sso-enabled/$HBOX_OPTIONS_HEADER_SSO_ENABLED (default: false) + --options-header-sso-allowed_ip/$HBOX_OPTIONS_HEADER_SSO_ALLOWED_IP --help/-h display this help message ```