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 <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot
2026-08-17 22:24:18 -07:00
committed by GitHub
parent f4bff43d46
commit 50be834d24
2 changed files with 19 additions and 8 deletions
+7 -5
View File
@@ -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) {
+12 -3
View File
@@ -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)()