From 1165c91fccc898ad07bf4953e36e9556e8bcb7d6 Mon Sep 17 00:00:00 2001 From: wwhsaber <120183605@qq.com> Date: Thu, 16 Jul 2026 19:23:07 +0800 Subject: [PATCH 1/4] fix: abstain when OAuth whitelist is empty (#1010) Co-authored-by: wwhsaber --- internal/service/access_controls_rules.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/service/access_controls_rules.go b/internal/service/access_controls_rules.go index f0597a31..e506db12 100644 --- a/internal/service/access_controls_rules.go +++ b/internal/service/access_controls_rules.go @@ -43,6 +43,11 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect { rule.Log.App.Debug().Msg("User is an OAuth user, checking OAuth whitelist") match, err := utils.CheckFilter(ctx.ACLs.OAuth.Whitelist, ctx.UserContext.OAuth.Email) if err != nil { + // Empty whitelist means "not configured" — let OAuth group rules decide. + if err == utils.ErrFilterEmpty { + rule.Log.App.Debug().Msg("OAuth whitelist is empty, abstaining") + return EffectAbstain + } rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.OAuth.Email).Msg("Invalid entry in OAuth whitelist") return EffectDeny } From 589fe22138a7151f9732b3d8e3bc9f12b9f27191 Mon Sep 17 00:00:00 2001 From: Stavros Date: Thu, 16 Jul 2026 14:30:48 +0300 Subject: [PATCH 2/4] tests: add tests for abstain in oauth whitelist --- internal/service/access_controls_rules.go | 6 +++--- internal/service/access_controls_rules_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/service/access_controls_rules.go b/internal/service/access_controls_rules.go index e506db12..318894d2 100644 --- a/internal/service/access_controls_rules.go +++ b/internal/service/access_controls_rules.go @@ -1,6 +1,7 @@ package service import ( + "errors" "regexp" "strings" @@ -43,8 +44,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect { rule.Log.App.Debug().Msg("User is an OAuth user, checking OAuth whitelist") match, err := utils.CheckFilter(ctx.ACLs.OAuth.Whitelist, ctx.UserContext.OAuth.Email) if err != nil { - // Empty whitelist means "not configured" — let OAuth group rules decide. - if err == utils.ErrFilterEmpty { + if errors.Is(err, utils.ErrFilterEmpty) { rule.Log.App.Debug().Msg("OAuth whitelist is empty, abstaining") return EffectAbstain } @@ -77,7 +77,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect { match, err := utils.CheckFilter(ctx.ACLs.Users.Allow, ctx.UserContext.GetUsername()) if err != nil { - if err == utils.ErrFilterEmpty { + if errors.Is(err, utils.ErrFilterEmpty) { return EffectAbstain } rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users allow list") diff --git a/internal/service/access_controls_rules_test.go b/internal/service/access_controls_rules_test.go index f3371801..7cb0f8ba 100644 --- a/internal/service/access_controls_rules_test.go +++ b/internal/service/access_controls_rules_test.go @@ -44,6 +44,21 @@ func TestUserAllowedRule(t *testing.T) { }, expected: EffectAbstain, }, + { + name: "abstains when filter is empty", + ctx: &ACLContext{ + ACLs: &model.App{ + OAuth: model.AppOAuth{Whitelist: ""}, + }, + UserContext: &model.UserContext{ + Provider: model.ProviderOAuth, + OAuth: &model.OAuthContext{ + BaseContext: model.BaseContext{Username: "alice"}, + }, + }, + }, + expected: EffectAbstain, + }, { name: "allows OAuth user when email matches whitelist", ctx: &ACLContext{ From 3cf3cc9090ba24af29c37be9f1a02810d7ad0988 Mon Sep 17 00:00:00 2001 From: Stavros Date: Thu, 16 Jul 2026 14:34:12 +0300 Subject: [PATCH 3/4] fix: don't fail app on label provider init failure --- internal/bootstrap/service_bootstrap.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/bootstrap/service_bootstrap.go b/internal/bootstrap/service_bootstrap.go index 11dbb693..0e61c5fb 100644 --- a/internal/bootstrap/service_bootstrap.go +++ b/internal/bootstrap/service_bootstrap.go @@ -85,7 +85,8 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) { err := app.dig.Provide(service.NewKubernetesService) if err != nil { - return nil, fmt.Errorf("failed to provide kubernetes service: %w", err) + app.log.App.Warn().Err(err).Msg("Failed to provide kubernetes service") + return nil, nil } err = app.dig.Invoke(func(k *service.KubernetesService) error { @@ -94,7 +95,8 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) { }) if err != nil { - return nil, fmt.Errorf("failed to invoke kubernetes service: %w", err) + app.log.App.Warn().Err(err).Msg("Failed to invoke kubernetes service") + return nil, nil } // Kubernetes will fail to initialize with an error if it cannot connect to the cluster @@ -114,7 +116,8 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) { err := app.dig.Provide(service.NewDockerService) if err != nil { - return nil, fmt.Errorf("failed to provide docker service: %w", err) + app.log.App.Warn().Err(err).Msg("Failed to provide docker service") + return nil, nil } err = app.dig.Invoke(func(d *service.DockerService) error { @@ -123,7 +126,8 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) { }) if err != nil { - return nil, fmt.Errorf("failed to invoke docker service: %w", err) + app.log.App.Warn().Err(err).Msg("Failed to invoke docker service") + return nil, nil } if app.services.dockerService == nil { From 04a06ec5ff46a3b4015dc7b98e6f00c027a722a6 Mon Sep 17 00:00:00 2001 From: Stavros Date: Thu, 16 Jul 2026 17:13:36 +0300 Subject: [PATCH 4/4] chore: use blue color for totp generate --- cmd/tinyauth/generate_totp.go | 4 ++-- cmd/tinyauth/tinyauth.go | 10 +++++----- internal/model/config.go | 35 ++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/cmd/tinyauth/generate_totp.go b/cmd/tinyauth/generate_totp.go index 696af69a..1cc7df80 100644 --- a/cmd/tinyauth/generate_totp.go +++ b/cmd/tinyauth/generate_totp.go @@ -116,11 +116,11 @@ func generateTotpCmd() *cli.Command { userStr := fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret) fmt.Print("\nOr add the following TOTP secret to your authenticator app: ") - fmt.Print(colors.green.Render(secret)) + fmt.Print(colors.blue.Render(secret)) fmt.Print("\n\n") fmt.Printf("Finally, add your user '%s' back to your configuration: ", user.Username) - fmt.Print(colors.green.Render(userStr)) + fmt.Print(colors.blue.Render(userStr)) fmt.Print("\n") return nil diff --git a/cmd/tinyauth/tinyauth.go b/cmd/tinyauth/tinyauth.go index a39e49c2..b5720b0c 100644 --- a/cmd/tinyauth/tinyauth.go +++ b/cmd/tinyauth/tinyauth.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "reflect" "strings" "charm.land/huh/v2" @@ -32,10 +31,11 @@ func main() { Configuration: tConfig, Resources: loaders, Run: func(_ []string) error { - if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) { - colors := getColors() - fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.") - } + // enable this on experimental features + //if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) { + // colors := getColors() + // fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.") + //} return runCmd(*tConfig) }, } diff --git a/internal/model/config.go b/internal/model/config.go index c253d2f6..79e79788 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -101,22 +101,23 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config { } type Config struct { - AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl,omitempty"` - ConfigFile string `description:"Path to config file." yaml:"-" gen:"include"` - LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider,omitempty"` - Database DatabaseConfig `description:"Database configuration." yaml:"database,omitempty"` - Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics,omitempty"` - Resources ResourcesConfig `description:"Resources configuration." yaml:"resources,omitempty"` - Server ServerConfig `description:"Server configuration." yaml:"server,omitempty"` - Auth AuthConfig `description:"Authentication configuration." yaml:"auth,omitempty"` - Apps map[string]App `description:"Application ACLs configuration." yaml:"apps,omitempty"` - OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth,omitempty"` - OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc,omitempty"` - UI UIConfig `description:"UI customization." yaml:"ui,omitempty"` - LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"` - Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"` - Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale,omitempty"` - Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"` + AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl,omitempty"` + ConfigFile string `description:"Path to config file." yaml:"-" gen:"include"` + LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider,omitempty"` + Database DatabaseConfig `description:"Database configuration." yaml:"database,omitempty"` + Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics,omitempty"` + Resources ResourcesConfig `description:"Resources configuration." yaml:"resources,omitempty"` + Server ServerConfig `description:"Server configuration." yaml:"server,omitempty"` + Auth AuthConfig `description:"Authentication configuration." yaml:"auth,omitempty"` + Apps map[string]App `description:"Application ACLs configuration." yaml:"apps,omitempty"` + OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth,omitempty"` + OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc,omitempty"` + UI UIConfig `description:"UI customization." yaml:"ui,omitempty"` + LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"` + // enable the cli warning on experimental features + //Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"` + Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale,omitempty"` + Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"` } type DatabaseConfig struct { @@ -237,7 +238,7 @@ type LogStreamConfig struct { Level string `description:"Log level for this stream. Use global if empty." yaml:"level,omitempty"` } -type ExperimentalConfig struct{} +//type ExperimentalConfig struct{} type TailscaleConfig struct { Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`