From 10d2a7d52fd6c59a1d3628ef5b0025728c053788 Mon Sep 17 00:00:00 2001 From: pat-s Date: Fri, 14 Aug 2026 13:10:38 +0000 Subject: [PATCH 1/4] feat(auth): add SSO-only login mode --- cmd/config.go | 20 +++++++++ cmd/config_test.go | 41 +++++++++++++++++++ cmd/login.go | 8 ++++ config.sample.toml | 6 +++ .../main/src/views/auth/UserLoginView.vue | 8 +++- 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 cmd/config_test.go diff --git a/cmd/config.go b/cmd/config.go index 8a04a219..e354ab9d 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -4,9 +4,28 @@ import ( "encoding/json" "github.com/abhinavxd/libredesk/internal/envelope" + oidcmodels "github.com/abhinavxd/libredesk/internal/oidc/models" "github.com/zerodha/fastglue" ) +const localLoginEnabledKey = "auth.local_login_enabled" + +// isLocalLoginEnabled keeps local login available by default and whenever no +// OIDC provider is enabled, preventing an administrator from locking everyone out. +func isLocalLoginEnabled(providers []oidcmodels.OIDC) bool { + if !ko.Exists(localLoginEnabledKey) || ko.Bool(localLoginEnabledKey) { + return true + } + + for _, provider := range providers { + if provider.Enabled { + return false + } + } + + return true +} + // handleGetConfig returns the public configuration needed for app initialization, this includes minimal app settings and enabled SSO providers (without secrets). func handleGetConfig(r *fastglue.Request) error { var app = r.Context.(*App) @@ -58,6 +77,7 @@ func handleGetConfig(r *fastglue.Request) error { // Add SSO providers to the response publicSettings["app.sso_providers"] = enabledProviders + publicSettings["app.local_login_enabled"] = isLocalLoginEnabled(oidcProviders) return r.SendEnvelope(publicSettings) } diff --git a/cmd/config_test.go b/cmd/config_test.go new file mode 100644 index 00000000..fcca43fa --- /dev/null +++ b/cmd/config_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "testing" + + oidcmodels "github.com/abhinavxd/libredesk/internal/oidc/models" +) + +func TestIsLocalLoginEnabled(t *testing.T) { + tests := []struct { + name string + configured *bool + providers []oidcmodels.OIDC + want bool + }{ + {name: "defaults to enabled", want: true}, + {name: "remains enabled when configured", configured: boolPointer(true), want: true}, + {name: "remains enabled without an enabled OIDC provider", configured: boolPointer(false), providers: []oidcmodels.OIDC{{Enabled: false}}, want: true}, + {name: "is disabled with an enabled OIDC provider", configured: boolPointer(false), providers: []oidcmodels.OIDC{{Enabled: true}}, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ko.Delete(localLoginEnabledKey) + if tt.configured != nil { + if err := ko.Set(localLoginEnabledKey, *tt.configured); err != nil { + t.Fatalf("set config: %v", err) + } + } + t.Cleanup(func() { ko.Delete(localLoginEnabledKey) }) + + if got := isLocalLoginEnabled(tt.providers); got != tt.want { + t.Fatalf("isLocalLoginEnabled() = %v, want %v", got, tt.want) + } + }) + } +} + +func boolPointer(value bool) *bool { + return &value +} diff --git a/cmd/login.go b/cmd/login.go index 2cf3968e..c20ab830 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -21,6 +21,14 @@ func handleLogin(r *fastglue.Request) error { loginReq loginRequest ) + oidcProviders, err := app.oidc.GetAll() + if err != nil { + return sendErrorEnvelope(r, err) + } + if !isLocalLoginEnabled(oidcProviders) { + return r.SendErrorEnvelope(fasthttp.StatusForbidden, "Local login is disabled. Sign in with an SSO provider.", nil, envelope.PermissionError) + } + // Decode JSON request. if err := r.Decode(&loginReq, "json"); err != nil { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("errors.parsingRequest"), nil, envelope.InputError) diff --git a/config.sample.toml b/config.sample.toml index 713c01a8..b39aa2ff 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -34,6 +34,12 @@ read_buffer_size = 65536 # Keepalive settings. keepalive_timeout = "10s" +[auth] +# Allow users to sign in with their LibreDesk email and password. +# Disable this only after configuring and enabling at least one OIDC provider. +# LibreDesk automatically keeps local login available when no OIDC provider is enabled to prevent lockout. +local_login_enabled = true + # File upload provider to use, either `fs` or `s3`. [upload] provider = "fs" diff --git a/frontend/apps/main/src/views/auth/UserLoginView.vue b/frontend/apps/main/src/views/auth/UserLoginView.vue index 3cf65fd1..5ca74ff1 100644 --- a/frontend/apps/main/src/views/auth/UserLoginView.vue +++ b/frontend/apps/main/src/views/auth/UserLoginView.vue @@ -32,7 +32,7 @@ {{ oidcProvider.name }} -
+
@@ -42,7 +42,7 @@
-
+