diff --git a/.env.example b/.env.example index 5d7509684..6b02b52a4 100644 --- a/.env.example +++ b/.env.example @@ -101,8 +101,6 @@ TINYAUTH_AUTH_SESSIONMAXLIFETIME=0 TINYAUTH_AUTH_LOGINTIMEOUT=300 # Maximum login retries. TINYAUTH_AUTH_LOGINMAXRETRIES=3 -# Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically. -TINYAUTH_AUTH_LOCKDOWNENABLED=true # Comma-separated list of trusted proxy addresses. TINYAUTH_AUTH_TRUSTEDPROXIES= # ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow. diff --git a/README.md b/README.md index 780e6bf32..1f9906371 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,3 @@ for their support for Tinyauth. - **Freepik** for providing the police hat and badge. - **Renee French** for the original gopher logo. - **Syrhu** for providing the background image of the app. - -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=tinyauthapp/tinyauth&type=Date)](https://www.star-history.com/#tinyauthapp/tinyauth&Date) diff --git a/assets/screenshot.png b/assets/screenshot.png index 62a96bf80..9470b067e 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/internal/bootstrap/router_bootstrap.go b/internal/bootstrap/router_bootstrap.go index 495b693df..ae82c2d36 100644 --- a/internal/bootstrap/router_bootstrap.go +++ b/internal/bootstrap/router_bootstrap.go @@ -30,6 +30,16 @@ func (app *BootstrapApp) setupRouter() error { if err != nil { return fmt.Errorf("failed to set trusted proxies: %w", err) } + + app.runtime.TrustedProxiesConfigured = true + } else { + err := engine.SetTrustedProxies(nil) + + if err != nil { + return fmt.Errorf("failed to set trusted proxies: %w", err) + } + + app.log.App.Warn().Msg("Trusted proxies are not configured, IP access controls will NOT work") } middlewareProvideFor := []any{ diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index ffafaffd5..cc398d1df 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -111,9 +111,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { clientIP := c.ClientIP() aclsCtx := &service.ACLContext{ - ACLs: acls, - IP: net.ParseIP(clientIP), - Path: proxyCtx.Path, + ACLs: acls, + IP: net.ParseIP(clientIP), + Path: proxyCtx.Path, + TrustedProxiesConfigured: controller.runtime.TrustedProxiesConfigured, } if controller.policyEngine.Evaluate(service.RuleIPBypassed, aclsCtx) { diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index 00c3fbaf4..73a78b8cc 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -72,27 +72,12 @@ func (controller *UserController) loginHandler(c *gin.Context) { controller.log.App.Debug().Str("username", req.Username).Msg("Login attempt") - isLocked, remaining := controller.auth.IsAccountLocked(req.Username) - - if isLocked { - controller.log.App.Warn().Str("username", req.Username).Msg("Account is locked due to too many failed login attempts") - controller.log.AuditLoginFailure(req.Username, "local", c.ClientIP(), "account locked") - c.Writer.Header().Add("x-tinyauth-lock-locked", "true") - c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339)) - c.JSON(429, gin.H{ - "status": 429, - "message": fmt.Sprintf("Too many failed login attempts. Try again in %d seconds", remaining), - }) - return - } - search, err := controller.auth.SearchUser(req.Username) if err != nil { if errors.Is(err, service.ErrUserNotFound) { controller.auth.DummyPasswordCheck(req.Password) controller.log.App.Warn().Str("username", req.Username).Msg("User not found during login attempt") - controller.auth.RecordLoginAttempt(req.Username, false) controller.log.AuditLoginFailure(req.Username, "unknown", c.ClientIP(), "user not found") c.JSON(401, gin.H{ "status": 401, @@ -108,14 +93,24 @@ func (controller *UserController) loginHandler(c *gin.Context) { return } + isLocked, remaining := controller.auth.IsAccountLocked(req.Username) + + if isLocked { + controller.log.App.Warn().Str("username", req.Username).Msg("Account is locked due to too many failed login attempts") + controller.log.AuditLoginFailure(req.Username, search.Type.String(), c.ClientIP(), "account locked") + c.Writer.Header().Add("x-tinyauth-lock-locked", "true") + c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339)) + c.JSON(429, gin.H{ + "status": 429, + "message": fmt.Sprintf("Too many failed login attempts. Try again in %d seconds", remaining), + }) + return + } + if err := controller.auth.CheckUserPassword(*search, req.Password); err != nil { controller.log.App.Warn().Str("username", req.Username).Msg("Invalid password during login attempt") controller.auth.RecordLoginAttempt(req.Username, false) - if search.Type == model.UserLocal { - controller.log.AuditLoginFailure(req.Username, "local", c.ClientIP(), "invalid password") - } else { - controller.log.AuditLoginFailure(req.Username, "ldap", c.ClientIP(), "invalid password") - } + controller.log.AuditLoginFailure(req.Username, search.Type.String(), c.ClientIP(), "invalid password") c.JSON(401, gin.H{ "status": 401, "message": "Unauthorized", @@ -216,11 +211,7 @@ func (controller *UserController) loginHandler(c *gin.Context) { controller.log.App.Info().Str("username", req.Username).Msg("Login successful") - if search.Type == model.UserLocal { - controller.log.AuditLoginSuccess(req.Username, "local", c.ClientIP()) - } else { - controller.log.AuditLoginSuccess(req.Username, "ldap", c.ClientIP()) - } + controller.log.AuditLoginSuccess(req.Username, search.Type.String(), c.ClientIP()) controller.auth.RecordLoginAttempt(req.Username, true) diff --git a/internal/model/config.go b/internal/model/config.go index c1a611566..c253d2f64 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -49,7 +49,6 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config { ACLs: ACLsConfig{ Policy: "allow", }, - LockdownEnabled: true, }, UI: UIConfig{ Title: "Tinyauth", @@ -151,7 +150,6 @@ type AuthConfig struct { SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime,omitempty"` LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout,omitempty"` LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries,omitempty"` - LockdownEnabled bool `description:"Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically." yaml:"lockdownEnabled,omitempty"` TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies,omitempty"` ACLs ACLsConfig `description:"ACLs configuration." yaml:"acls,omitempty"` } diff --git a/internal/model/runtime.go b/internal/model/runtime.go index e1c034d3b..920002655 100644 --- a/internal/model/runtime.go +++ b/internal/model/runtime.go @@ -1,17 +1,18 @@ package model type RuntimeConfig struct { - AppURL string - UUID string - CookieDomain string - SessionCookieName string - CSRFCookieName string - RedirectCookieName string - OAuthSessionCookieName string - LocalUsers []LocalUser - OAuthProviders map[string]OAuthServiceConfig - OAuthWhitelist []string - ConfiguredProviders []Provider + AppURL string + UUID string + CookieDomain string + SessionCookieName string + CSRFCookieName string + RedirectCookieName string + OAuthSessionCookieName string + LocalUsers []LocalUser + OAuthProviders map[string]OAuthServiceConfig + OAuthWhitelist []string + ConfiguredProviders []Provider + TrustedProxiesConfigured bool } type Provider struct { diff --git a/internal/model/users.go b/internal/model/users.go index 8dc9523dd..19560a4b0 100644 --- a/internal/model/users.go +++ b/internal/model/users.go @@ -7,6 +7,16 @@ const ( UserLDAP ) +func (t UserSearchType) String() string { + switch t { + case UserLocal: + return "local" + case UserLDAP: + return "ldap" + } + return "unknown" +} + type LDAPUser struct { DN string Groups []string diff --git a/internal/service/access_controls_rules.go b/internal/service/access_controls_rules.go index 98d2f1df9..f0597a317 100644 --- a/internal/service/access_controls_rules.go +++ b/internal/service/access_controls_rules.go @@ -215,6 +215,10 @@ type IPAllowedRule struct { } func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect { + if !ctx.TrustedProxiesConfigured { + return EffectAllow // We can't block the proxy + } + // merge global and per-app block/allow lists blockedIps := append([]string{}, rule.Config.Auth.IP.Block...) allowedIPs := append([]string{}, rule.Config.Auth.IP.Allow...) @@ -263,6 +267,10 @@ type IPBypassedRule struct { } func (rule *IPBypassedRule) Evaluate(ctx *ACLContext) Effect { + if !ctx.TrustedProxiesConfigured { + return EffectDeny + } + // merge global and per-app bypass lists bypassList := append([]string{}, rule.Config.Auth.IP.Bypass...) if ctx.ACLs != nil { diff --git a/internal/service/access_controls_rules_test.go b/internal/service/access_controls_rules_test.go index 2fc43f513..f3371801a 100644 --- a/internal/service/access_controls_rules_test.go +++ b/internal/service/access_controls_rules_test.go @@ -612,20 +612,30 @@ func TestIPAllowedRule(t *testing.T) { expected Effect }{ { - name: "allows when ACLs are nil and no global lists configured", + name: "when trusted proxies are not configured, IP is allowed", ctx: &ACLContext{ - ACLs: nil, + ACLs: &model.App{}, IP: net.ParseIP("10.0.0.1"), }, expected: EffectAllow, }, + { + name: "allows when ACLs are nil and no global lists configured", + ctx: &ACLContext{ + ACLs: nil, + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, + }, + expected: EffectAllow, + }, { name: "denies when IP matches app block list", ctx: &ACLContext{ ACLs: &model.App{ IP: model.AppIP{Block: []string{"10.0.0.1"}}, }, - IP: net.ParseIP("10.0.0.1"), + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -637,8 +647,9 @@ func TestIPAllowedRule(t *testing.T) { }, }, ctx: &ACLContext{ - ACLs: &model.App{}, - IP: net.ParseIP("10.0.0.5"), + ACLs: &model.App{}, + IP: net.ParseIP("10.0.0.5"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -648,7 +659,8 @@ func TestIPAllowedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Allow: []string{"192.168.1.0/24"}}, }, - IP: net.ParseIP("192.168.1.10"), + IP: net.ParseIP("192.168.1.10"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -660,8 +672,9 @@ func TestIPAllowedRule(t *testing.T) { }, }, ctx: &ACLContext{ - ACLs: &model.App{}, - IP: net.ParseIP("192.168.1.10"), + ACLs: &model.App{}, + IP: net.ParseIP("192.168.1.10"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -671,15 +684,17 @@ func TestIPAllowedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Allow: []string{"192.168.1.0/24"}}, }, - IP: net.ParseIP("10.0.0.1"), + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, { name: "allows when no block or allow lists are configured", ctx: &ACLContext{ - ACLs: &model.App{}, - IP: net.ParseIP("10.0.0.1"), + ACLs: &model.App{}, + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -692,7 +707,8 @@ func TestIPAllowedRule(t *testing.T) { Allow: []string{"10.0.0.1"}, }, }, - IP: net.ParseIP("10.0.0.1"), + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -705,7 +721,8 @@ func TestIPAllowedRule(t *testing.T) { Allow: []string{"10.0.0.1"}, }, }, - IP: net.ParseIP("10.0.0.1"), + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -735,12 +752,23 @@ func TestIPBypassedRule(t *testing.T) { ctx *ACLContext expected Effect }{ + { + name: "when trusted proxies are not configured, IP is not bypassed", + rule: defaultIPBR, + ctx: &ACLContext{ + ACLs: &model.App{}, + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: false, + }, + expected: EffectDeny, + }, { name: "deny when ACLs are nil and no global bypass", rule: defaultIPBR, ctx: &ACLContext{ - ACLs: nil, - IP: net.ParseIP("10.0.0.1"), + ACLs: nil, + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -748,8 +776,9 @@ func TestIPBypassedRule(t *testing.T) { name: "allows when ACLs are nil but IP matches global bypass", rule: globBypassIPBR, ctx: &ACLContext{ - ACLs: nil, - IP: net.ParseIP("10.0.0.5"), + ACLs: nil, + IP: net.ParseIP("10.0.0.5"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -757,8 +786,9 @@ func TestIPBypassedRule(t *testing.T) { name: "denies when ACLs are nil and IP does not match global bypass", rule: globBypassIPBR, ctx: &ACLContext{ - ACLs: nil, - IP: net.ParseIP("192.168.1.1"), + ACLs: nil, + IP: net.ParseIP("192.168.1.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -769,7 +799,8 @@ func TestIPBypassedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}}, }, - IP: net.ParseIP("10.0.0.5"), + IP: net.ParseIP("10.0.0.5"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -780,7 +811,8 @@ func TestIPBypassedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Bypass: []string{"172.16.0.0/24"}}, }, - IP: net.ParseIP("10.0.0.5"), + IP: net.ParseIP("10.0.0.5"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -791,7 +823,8 @@ func TestIPBypassedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}}, }, - IP: net.ParseIP("10.0.0.5"), + IP: net.ParseIP("10.0.0.5"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, @@ -802,7 +835,8 @@ func TestIPBypassedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}}, }, - IP: net.ParseIP("192.168.1.1"), + IP: net.ParseIP("192.168.1.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -810,8 +844,9 @@ func TestIPBypassedRule(t *testing.T) { name: "denies when bypass list is empty", rule: defaultIPBR, ctx: &ACLContext{ - ACLs: &model.App{}, - IP: net.ParseIP("10.0.0.1"), + ACLs: &model.App{}, + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectDeny, }, @@ -822,7 +857,8 @@ func TestIPBypassedRule(t *testing.T) { ACLs: &model.App{ IP: model.AppIP{Bypass: []string{"not-an-ip", "10.0.0.1"}}, }, - IP: net.ParseIP("10.0.0.1"), + IP: net.ParseIP("10.0.0.1"), + TrustedProxiesConfigured: true, }, expected: EffectAllow, }, diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index b6d0ba56c..c75285c3f 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -8,7 +8,6 @@ import ( "math/big" "net/http" "strings" - "sync" "time" "github.com/steveiliop56/ding" @@ -71,21 +70,11 @@ type AuthService struct { dummyHash string - lockdown struct { - active bool - until time.Time - ctx context.Context - cancelFunc context.CancelFunc - mu sync.RWMutex - } - caches struct { login *CacheStore[LoginAttempt] oauth *CacheStore[OAuthPendingSession] ldap *CacheStore[[]string] } - - maxLoginLimits int } type AuthServiceInput struct { @@ -116,15 +105,6 @@ func NewAuthService(i AuthServiceInput) (*AuthService, error) { policyEngine: i.PolicyEngine, } - // get the max login limits based on the number of users and the configured max retries - service.maxLoginLimits = service.calculateLockdownLimit() - - loginCacheSize := 0 - - if !service.config.Auth.LockdownEnabled { - loginCacheSize = service.maxLoginLimits - } - // dummy hash dummyHash, err := bcrypt.GenerateFromPassword([]byte(utils.GenerateString(8)), bcrypt.DefaultCost) @@ -136,7 +116,7 @@ func NewAuthService(i AuthServiceInput) (*AuthService, error) { // caches setup oauthCache := NewCacheStore[OAuthPendingSession](256) - loginCache := NewCacheStore[LoginAttempt](loginCacheSize) + loginCache := NewCacheStore[LoginAttempt](service.calculateLockdownLimit()) ldapCache := NewCacheStore[[]string](1024) service.caches.oauth = oauthCache @@ -159,6 +139,23 @@ func NewAuthService(i AuthServiceInput) (*AuthService, error) { } }, ding.RingMinor) + i.Ding.Go(func(ctx context.Context) { + ticker := time.NewTicker(15 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + service.log.App.Debug().Msg("Updating login cache limits") + service.caches.login.SetMaxSize(service.calculateLockdownLimit()) + service.log.App.Debug().Msg("Login cache limits updated") + case <-ctx.Done(): + return + } + } + + }, ding.RingMinor) + return service, nil } @@ -260,10 +257,6 @@ func (auth *AuthService) GetLDAPUser(userDN string) (*model.LDAPUser, error) { } func (auth *AuthService) IsAccountLocked(identifier string) (bool, int) { - if locked, remaining := auth.IsInLockdown(); locked { - return true, remaining - } - if auth.config.Auth.LoginMaxRetries <= 0 || auth.config.Auth.LoginTimeout <= 0 { return false, 0 } @@ -286,14 +279,6 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) { return } - if !success && auth.config.Auth.LockdownEnabled && auth.caches.login.Size() >= auth.maxLoginLimits { - if locked, _ := auth.IsInLockdown(); locked { - return - } - go auth.lockdownMode() - return - } - auth.caches.login.WithLock(func(actions CacheStoreActions[LoginAttempt]) { entry, ok := actions.Get(identifier) @@ -359,7 +344,7 @@ func (auth *AuthService) CreateSession(ctx context.Context, data repository.Sess return nil, fmt.Errorf("tailscale service not configured, cannot create session for tailscale user") } - uuid, err := uuid.NewRandom() + u, err := uuid.NewRandom() if err != nil { return nil, fmt.Errorf("failed to generate session uuid: %w", err) @@ -376,7 +361,7 @@ func (auth *AuthService) CreateSession(ctx context.Context, data repository.Sess expiresAt := time.Now().Add(time.Duration(expiry) * time.Second) session := repository.CreateSessionParams{ - UUID: uuid.String(), + UUID: u.String(), Username: data.Username, Email: data.Email, Name: data.Name, @@ -631,62 +616,7 @@ func (auth *AuthService) GetOAuthPendingSession(sessionId string) (*OAuthPending return &session, nil } -func (auth *AuthService) lockdownMode() { - auth.lockdown.mu.Lock() - - if auth.lockdown.active { - auth.lockdown.mu.Unlock() - return - } - - ctx, cancel := context.WithCancel(auth.ctx) - - auth.log.App.Warn().Msg("Too many failed login attempts, entering lockdown mode") - - auth.lockdown.active = true - auth.lockdown.ctx = ctx - auth.lockdown.cancelFunc = cancel - - d := time.Duration(auth.config.Auth.LoginTimeout) * time.Second - auth.lockdown.until = time.Now().Add(d) - timer := time.NewTimer(d) - - auth.lockdown.mu.Unlock() - - defer cancel() - defer timer.Stop() - - select { - case <-timer.C: - // Timer expired, end lockdown - case <-ctx.Done(): - // Context cancelled, end lockdown - } - - auth.lockdown.mu.Lock() - - auth.log.App.Info().Msg("Exiting lockdown mode") - - auth.caches.login.Clear() - auth.lockdown.active = false - auth.lockdown.until = time.Time{} - auth.lockdown.ctx = nil - auth.lockdown.cancelFunc = nil - - auth.lockdown.mu.Unlock() -} - -func (auth *AuthService) IsInLockdown() (bool, int) { - auth.lockdown.mu.RLock() - defer auth.lockdown.mu.RUnlock() - if auth.lockdown.active { - remaining := int(time.Until(auth.lockdown.until).Seconds()) - return true, remaining - } - return false, 0 -} - -// mostly a testing function, not useful for anything else +// ClearLoginAttempts is a testing function, not useful for anything else func (auth *AuthService) ClearLoginAttempts() { auth.caches.login.Clear() } diff --git a/internal/service/cache_store.go b/internal/service/cache_store.go index 9dbe057d9..06e230ee7 100644 --- a/internal/service/cache_store.go +++ b/internal/service/cache_store.go @@ -195,3 +195,19 @@ func (cs *CacheStore[T]) Clear() { cs.cache = make(map[string]cacheEntry[T]) cs.order = make([]string, 0) } + +func (cs *CacheStore[T]) SetMaxSize(maxSize int) { + cs.mu.Lock() + defer cs.mu.Unlock() + cs.maxSize = maxSize + for len(cs.cache) > maxSize { + if !cs.evictOne() { + break + } + } +} +func (cs *CacheStore[T]) GetMaxSize() int { + cs.mu.Lock() + defer cs.mu.Unlock() + return cs.maxSize +} diff --git a/internal/service/cache_store_test.go b/internal/service/cache_store_test.go index 8908017f7..780f98009 100644 --- a/internal/service/cache_store_test.go +++ b/internal/service/cache_store_test.go @@ -316,6 +316,21 @@ func TestCacheStoreSizeAndClear(t *testing.T) { assert.False(t, ok) } +func TestCacheStoreWithMaxSize(t *testing.T) { + cs := NewCacheStore[string](0) + assert.Equal(t, 0, cs.Size()) + + for i := 0; i < 100; i++ { + cs.Set(strconv.Itoa(i), strconv.Itoa(i), 0) + } + + assert.Equal(t, 100, cs.Size()) + + cs.SetMaxSize(10) + + assert.Equal(t, 10, cs.Size()) +} + func TestCacheStoreWithLock(t *testing.T) { cs := NewCacheStore[int](0) cs.Set("counter", 1, 0) diff --git a/internal/service/policy_engine.go b/internal/service/policy_engine.go index c3bbb1337..943cd4c98 100644 --- a/internal/service/policy_engine.go +++ b/internal/service/policy_engine.go @@ -29,10 +29,11 @@ type Rule interface { } type ACLContext struct { - ACLs *model.App - UserContext *model.UserContext - IP net.IP - Path string + ACLs *model.App + UserContext *model.UserContext + IP net.IP + Path string + TrustedProxiesConfigured bool } type PolicyEngine struct { diff --git a/internal/test/test.go b/internal/test/test.go index a3f07ca0f..20275adce 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -163,9 +163,10 @@ func CreateTestConfigs(t *testing.T) (model.Config, model.RuntimeConfig) { }, }, }, - CookieDomain: "example.com", - AppURL: "https://tinyauth.example.com", - SessionCookieName: "tinyauth-session", + CookieDomain: "example.com", + AppURL: "https://tinyauth.example.com", + SessionCookieName: "tinyauth-session", + TrustedProxiesConfigured: true, } return config, runtime