From 9080170d6defa4545469653de6e71716ae087df4 Mon Sep 17 00:00:00 2001 From: Michiel Mak Date: Tue, 11 Aug 2026 18:06:21 +0200 Subject: [PATCH] Add GOTIFY_LOCALAUTH_ENABLED to disable local authentication Adds a config option to turn off the built-in username/password authentication, so a Gotify instance can rely on OIDC only. When GOTIFY_LOCALAUTH_ENABLED is false: - username/password credentials are no longer accepted for HTTP basic auth on any endpoint, so local credentials cannot be used to bypass the disabled login, - POST /auth/local/login responds with 403, - the default admin user is not created on startup, - the WebUI hides the login form and the register button. Client tokens, application tokens and OIDC sessions are unaffected. The option defaults to true, so existing installations do not change. Gotify refuses to start when both local authentication and OIDC are disabled, because no way to authenticate would remain. Closes #1007 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- app.go | 2 +- auth/authentication.go | 6 +++ auth/authentication_test.go | 47 +++++++++++++++++- config/config.go | 15 ++++++ config/config_test.go | 36 ++++++++++++++ config/keys.go | 1 + database/database_test.go | 15 ++++++ docs/spec.json | 9 +++- gotify-server.env.example | 11 +++++ model/gotifyinfo.go | 5 ++ router/router.go | 20 +++++--- router/router_test.go | 99 ++++++++++++++++++++++++++++++++++--- ui/serve.go | 11 +++-- ui/src/config.ts | 2 + ui/src/user/Login.tsx | 86 +++++++++++++++++--------------- 15 files changed, 303 insertions(+), 62 deletions(-) diff --git a/app.go b/app.go index 3935ffc6d..316be895c 100644 --- a/app.go +++ b/app.go @@ -106,7 +106,7 @@ func serve(vInfo *model.VersionInfo) int { return 1 } - db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass, conf.PassStrength, true, time.Now) + db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass, conf.PassStrength, conf.LocalAuth.Enabled, time.Now) if err != nil { log.Error().Err(err).Msg("Cannot initialize database") return 1 diff --git a/auth/authentication.go b/auth/authentication.go index 6d8338f92..d9fe981ca 100644 --- a/auth/authentication.go +++ b/auth/authentication.go @@ -42,6 +42,9 @@ type Auth struct { DB Database SecureCookie bool CrossOrigin *http.CrossOriginProtection + // LocalAuthEnabled controls whether username/password (basic auth) credentials + // are accepted. When false, only token based auth and OIDC sessions work. + LocalAuthEnabled bool } // RequireAdmin requires an elevated client token or basic auth, the user must be an admin. @@ -146,6 +149,9 @@ func (a *Auth) rejectForeignOrigin(ctx *gin.Context) bool { func (a *Auth) handleUser(checks ...func(*model.User) (authState, error)) func(ctx *gin.Context) (authState, error) { return func(ctx *gin.Context) (authState, error) { + if !a.LocalAuthEnabled { + return authStateSkip, nil + } if name, pass, ok := ctx.Request.BasicAuth(); ok { if user, err := a.DB.GetUserByName(name); err != nil { return authStateSkip, err diff --git a/auth/authentication_test.go b/auth/authentication_test.go index d92ecf10f..a7e951967 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -29,7 +29,7 @@ type AuthenticationSuite struct { func (s *AuthenticationSuite) SetupSuite() { mode.Set(mode.TestDev) s.DB = testdb.NewDB(s.T()) - s.auth = &Auth{DB: s.DB, CrossOrigin: http.NewCrossOriginProtection()} + s.auth = &Auth{DB: s.DB, CrossOrigin: http.NewCrossOriginProtection(), LocalAuthEnabled: true} now := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) timeNow = func() time.Time { return now } @@ -416,3 +416,48 @@ func (s *AuthenticationSuite) assertCsrfRequest(headers map[string]string, cooki } type fMiddleware gin.HandlerFunc + +func (s *AuthenticationSuite) TestLocalAuthDisabledRejectsBasicAuth() { + disabled := &Auth{DB: s.DB, CrossOrigin: http.NewCrossOriginProtection(), LocalAuthEnabled: false} + + // Valid local credentials must not authenticate anywhere, otherwise disabling + // local auth could be bypassed with basic auth. + s.assertBasicAuthRequest("admin", "pw", disabled.RequireClient, 401) + s.assertBasicAuthRequest("admin", "pw", disabled.RequireAdmin, 401) + s.assertBasicAuthRequest("admin", "pw", disabled.RequireElevatedClient, 401) + s.assertBasicAuthRequest("admin", "pw", disabled.RequireApplicationOrClient, 401) + s.assertBasicAuthRequest("existing", "pw", disabled.RequireClient, 401) + + // Optional auth must not register the user either. + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest("GET", "/", nil) + ctx.Request.SetBasicAuth("admin", "pw") + disabled.Optional(ctx) + assert.Nil(s.T(), ctx.Keys["user"]) + + // Token based auth keeps working. + s.assertHeaderRequestWith("X-Gotify-Key", "clienttoken", disabled.RequireClient, 200) + s.assertHeaderRequestWith("X-Gotify-Key", "apptoken", disabled.RequireApplicationToken, 200) + + // With local auth enabled the same credentials still work. + s.assertBasicAuthRequest("admin", "pw", s.auth.RequireAdmin, 200) +} + +func (s *AuthenticationSuite) assertBasicAuthRequest(user, pass string, f fMiddleware, code int) { + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest("GET", "/", nil) + ctx.Request.SetBasicAuth(user, pass) + f(ctx) + assert.Equal(s.T(), code, recorder.Code) +} + +func (s *AuthenticationSuite) assertHeaderRequestWith(key, value string, f fMiddleware, code int) { + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest("GET", "/", nil) + ctx.Request.Header.Set(key, value) + f(ctx) + assert.Equal(s.T(), code, recorder.Code) +} diff --git a/config/config.go b/config/config.go index 71a9963c6..6362b5ccf 100644 --- a/config/config.go +++ b/config/config.go @@ -70,6 +70,11 @@ type OIDC struct { Scopes []string } +// LocalAuth configures the built-in username/password authentication. +type LocalAuth struct { + Enabled bool +} + type Configuration struct { LogLevel LogLevel Server Server @@ -80,6 +85,7 @@ type Configuration struct { PluginsDir string Registration bool OIDC OIDC + LocalAuth LocalAuth NoColor string } @@ -116,6 +122,9 @@ func Get() (*Configuration, []FutureLog) { AutoRegister: true, Scopes: []string{"openid", "profile", "email"}, }, + LocalAuth: LocalAuth{ + Enabled: true, + }, } logs := loadFiles() @@ -178,10 +187,16 @@ func Get() (*Configuration, []FutureLog) { add(parseBool(&c.OIDC.LinkByUsername, EnvOIDCLinkByUsername)) add(parseList(&c.OIDC.Scopes, EnvOIDCScopes)) + add(parseBool(&c.LocalAuth.Enabled, EnvLocalAuthEnabled)) + add(parseString(&c.NoColor, EnvNoColor)) addTrailingSlashToPaths(c) + if !c.LocalAuth.Enabled && !c.OIDC.Enabled { + logs = append(logs, futureFatal(EnvLocalAuthEnabled+" is false and "+EnvOIDCEnabled+" is false, there would be no way to authenticate. Enable OIDC or re-enable local authentication.")) + } + return c, logs } diff --git a/config/config_test.go b/config/config_test.go index 216d80789..d6d38f7bf 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -6,9 +6,45 @@ import ( "testing" "github.com/gotify/server/v2/mode" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) +func TestLocalAuthEnabled(t *testing.T) { + mode.Set(mode.TestDev) + + conf, logs := Get() + assert.True(t, conf.LocalAuth.Enabled, "should default to true") + assert.Empty(t, fatalLogs(logs)) + + os.Setenv("GOTIFY_LOCALAUTH_ENABLED", "false") + defer os.Unsetenv("GOTIFY_LOCALAUTH_ENABLED") + + // localauth disabled without OIDC leaves no way to authenticate -> fatal. + conf, logs = Get() + assert.False(t, conf.LocalAuth.Enabled, "should parse env var") + assert.Len(t, fatalLogs(logs), 1, "should refuse to start without any auth method") + + // localauth disabled with OIDC enabled is a valid combination. + os.Setenv("GOTIFY_OIDC_ENABLED", "true") + defer os.Unsetenv("GOTIFY_OIDC_ENABLED") + + conf, logs = Get() + assert.False(t, conf.LocalAuth.Enabled) + assert.True(t, conf.OIDC.Enabled) + assert.Empty(t, fatalLogs(logs), "should be allowed when OIDC can authenticate users") +} + +func fatalLogs(logs []FutureLog) []FutureLog { + var fatal []FutureLog + for _, l := range logs { + if l.Level == zerolog.FatalLevel { + fatal = append(fatal, l) + } + } + return fatal +} + func TestConfigEnv(t *testing.T) { mode.Set(mode.TestDev) os.Setenv("GOTIFY_DEFAULTUSER_NAME", "jmattheis") diff --git a/config/keys.go b/config/keys.go index 6578fd13f..beef379fc 100644 --- a/config/keys.go +++ b/config/keys.go @@ -41,5 +41,6 @@ const ( EnvOIDCAutoRegister = "GOTIFY_OIDC_AUTOREGISTER" EnvOIDCLinkByUsername = "GOTIFY_OIDC_LINK_BY_USERNAME" EnvOIDCScopes = "GOTIFY_OIDC_SCOPES" + EnvLocalAuthEnabled = "GOTIFY_LOCALAUTH_ENABLED" EnvNoColor = "NOCOLOR" ) diff --git a/database/database_test.go b/database/database_test.go index 5212f9fbe..89d25bbf2 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -116,3 +116,18 @@ func TestMigrateSortKey(t *testing.T) { assert.Equal(t, apps[0].Name, "one-other") assert.Equal(t, apps[0].SortKey, "a0") } + +func TestNoDefaultUserWhenDisabled(t *testing.T) { + tmpDir := test.NewTmpDir("gotify_testnodefaultuser") + defer tmpDir.Clean() + + // Mirrors localauth being disabled: no default admin may be created, + // otherwise a password login account would exist that cannot be used. + db, err := New("sqlite3", tmpDir.Path("testdb.db"), "defaultUser", "defaultPass", 5, false, fixedNow) + assert.Nil(t, err) + defer db.Close() + + users, err := db.GetUsers() + assert.Nil(t, err) + assert.Empty(t, users, "no default user should be created") +} diff --git a/docs/spec.json b/docs/spec.json index a9f17989d..3f8226884 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -2940,9 +2940,16 @@ "required": [ "version", "register", - "oidc" + "oidc", + "localauth" ], "properties": { + "localauth": { + "description": "If local (username/password) authentication is enabled.", + "type": "boolean", + "x-go-name": "LocalAuth", + "example": true + }, "oidc": { "description": "If oidc is enabled.", "type": "boolean", diff --git a/gotify-server.env.example b/gotify-server.env.example index c0b23d557..439265059 100644 --- a/gotify-server.env.example +++ b/gotify-server.env.example @@ -224,6 +224,17 @@ # Type: text-list # GOTIFY_OIDC_SCOPES=openid,profile,email +# Enable the built-in local username/password authentication. When disabled, +# the local login endpoint is rejected, username/password credentials are no +# longer accepted via HTTP basic auth on the API, the login form is hidden in +# the WebUI and the default admin user is not created. +# +# Disabling this requires OIDC to be enabled (GOTIFY_OIDC_ENABLED=true), +# otherwise there would be no way to authenticate and Gotify refuses to start. +# +# Type: boolean +# GOTIFY_LOCALAUTH_ENABLED=true + # Database driver to use. For mysql and postgres the target database must # already exist and the configured user must have sufficient permissions. # diff --git a/model/gotifyinfo.go b/model/gotifyinfo.go index c2db0bd2e..726a4f0f1 100644 --- a/model/gotifyinfo.go +++ b/model/gotifyinfo.go @@ -19,4 +19,9 @@ type GotifyInfo struct { // required: true // example: true Oidc bool `json:"oidc"` + // If local (username/password) authentication is enabled. + // + // required: true + // example: true + LocalAuth bool `json:"localauth"` } diff --git a/router/router.go b/router/router.go index cf4d8da46..1cebfddac 100644 --- a/router/router.go +++ b/router/router.go @@ -1,6 +1,7 @@ package router import ( + "errors" "fmt" "net/http" "path/filepath" @@ -85,9 +86,10 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co } }() authentication := auth.Auth{ - DB: db, - SecureCookie: conf.Server.SecureCookie, - CrossOrigin: http.NewCrossOriginProtection(), + DB: db, + SecureCookie: conf.Server.SecureCookie, + CrossOrigin: http.NewCrossOriginProtection(), + LocalAuthEnabled: conf.LocalAuth.Enabled, } messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db} healthHandler := api.HealthAPI{DB: db} @@ -118,7 +120,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co userChangeNotifier.OnUserDeleted(pluginManager.RemoveUser) userChangeNotifier.OnUserAdded(pluginManager.InitializeForUserID) - ui.Register(g, *vInfo, conf.Registration, conf.OIDC.Enabled) + ui.Register(g, *vInfo, conf.Registration, conf.OIDC.Enabled, conf.LocalAuth.Enabled) if conf.OIDC.Enabled { oidcHandler := api.NewOIDC(conf, db, userChangeNotifier) @@ -158,7 +160,13 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co g.Group("/user").Use(authentication.Optional).POST("", userHandler.CreateUser) - g.POST("/auth/local/login", sessionHandler.Login) + login := sessionHandler.Login + if !conf.LocalAuth.Enabled { + login = func(ctx *gin.Context) { + ctx.AbortWithError(http.StatusForbidden, errors.New("local authentication is disabled")) + } + } + g.POST("/auth/local/login", login) g.OPTIONS("/*any") @@ -189,7 +197,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co // schema: // $ref: "#/definitions/GotifyInfo" g.GET("gotifyinfo", func(ctx *gin.Context) { - ctx.JSON(200, &model.GotifyInfo{Version: vInfo.Version, Oidc: conf.OIDC.Enabled, Register: conf.Registration}) + ctx.JSON(200, &model.GotifyInfo{Version: vInfo.Version, Oidc: conf.OIDC.Enabled, Register: conf.Registration, LocalAuth: conf.LocalAuth.Enabled}) }) g.Group("/").Use(authentication.RequireApplicationOrClient).POST("/message", messageHandler.CreateMessage) diff --git a/router/router_test.go b/router/router_test.go index f72e95a8f..efd6b16b0 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -41,7 +41,7 @@ func (s *IntegrationSuite) BeforeTest(string, string) { g, closable := Create(s.db.GormDatabase, &model.VersionInfo{Version: "1.0.0", BuildDate: "2018-02-20-17:30:47", Commit: "asdasds"}, - &config.Configuration{PassStrength: 5}, + &config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}}, ) s.closable = closable s.server = httptest.NewServer(g) @@ -73,7 +73,7 @@ func TestHeadersFromConfiguration(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.ResponseHeaders = map[string]string{ "New-Cool-Header": "Nice", "Access-Control-Allow-Origin": "http://test1.com", @@ -105,7 +105,7 @@ func TestHeadersFromCORSConfig(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.Cors.AllowOrigins = []string{"---", "http://test.com"} g, closable := Create(db.GormDatabase, @@ -134,7 +134,7 @@ func TestInvalidOrigin(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.Cors.AllowOrigins = []string{"---", "http://test.com"} g, closable := Create(db.GormDatabase, @@ -163,7 +163,7 @@ func TestAllowedOriginFromResponseHeaders(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.ResponseHeaders = map[string]string{ "Access-Control-Allow-Origin": "http://test1.com", "Access-Control-Allow-Methods": "GET,POST", @@ -201,7 +201,7 @@ func TestAllowedWildcardOriginInHeader(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.ResponseHeaders = map[string]string{ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST", @@ -233,7 +233,7 @@ func TestCORSHeaderRegex(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.Cors.AllowOrigins = []string{"---", "^http://test\\d{3}.com$"} g, closable := Create(db.GormDatabase, @@ -263,7 +263,7 @@ func TestCORSConfigOverride(t *testing.T) { db := testdb.NewDBWithDefaultUser(t) defer db.Close() - config := config.Configuration{PassStrength: 5} + config := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} config.Server.ResponseHeaders = map[string]string{ "New-Cool-Header": "Nice", "Access-Control-Allow-Origin": "http://example.com/", @@ -390,6 +390,89 @@ func (s *IntegrationSuite) TestAuthentication() { assert.Equal(s.T(), "android-client", token.Name) } +func TestLocalAuthDisabled(t *testing.T) { + mode.Set(mode.TestDev) + db := testdb.NewDBWithDefaultUser(t) + defer db.Close() + + conf := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} + conf.LocalAuth.Enabled = false + + g, closable := Create(db.GormDatabase, + &model.VersionInfo{Version: "1.0.0", BuildDate: "2018-02-20-17:30:47", Commit: "asdasds"}, + &conf, + ) + server := httptest.NewServer(g) + defer func() { + closable() + server.Close() + }() + + do := func(method, path string, basicAuth bool) *http.Response { + req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", server.URL, path), strings.NewReader(`{"name":"test"}`)) + assert.Nil(t, err) + req.Header.Add("Content-Type", "application/json") + if basicAuth { + req.SetBasicAuth("admin", "pw") + } + res, err := client.Do(req) + assert.Nil(t, err) + return res + } + + assert.Equal(t, http.StatusForbidden, do("POST", "auth/local/login", true).StatusCode, + "local login endpoint must be rejected") + + // Basic auth with valid local credentials must not work anywhere, otherwise + // local login could be bypassed via the regular API. + assert.Equal(t, http.StatusUnauthorized, do("GET", "current/user", true).StatusCode, + "basic auth must be rejected on client endpoints") + assert.Equal(t, http.StatusUnauthorized, do("GET", "application", true).StatusCode, + "basic auth must be rejected on application endpoints") + assert.Equal(t, http.StatusUnauthorized, do("GET", "user", true).StatusCode, + "basic auth must be rejected on admin endpoints") + + res := do("GET", "gotifyinfo", false) + info := &model.GotifyInfo{} + json.NewDecoder(res.Body).Decode(info) + assert.False(t, info.LocalAuth, "gotifyinfo should report localauth as disabled") +} + +func TestLocalAuthEnabledByDefaultKeepsBasicAuth(t *testing.T) { + mode.Set(mode.TestDev) + db := testdb.NewDBWithDefaultUser(t) + defer db.Close() + + conf := config.Configuration{PassStrength: 5, LocalAuth: config.LocalAuth{Enabled: true}} + conf.LocalAuth.Enabled = true + + g, closable := Create(db.GormDatabase, + &model.VersionInfo{Version: "1.0.0", BuildDate: "2018-02-20-17:30:47", Commit: "asdasds"}, + &conf, + ) + server := httptest.NewServer(g) + defer func() { + closable() + server.Close() + }() + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/auth/local/login", server.URL), strings.NewReader(`{"name":"test"}`)) + assert.Nil(t, err) + req.Header.Add("Content-Type", "application/json") + req.SetBasicAuth("admin", "pw") + res, err := client.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + + req, err = http.NewRequest("GET", fmt.Sprintf("%s/gotifyinfo", server.URL), nil) + assert.Nil(t, err) + res, err = client.Do(req) + assert.Nil(t, err) + info := &model.GotifyInfo{} + json.NewDecoder(res.Body).Decode(info) + assert.True(t, info.LocalAuth, "gotifyinfo should report localauth as enabled") +} + func (s *IntegrationSuite) newRequest(method, url, body string) *http.Request { req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", s.server.URL, url), strings.NewReader(body)) req.Header.Add("Content-Type", "application/json") diff --git a/ui/serve.go b/ui/serve.go index 45e46f441..08ba06fac 100644 --- a/ui/serve.go +++ b/ui/serve.go @@ -16,14 +16,15 @@ import ( var box embed.FS type uiConfig struct { - Register bool `json:"register"` - Version model.VersionInfo `json:"version"` - OIDC bool `json:"oidc"` + Register bool `json:"register"` + Version model.VersionInfo `json:"version"` + OIDC bool `json:"oidc"` + LocalAuth bool `json:"localauth"` } // Register registers the ui on the root path. -func Register(r *gin.Engine, version model.VersionInfo, register, oidcEnabled bool) { - uiConfigBytes, err := json.Marshal(uiConfig{Version: version, Register: register, OIDC: oidcEnabled}) +func Register(r *gin.Engine, version model.VersionInfo, register, oidcEnabled, localAuth bool) { + uiConfigBytes, err := json.Marshal(uiConfig{Version: version, Register: register, OIDC: oidcEnabled, LocalAuth: localAuth}) if err != nil { panic(err) } diff --git a/ui/src/config.ts b/ui/src/config.ts index 00981ded9..36ed7aacb 100644 --- a/ui/src/config.ts +++ b/ui/src/config.ts @@ -5,6 +5,7 @@ export interface IConfig { register: boolean; version: IVersion; oidc: boolean; + localauth: boolean; } declare global { @@ -18,6 +19,7 @@ const config: IConfig = { register: false, version: {commit: 'unknown', buildDate: 'unknown', version: 'unknown'}, oidc: false, + localauth: true, ...window.config, }; diff --git a/ui/src/user/Login.tsx b/ui/src/user/Login.tsx index 77f93750b..9ad86e068 100644 --- a/ui/src/user/Login.tsx +++ b/ui/src/user/Login.tsx @@ -23,7 +23,7 @@ const Login = observer(() => { } }, [currentUser.loggedIn]); const registerButton = () => { - if (config.get('register')) + if (config.get('localauth') && config.get('register')) return ( - + {localAuth && ( +
e.preventDefault()} id="login-form"> + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + )} {config.get('oidc') && ( <> - or + {localAuth && ( + or + )}