From c93281775771f7570bcf58f8382bd68d41fe63ba Mon Sep 17 00:00:00 2001 From: Stavros Date: Mon, 4 May 2026 20:07:03 +0300 Subject: [PATCH] fix: fix controller tests --- .../controller/context_controller_test.go | 20 ++- internal/controller/oidc_controller_test.go | 26 ++-- internal/controller/proxy_controller.go | 16 +- internal/controller/proxy_controller_test.go | 62 ++++---- internal/controller/user_controller_test.go | 137 ++++++++++-------- .../controller/well_known_controller_test.go | 10 +- 6 files changed, 154 insertions(+), 117 deletions(-) diff --git a/internal/controller/context_controller_test.go b/internal/controller/context_controller_test.go index 2329425b..12a8e22b 100644 --- a/internal/controller/context_controller_test.go +++ b/internal/controller/context_controller_test.go @@ -7,11 +7,11 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/tinyauthapp/tinyauth/internal/config" + "github.com/stretchr/testify/assert" "github.com/tinyauthapp/tinyauth/internal/controller" + "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/utils" "github.com/tinyauthapp/tinyauth/internal/utils/tlog" - "github.com/stretchr/testify/assert" ) func TestContextController(t *testing.T) { @@ -79,12 +79,16 @@ func TestContextController(t *testing.T) { description: "Ensure user context returns when authorized", middlewares: []gin.HandlerFunc{ func(c *gin.Context) { - c.Set("context", &config.UserContext{ - Username: "johndoe", - Name: "John Doe", - Email: utils.CompileUserEmail("johndoe", controllerConfig.CookieDomain), - Provider: "local", - IsLoggedIn: true, + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "johndoe", + Name: "John Doe", + Email: utils.CompileUserEmail("johndoe", controllerConfig.CookieDomain), + }, + }, }) }, }, diff --git a/internal/controller/oidc_controller_test.go b/internal/controller/oidc_controller_test.go index a09697bf..150540fc 100644 --- a/internal/controller/oidc_controller_test.go +++ b/internal/controller/oidc_controller_test.go @@ -12,14 +12,14 @@ import ( "github.com/gin-gonic/gin" "github.com/google/go-querystring/query" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/bootstrap" - "github.com/tinyauthapp/tinyauth/internal/config" "github.com/tinyauthapp/tinyauth/internal/controller" + "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/repository" "github.com/tinyauthapp/tinyauth/internal/service" "github.com/tinyauthapp/tinyauth/internal/utils/tlog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestOIDCController(t *testing.T) { @@ -27,7 +27,7 @@ func TestOIDCController(t *testing.T) { tempDir := t.TempDir() oidcServiceCfg := service.OIDCServiceConfig{ - Clients: map[string]config.OIDCClientConfig{ + Clients: map[string]model.OIDCClientConfig{ "test": { ClientID: "some-client-id", ClientSecret: "some-client-secret", @@ -44,12 +44,16 @@ func TestOIDCController(t *testing.T) { controllerCfg := controller.OIDCControllerConfig{} simpleCtx := func(c *gin.Context) { - c.Set("context", &config.UserContext{ - Username: "test", - Name: "Test User", - Email: "test@example.com", - IsLoggedIn: true, - Provider: "local", + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "test", + Name: "Test User", + Email: "test@example.com", + }, + }, }) c.Next() } @@ -848,7 +852,7 @@ func TestOIDCController(t *testing.T) { }, } - app := bootstrap.NewBootstrapApp(config.Config{}) + app := bootstrap.NewBootstrapApp(model.Config{}) db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db")) require.NoError(t, err) diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index c4d97643..7cd01969 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -99,16 +99,12 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { return } - if acls == nil { - acls = &model.App{} - } - tlog.App.Trace().Interface("acls", acls).Msg("ACLs for resource") clientIP := c.ClientIP() if controller.auth.IsBypassedIP(clientIP, acls) { - controller.setHeaders(c, *acls) + controller.setHeaders(c, acls) c.JSON(200, gin.H{ "status": 200, "message": "Authenticated", @@ -126,7 +122,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { if !authEnabled { tlog.App.Debug().Msg("Authentication disabled for resource, allowing access") - controller.setHeaders(c, *acls) + controller.setHeaders(c, acls) c.JSON(200, gin.H{ "status": 200, "message": "Authenticated", @@ -267,7 +263,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuth.Sub)) } - controller.setHeaders(c, *acls) + controller.setHeaders(c, acls) c.JSON(200, gin.H{ "status": 200, @@ -300,9 +296,13 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, redirectURL) } -func (controller *ProxyController) setHeaders(c *gin.Context, acls model.App) { +func (controller *ProxyController) setHeaders(c *gin.Context, acls *model.App) { c.Header("Authorization", c.Request.Header.Get("Authorization")) + if acls == nil { + return + } + headers := utils.ParseHeaders(acls.Response.Headers) for key, value := range headers { diff --git a/internal/controller/proxy_controller_test.go b/internal/controller/proxy_controller_test.go index 8efbd31c..6e4e4c0e 100644 --- a/internal/controller/proxy_controller_test.go +++ b/internal/controller/proxy_controller_test.go @@ -6,14 +6,14 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/bootstrap" - "github.com/tinyauthapp/tinyauth/internal/config" "github.com/tinyauthapp/tinyauth/internal/controller" + "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/repository" "github.com/tinyauthapp/tinyauth/internal/service" "github.com/tinyauthapp/tinyauth/internal/utils/tlog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestProxyController(t *testing.T) { @@ -21,7 +21,7 @@ func TestProxyController(t *testing.T) { tempDir := t.TempDir() authServiceCfg := service.AuthServiceConfig{ - Users: []config.User{ + LocalUsers: []model.LocalUser{ { Username: "testuser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password @@ -29,7 +29,7 @@ func TestProxyController(t *testing.T) { { Username: "totpuser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password - TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", + TOTPSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", }, }, SessionExpiry: 10, // 10 seconds, useful for testing @@ -43,28 +43,28 @@ func TestProxyController(t *testing.T) { AppURL: "https://tinyauth.example.com", } - acls := map[string]config.App{ + acls := map[string]model.App{ "app_path_allow": { - Config: config.AppConfig{ + Config: model.AppConfig{ Domain: "path-allow.example.com", }, - Path: config.AppPath{ + Path: model.AppPath{ Allow: "/allowed", }, }, "app_user_allow": { - Config: config.AppConfig{ + Config: model.AppConfig{ Domain: "user-allow.example.com", }, - Users: config.AppUsers{ + Users: model.AppUsers{ Allow: "testuser", }, }, "ip_bypass": { - Config: config.AppConfig{ + Config: model.AppConfig{ Domain: "ip-bypass.example.com", }, - IP: config.AppIP{ + IP: model.AppIP{ Bypass: []string{"10.10.10.10"}, }, }, @@ -74,24 +74,32 @@ func TestProxyController(t *testing.T) { Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36` simpleCtx := func(c *gin.Context) { - c.Set("context", &config.UserContext{ - Username: "testuser", - Name: "Testuser", - Email: "testuser@example.com", - IsLoggedIn: true, - Provider: "local", + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "testuser", + Name: "Testuser", + Email: "testuser@example.com", + }, + }, }) c.Next() } simpleCtxTotp := func(c *gin.Context) { - c.Set("context", &config.UserContext{ - Username: "totpuser", - Name: "Totpuser", - Email: "totpuser@example.com", - IsLoggedIn: true, - Provider: "local", - TotpEnabled: true, + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "totpuser", + Name: "Totpuser", + Email: "totpuser@example.com", + }, + TOTPEnabled: true, + }, }) c.Next() } @@ -391,9 +399,9 @@ func TestProxyController(t *testing.T) { }, } - oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig) + oauthBrokerCfgs := make(map[string]model.OAuthServiceConfig) - app := bootstrap.NewBootstrapApp(config.Config{}) + app := bootstrap.NewBootstrapApp(model.Config{}) db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db")) require.NoError(t, err) diff --git a/internal/controller/user_controller_test.go b/internal/controller/user_controller_test.go index 65ef15ef..8d2e6c5e 100644 --- a/internal/controller/user_controller_test.go +++ b/internal/controller/user_controller_test.go @@ -10,14 +10,14 @@ import ( "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/bootstrap" - "github.com/tinyauthapp/tinyauth/internal/config" "github.com/tinyauthapp/tinyauth/internal/controller" + "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/repository" "github.com/tinyauthapp/tinyauth/internal/service" "github.com/tinyauthapp/tinyauth/internal/utils/tlog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestUserController(t *testing.T) { @@ -25,7 +25,7 @@ func TestUserController(t *testing.T) { tempDir := t.TempDir() authServiceCfg := service.AuthServiceConfig{ - Users: []config.User{ + LocalUsers: []model.LocalUser{ { Username: "testuser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password @@ -33,12 +33,12 @@ func TestUserController(t *testing.T) { { Username: "totpuser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password - TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", + TOTPSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", }, { Username: "attruser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password - Attributes: config.UserAttributes{ + Attributes: model.UserAttributes{ Name: "Alice Smith", Email: "alice@example.com", }, @@ -46,8 +46,8 @@ func TestUserController(t *testing.T) { { Username: "attrtotpuser", Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password - TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", - Attributes: config.UserAttributes{ + TOTPSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", + Attributes: model.UserAttributes{ Name: "Bob Jones", Email: "bob@example.com", }, @@ -61,7 +61,54 @@ func TestUserController(t *testing.T) { } userControllerCfg := controller.UserControllerConfig{ - CookieDomain: "example.com", + CookieDomain: "example.com", + SessionCookieName: "tinyauth-session", + } + + totpCtx := func(c *gin.Context) { + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "totpuser", + Name: "Totpuser", + Email: "totpuser@example.com", + }, + TOTPPending: true, + TOTPEnabled: true, + }, + }) + } + + totpAttrCtx := func(c *gin.Context) { + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "attrtotpuser", + Name: "Bob Jones", + Email: "bob@example.com", + }, + TOTPPending: true, + TOTPEnabled: true, + }, + }) + } + + simpleCtx := func(c *gin.Context) { + c.Set("context", &model.UserContext{ + Authenticated: true, + Provider: model.ProviderLocal, + Local: &model.LocalContext{ + BaseContext: model.BaseContext{ + Username: "testuser", + Name: "Test User", + Email: "testuser@example.com", + }, + }, + }) } type testCase struct { @@ -188,7 +235,9 @@ func TestUserController(t *testing.T) { }, { description: "Should be able to logout", - middlewares: []gin.HandlerFunc{}, + middlewares: []gin.HandlerFunc{ + simpleCtx, + }, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { // First login to get a session cookie loginReq := controller.LoginRequest{ @@ -204,9 +253,10 @@ func TestUserController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 200, recorder.Code) - assert.Len(t, recorder.Result().Cookies(), 1) + cookies := recorder.Result().Cookies() + assert.Len(t, cookies, 1) - cookie := recorder.Result().Cookies()[0] + cookie := cookies[0] assert.Equal(t, "tinyauth-session", cookie.Name) // Now logout using the session cookie @@ -217,17 +267,20 @@ func TestUserController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 200, recorder.Code) - assert.Len(t, recorder.Result().Cookies(), 1) + cookies = recorder.Result().Cookies() + assert.Len(t, cookies, 1) - logoutCookie := recorder.Result().Cookies()[0] - assert.Equal(t, "tinyauth-session", logoutCookie.Name) - assert.Equal(t, "", logoutCookie.Value) - assert.Equal(t, -1, logoutCookie.MaxAge) // MaxAge -1 means delete cookie + cookie = cookies[0] + assert.Equal(t, "tinyauth-session", cookie.Name) + assert.Equal(t, "", cookie.Value) + assert.Equal(t, -1, cookie.MaxAge) // MaxAge -1 means delete cookie }, }, { description: "Should be able to login with totp", - middlewares: []gin.HandlerFunc{}, + middlewares: []gin.HandlerFunc{ + totpCtx, + }, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { code, err := totp.GenerateCode("JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", time.Now()) assert.NoError(t, err) @@ -258,7 +311,9 @@ func TestUserController(t *testing.T) { }, { description: "Totp should rate limit on multiple invalid attempts", - middlewares: []gin.HandlerFunc{}, + middlewares: []gin.HandlerFunc{ + totpCtx, + }, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { for range 3 { totpReq := controller.TotpRequest{ @@ -328,7 +383,9 @@ func TestUserController(t *testing.T) { }, { description: "TOTP completion uses name and email from user attributes", - middlewares: []gin.HandlerFunc{}, + middlewares: []gin.HandlerFunc{ + totpAttrCtx, + }, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { code, err := totp.GenerateCode("JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", time.Now()) require.NoError(t, err) @@ -349,9 +406,9 @@ func TestUserController(t *testing.T) { }, } - oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig) + oauthBrokerCfgs := make(map[string]model.OAuthServiceConfig) - app := bootstrap.NewBootstrapApp(config.Config{}) + app := bootstrap.NewBootstrapApp(model.Config{}) db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db")) require.NoError(t, err) @@ -379,33 +436,6 @@ func TestUserController(t *testing.T) { authService.ClearRateLimitsTestingOnly() } - setTotpMiddlewareOverrides := map[string]config.UserContext{ - "Should be able to login with totp": { - Username: "totpuser", - Name: "Totpuser", - Email: "totpuser@example.com", - Provider: "local", - TotpPending: true, - TotpEnabled: true, - }, - "Totp should rate limit on multiple invalid attempts": { - Username: "totpuser", - Name: "Totpuser", - Email: "totpuser@example.com", - Provider: "local", - TotpPending: true, - TotpEnabled: true, - }, - "TOTP completion uses name and email from user attributes": { - Username: "attrtotpuser", - Name: "Bob Jones", - Email: "bob@example.com", - Provider: "local", - TotpPending: true, - TotpEnabled: true, - }, - } - for _, test := range tests { beforeEach() t.Run(test.description, func(t *testing.T) { @@ -415,15 +445,6 @@ func TestUserController(t *testing.T) { router.Use(middleware) } - // Gin is stupid and doesn't allow setting a middleware after the groups - // so we need to do some stupid overrides here - if ctx, ok := setTotpMiddlewareOverrides[test.description]; ok { - ctx := ctx - router.Use(func(c *gin.Context) { - c.Set("context", &ctx) - }) - } - group := router.Group("/api") gin.SetMode(gin.TestMode) diff --git a/internal/controller/well_known_controller_test.go b/internal/controller/well_known_controller_test.go index 7d8d05f5..7dcf2bdc 100644 --- a/internal/controller/well_known_controller_test.go +++ b/internal/controller/well_known_controller_test.go @@ -8,14 +8,14 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/bootstrap" - "github.com/tinyauthapp/tinyauth/internal/config" "github.com/tinyauthapp/tinyauth/internal/controller" + "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/repository" "github.com/tinyauthapp/tinyauth/internal/service" "github.com/tinyauthapp/tinyauth/internal/utils/tlog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestWellKnownController(t *testing.T) { @@ -23,7 +23,7 @@ func TestWellKnownController(t *testing.T) { tempDir := t.TempDir() oidcServiceCfg := service.OIDCServiceConfig{ - Clients: map[string]config.OIDCClientConfig{ + Clients: map[string]model.OIDCClientConfig{ "test": { ClientID: "some-client-id", ClientSecret: "some-client-secret", @@ -101,7 +101,7 @@ func TestWellKnownController(t *testing.T) { }, } - app := bootstrap.NewBootstrapApp(config.Config{}) + app := bootstrap.NewBootstrapApp(model.Config{}) db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db")) require.NoError(t, err)