From 50be834d24cd2e37c6b5813a1cd5794acf144b50 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 17 Aug 2026 22:24:18 -0700 Subject: [PATCH] fix: allow anonymous theme switching when REQUIRE_SIGNIN_VIEW is set (#38956) (#38961) Backport #38956 by @bircni Fixes https://github.com/go-gitea/gitea/issues/38950 `POST /-/web-theme/apply` used the `optSignIn` middleware, which forces sign-in when `REQUIRE_SIGNIN_VIEW` is enabled. This made theme switching unusable for anonymous users (e.g. on the sign-in page), even though the handler already supports anonymous users by storing the choice in a cookie. This was an unintended regression from https://github.com/go-gitea/gitea/pull/36183, which replaced the route's CSRF-only middleware with `optSignIn`, incidentally pulling in the sign-in requirement meant for content routes. The fix drops the sign-in requirement for this route while keeping cross-origin protection and the usual signed-in-user checks (inactive/prohibited login, forced password change). Co-authored-by: bircni Co-authored-by: wxiaoguang --- routers/web/web.go | 12 +++++++----- tests/integration/signin_test.go | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 0bfae44e869..bbb933acc21 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -327,14 +327,16 @@ var optSignInFromAnyOrigin = verifyAuthWithOptions(&common.VerifyOptions{Disable // registerWebRoutes register routes func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { - // required to be signed in or signed out + validation.AddBindingRules() + + // middleware: required to be signed in or signed out reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) - // optional sign in (if signed in, use the user as doer, if not, no doer) + // middleware: optional sign in (if signed in, use the user as doer, if not, no doer) optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict}) optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView}) - - validation.AddBindingRules() + // middleware: only apply CrossOriginProtection + crossOriginProtect := verifyAuthWithOptions(&common.VerifyOptions{DisableCrossOriginProtection: false}) openIDSignInEnabled := func(ctx *context.Context) { if !setting.Service.EnableOpenIDSignIn { @@ -530,7 +532,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Post("/-/markup", reqSignIn, web.Bind(structs.MarkupOption{}), misc.Markup) m.Post("/-/web-banner/dismiss", misc.WebBannerDismiss) m.Get("/-/web-theme/list", misc.WebThemeList) - m.Post("/-/web-theme/apply", optSignIn, misc.WebThemeApply) + m.Post("/-/web-theme/apply", crossOriginProtect, misc.WebThemeApply) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index ab1167ce2cd..9b0d263ed50 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -17,6 +17,7 @@ import ( "gitea.dev/modules/test" "gitea.dev/modules/translation" "gitea.dev/modules/web" + "gitea.dev/modules/web/middleware" "gitea.dev/routers" "gitea.dev/routers/web/auth" "gitea.dev/services/context" @@ -183,9 +184,17 @@ func TestRequireSignInView(t *testing.T) { t.Run("RequireSignInView", func(t *testing.T) { defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") - resp := MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user/login?redirect_to=%2Fuser2%2Frepo1%2Fsrc%2Fbranch%2Fmaster", resp.Header().Get("Location")) + t.Run("AccessPublicRepo", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login?redirect_to=%2Fuser2%2Frepo1%2Fsrc%2Fbranch%2Fmaster", resp.Header().Get("Location")) + }) + t.Run("UpdateTheme", func(t *testing.T) { + session := emptyTestSession(t) + req := NewRequest(t, "POST", "/-/web-theme/apply?theme=gitea-dark") + session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "gitea-dark", session.GetSiteCookie(middleware.CookieTheme)) + }) }) t.Run("BlockAnonymousAccessExpensive", func(t *testing.T) { defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, false)()