From ba8dc42578614f9770919425c158fe0ca22a21df Mon Sep 17 00:00:00 2001 From: Stavros Date: Fri, 10 Apr 2026 18:21:33 +0300 Subject: [PATCH 1/2] feat: add x-tinyauth-location to nginx response Solves #773. Normally you let Nginx handle the login URL creation but with this "hack" we can set an arbitary header with where Tinyauth wants the user to go to. Later the Nginx error page can get this header and redirect accordingly. --- internal/controller/proxy_controller.go | 97 +++++++++++--------- internal/controller/proxy_controller_test.go | 16 ++-- 2 files changed, 64 insertions(+), 49 deletions(-) diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index 404fc51d..9790daa9 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -131,14 +131,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { } if !controller.auth.CheckIP(acls.IP, clientIP) { - if !controller.useBrowserResponse(proxyCtx) { - c.JSON(401, gin.H{ - "status": 401, - "message": "Unauthorized", - }) - return - } - queries, err := query.Values(config.UnauthorizedQuery{ Resource: strings.Split(proxyCtx.Host, ".")[0], IP: clientIP, @@ -146,11 +138,22 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { if err != nil { tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query") - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + controller.handleError(c, proxyCtx) + return + } + + redirectURL := fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode()) + + if !controller.useBrowserResponse(proxyCtx) { + c.Header("x-tinyauth-location", redirectURL) + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) return } - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode())) + c.Redirect(http.StatusTemporaryRedirect, redirectURL) return } @@ -175,21 +178,13 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { if !userAllowed { tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource") - if !controller.useBrowserResponse(proxyCtx) { - c.JSON(403, gin.H{ - "status": 403, - "message": "Forbidden", - }) - return - } - queries, err := query.Values(config.UnauthorizedQuery{ Resource: strings.Split(proxyCtx.Host, ".")[0], }) if err != nil { tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query") - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + controller.handleError(c, proxyCtx) return } @@ -199,7 +194,18 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { queries.Set("username", userContext.Username) } - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode())) + redirectURL := fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode()) + + if !controller.useBrowserResponse(proxyCtx) { + c.Header("x-tinyauth-location", redirectURL) + c.JSON(403, gin.H{ + "status": 403, + "message": "Forbidden", + }) + return + } + + c.Redirect(http.StatusTemporaryRedirect, redirectURL) return } @@ -215,14 +221,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { if !groupOK { tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements") - if !controller.useBrowserResponse(proxyCtx) { - c.JSON(403, gin.H{ - "status": 403, - "message": "Forbidden", - }) - return - } - queries, err := query.Values(config.UnauthorizedQuery{ Resource: strings.Split(proxyCtx.Host, ".")[0], GroupErr: true, @@ -230,7 +228,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { if err != nil { tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query") - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + controller.handleError(c, proxyCtx) return } @@ -240,7 +238,18 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { queries.Set("username", userContext.Username) } - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode())) + redirectURL := fmt.Sprintf("%s/unauthorized?%s", controller.config.AppURL, queries.Encode()) + + if !controller.useBrowserResponse(proxyCtx) { + c.Header("x-tinyauth-location", redirectURL) + c.JSON(403, gin.H{ + "status": 403, + "message": "Forbidden", + }) + return + } + + c.Redirect(http.StatusTemporaryRedirect, redirectURL) return } } @@ -266,25 +275,28 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { return } - if !controller.useBrowserResponse(proxyCtx) { - c.JSON(401, gin.H{ - "status": 401, - "message": "Unauthorized", - }) - return - } - queries, err := query.Values(config.RedirectQuery{ RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path), }) if err != nil { tlog.App.Error().Err(err).Msg("Failed to encode redirect URI query") - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + controller.handleError(c, proxyCtx) return } - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/login?%s", controller.config.AppURL, queries.Encode())) + redirectURL := fmt.Sprintf("%s/login?%s", controller.config.AppURL, queries.Encode()) + + if !controller.useBrowserResponse(proxyCtx) { + c.Header("x-tinyauth-location", redirectURL) + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) + return + } + + c.Redirect(http.StatusTemporaryRedirect, redirectURL) } func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) { @@ -306,7 +318,10 @@ func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) { } func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyContext) { + redirectURL := fmt.Sprintf("%s/error", controller.config.AppURL) + if !controller.useBrowserResponse(proxyCtx) { + c.Header("x-tinyauth-location", redirectURL) c.JSON(500, gin.H{ "status": 500, "message": "Internal Server Error", @@ -314,7 +329,7 @@ func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyCon return } - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + c.Redirect(http.StatusTemporaryRedirect, redirectURL) } func (controller *ProxyController) getHeader(c *gin.Context, header string) (string, bool) { diff --git a/internal/controller/proxy_controller_test.go b/internal/controller/proxy_controller_test.go index 35d5d6a2..f4855606 100644 --- a/internal/controller/proxy_controller_test.go +++ b/internal/controller/proxy_controller_test.go @@ -116,8 +116,7 @@ func TestProxyController(t *testing.T) { assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Contains(t, location, "https://tinyauth.example.com/login?redirect_uri=") - assert.Contains(t, location, "https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") }, }, { @@ -129,6 +128,8 @@ func TestProxyController(t *testing.T) { req.Header.Set("user-agent", browserUserAgent) router.ServeHTTP(recorder, req) assert.Equal(t, 401, recorder.Code) + location := recorder.Header().Get("x-tinyauth-location") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") }, }, { @@ -142,8 +143,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Contains(t, location, "https://tinyauth.example.com/login?redirect_uri=") - assert.Contains(t, location, "https%3A%2F%2Ftest.example.com%2Fhello") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello") }, }, { @@ -159,8 +159,7 @@ func TestProxyController(t *testing.T) { assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Contains(t, location, "https://tinyauth.example.com/login?redirect_uri=") - assert.Contains(t, location, "https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") }, }, { @@ -174,6 +173,8 @@ func TestProxyController(t *testing.T) { req.Header.Set("user-agent", browserUserAgent) router.ServeHTTP(recorder, req) assert.Equal(t, 401, recorder.Code) + location := recorder.Header().Get("x-tinyauth-location") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") }, }, { @@ -189,8 +190,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Contains(t, location, "https://tinyauth.example.com/login?redirect_uri=") - assert.Contains(t, location, "https%3A%2F%2Ftest.example.com%2Fhello") + assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello") }, }, { From e000959b5149f01c0f142d29410a75b880357668 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 11 Apr 2026 17:22:11 +0300 Subject: [PATCH 2/2] tests: fix assert.Equal order --- internal/controller/proxy_controller_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/controller/proxy_controller_test.go b/internal/controller/proxy_controller_test.go index f4855606..5c07acf5 100644 --- a/internal/controller/proxy_controller_test.go +++ b/internal/controller/proxy_controller_test.go @@ -116,7 +116,7 @@ func TestProxyController(t *testing.T) { assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F", location) }, }, { @@ -129,7 +129,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 401, recorder.Code) location := recorder.Header().Get("x-tinyauth-location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F", location) }, }, { @@ -143,7 +143,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello", location) }, }, { @@ -159,7 +159,7 @@ func TestProxyController(t *testing.T) { assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F", location) }, }, { @@ -174,7 +174,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 401, recorder.Code) location := recorder.Header().Get("x-tinyauth-location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2F", location) }, }, { @@ -190,7 +190,7 @@ func TestProxyController(t *testing.T) { router.ServeHTTP(recorder, req) assert.Equal(t, 307, recorder.Code) location := recorder.Header().Get("Location") - assert.Equal(t, location, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello") + assert.Equal(t, "https://tinyauth.example.com/login?redirect_uri=https%3A%2F%2Ftest.example.com%2Fhello", location) }, }, {