From c9cd3bf63b3876f5c75b3520472bb99b37e96284 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Wed, 6 Sep 2023 23:15:20 +0200
Subject: [PATCH 1/7] Automatically set cookie `secure` attribute, remove
 COOKIE_SECURE option

---
 custom/conf/app.example.ini                   |  3 ---
 .../config-cheat-sheet.en-us.md               |  1 -
 .../config-cheat-sheet.zh-cn.md               |  1 -
 modules/context/context.go                    | 26 +++++++++---------
 modules/context/context_cookie.go             |  4 +--
 modules/setting/session.go                    |  3 ---
 modules/web/middleware/cookie.go              | 23 +++++++++++-----
 modules/web/middleware/locale.go              | 10 +++----
 options/locale/locale_en-US.ini               |  1 -
 routers/common/middleware.go                  | 27 +++++++++++--------
 routers/web/admin/config.go                   |  1 -
 routers/web/auth/auth.go                      | 16 +++++------
 routers/web/auth/oauth.go                     |  4 +--
 routers/web/auth/openid.go                    |  4 +--
 routers/web/auth/password.go                  |  2 +-
 routers/web/home.go                           |  2 +-
 routers/web/user/setting/profile.go           |  2 +-
 services/auth/auth.go                         |  2 +-
 services/auth/middleware.go                   |  6 ++---
 templates/admin/config.tmpl                   |  2 --
 20 files changed, 72 insertions(+), 68 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index a2fab2fd50931..03ce10af3cb48 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1744,9 +1744,6 @@ LEVEL = Info
 ;; Session cookie name
 ;COOKIE_NAME = i_like_gitea
 ;;
-;; If you use session in https only, default is false
-;COOKIE_SECURE = false
-;;
 ;; Session GC time interval in seconds, default is 86400 (1 day)
 ;GC_INTERVAL_TIME = 86400
 ;;
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index 7e8befb8b7e67..1d947999ec243 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -776,7 +776,6 @@ and
 
 - `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]. Setting `db` will reuse the configuration in `[database]`
 - `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for db, empty (database config will be used); for others, the connection string. Relative paths will be made absolute against _`AppWorkPath`_.
-- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
 - `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
 - `GC_INTERVAL_TIME`: **86400**: GC interval in seconds.
 - `SESSION_LIFE_TIME`: **86400**: Session life time in seconds, default is 86400 (1 day)
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index 328c269aac80d..2ae675f80c0f8 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -742,7 +742,6 @@ Gitea 创建以下非唯一队列:
 
 - `PROVIDER`: **memory**:会话存储引擎 \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]。设置为 `db` 将会重用 `[database]` 的配置信息。
 - `PROVIDER_CONFIG`: **data/sessions**:对于文件,为根路径;对于 db,为空(将使用数据库配置);对于其他引擎,为连接字符串。相对路径将根据 _`AppWorkPath`_ 绝对化。
-- `COOKIE_SECURE`: **false**:启用此选项以强制在所有会话访问中使用 HTTPS。
 - `COOKIE_NAME`: **i\_like\_gitea**:用于会话 ID 的 cookie 名称。
 - `GC_INTERVAL_TIME`: **86400**:GC 间隔时间,以秒为单位。
 - `SESSION_LIFE_TIME`: **86400**:会话生命周期,以秒为单位,默认为 86400(1 天)。
diff --git a/modules/context/context.go b/modules/context/context.go
index 47ad310b095a2..5b7b0e215a3ed 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -133,17 +133,6 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
 // Contexter initializes a classic context for a request.
 func Contexter() func(next http.Handler) http.Handler {
 	rnd := templates.HTMLRenderer()
-	csrfOpts := CsrfOptions{
-		Secret:         setting.SecretKey,
-		Cookie:         setting.CSRFCookieName,
-		SetCookie:      true,
-		Secure:         setting.SessionConfig.Secure,
-		CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
-		Header:         "X-Csrf-Token",
-		CookieDomain:   setting.SessionConfig.Domain,
-		CookiePath:     setting.SessionConfig.CookiePath,
-		SameSite:       setting.SessionConfig.SameSite,
-	}
 	if !setting.IsProd {
 		CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
 	}
@@ -166,6 +155,17 @@ func Contexter() func(next http.Handler) http.Handler {
 			ctx.Base.AppendContextValue(WebContextKey, ctx)
 			ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
 
+			csrfOpts := CsrfOptions{
+				Secret:         setting.SecretKey,
+				Cookie:         setting.CSRFCookieName,
+				SetCookie:      true,
+				Secure:         middleware.GetCookieSecure(ctx.Req),
+				Header:         "X-Csrf-Token",
+				CookieDomain:   setting.SessionConfig.Domain,
+				CookiePath:     setting.SessionConfig.CookiePath,
+				CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
+				SameSite:       setting.SessionConfig.SameSite,
+			}
 			ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
 
 			// Get the last flash message from cookie
@@ -185,9 +185,9 @@ func Contexter() func(next http.Handler) http.Handler {
 			// if there are new messages in the ctx.Flash, write them into cookie
 			ctx.Resp.Before(func(resp ResponseWriter) {
 				if val := ctx.Flash.Encode(); val != "" {
-					middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
+					middleware.SetSiteCookie(ctx.Resp, ctx.Req, CookieNameFlash, val, 0)
 				} else if lastFlashCookie != "" {
-					middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
+					middleware.SetSiteCookie(ctx.Resp, ctx.Req, CookieNameFlash, "", -1)
 				}
 			})
 
diff --git a/modules/context/context_cookie.go b/modules/context/context_cookie.go
index 9ce67a5298154..c0327e2116811 100644
--- a/modules/context/context_cookie.go
+++ b/modules/context/context_cookie.go
@@ -32,13 +32,13 @@ func removeSessionCookieHeader(w http.ResponseWriter) {
 // SetSiteCookie convenience function to set most cookies consistently
 // CSRF and a few others are the exception here
 func (ctx *Context) SetSiteCookie(name, value string, maxAge int) {
-	middleware.SetSiteCookie(ctx.Resp, name, value, maxAge)
+	middleware.SetSiteCookie(ctx.Resp, ctx.Req, name, value, maxAge)
 }
 
 // DeleteSiteCookie convenience function to delete most cookies consistently
 // CSRF and a few others are the exception here
 func (ctx *Context) DeleteSiteCookie(name string) {
-	middleware.SetSiteCookie(ctx.Resp, name, "", -1)
+	middleware.SetSiteCookie(ctx.Resp, ctx.Req, name, "", -1)
 }
 
 // GetSiteCookie returns given cookie value from request header.
diff --git a/modules/setting/session.go b/modules/setting/session.go
index d0bc938973ac1..ee498e1e1446a 100644
--- a/modules/setting/session.go
+++ b/modules/setting/session.go
@@ -27,8 +27,6 @@ var SessionConfig = struct {
 	Gclifetime int64
 	// Max life time in seconds. Default is whatever GC interval time is.
 	Maxlifetime int64
-	// Use HTTPS only. Default is false.
-	Secure bool
 	// Cookie domain name. Default is empty.
 	Domain string
 	// SameSite declares if your cookie should be restricted to a first-party or same-site context. Valid strings are "none", "lax", "strict". Default is "lax"
@@ -50,7 +48,6 @@ func loadSessionFrom(rootCfg ConfigProvider) {
 	}
 	SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
 	SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash
-	SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(false)
 	SessionConfig.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(86400)
 	SessionConfig.Maxlifetime = sec.Key("SESSION_LIFE_TIME").MustInt64(86400)
 	SessionConfig.Domain = sec.Key("DOMAIN").String()
diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go
index 621640895b95f..d96dbb198b05c 100644
--- a/modules/web/middleware/cookie.go
+++ b/modules/web/middleware/cookie.go
@@ -13,13 +13,13 @@ import (
 )
 
 // SetRedirectToCookie convenience function to set the RedirectTo cookie consistently
-func SetRedirectToCookie(resp http.ResponseWriter, value string) {
-	SetSiteCookie(resp, "redirect_to", value, 0)
+func SetRedirectToCookie(resp http.ResponseWriter, req *http.Request, value string) {
+	SetSiteCookie(resp, req, "redirect_to", value, 0)
 }
 
 // DeleteRedirectToCookie convenience function to delete most cookies consistently
-func DeleteRedirectToCookie(resp http.ResponseWriter) {
-	SetSiteCookie(resp, "redirect_to", "", -1)
+func DeleteRedirectToCookie(resp http.ResponseWriter, req *http.Request) {
+	SetSiteCookie(resp, req, "redirect_to", "", -1)
 }
 
 // GetSiteCookie returns given cookie value from request header.
@@ -32,15 +32,26 @@ func GetSiteCookie(req *http.Request, name string) string {
 	return val
 }
 
+// GetCookieSecure returns whether the "Secure" attribute on a cookie should be set
+func GetCookieSecure(req *http.Request) bool {
+	forwardedProto := req.Header.Get("x-forwarded-proto")
+	if forwardedProto != "" {
+		return forwardedProto == "https"
+	} else {
+		return req.TLS != nil
+	}
+}
+
 // SetSiteCookie returns given cookie value from request header.
-func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
+func SetSiteCookie(resp http.ResponseWriter, req *http.Request, name, value string, maxAge int) {
+
 	cookie := &http.Cookie{
 		Name:     name,
 		Value:    url.QueryEscape(value),
 		MaxAge:   maxAge,
 		Path:     setting.SessionConfig.CookiePath,
 		Domain:   setting.SessionConfig.Domain,
-		Secure:   setting.SessionConfig.Secure,
+		Secure:   GetCookieSecure(req),
 		HttpOnly: true,
 		SameSite: setting.SessionConfig.SameSite,
 	}
diff --git a/modules/web/middleware/locale.go b/modules/web/middleware/locale.go
index 34a16f04e7fac..d2da9244eef67 100644
--- a/modules/web/middleware/locale.go
+++ b/modules/web/middleware/locale.go
@@ -41,19 +41,19 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
 	}
 
 	if changeLang {
-		SetLocaleCookie(resp, lang, 1<<31-1)
+		SetLocaleCookie(resp, req, lang, 1<<31-1)
 	}
 
 	return translation.NewLocale(lang)
 }
 
 // SetLocaleCookie convenience function to set the locale cookie consistently
-func SetLocaleCookie(resp http.ResponseWriter, lang string, maxAge int) {
-	SetSiteCookie(resp, "lang", lang, maxAge)
+func SetLocaleCookie(resp http.ResponseWriter, req *http.Request, lang string, maxAge int) {
+	SetSiteCookie(resp, req, "lang", lang, maxAge)
 }
 
 // DeleteLocaleCookie convenience function to delete the locale cookie consistently
 // Setting the lang cookie will trigger the middleware to reset the language to previous state.
-func DeleteLocaleCookie(resp http.ResponseWriter) {
-	SetSiteCookie(resp, "lang", "", -1)
+func DeleteLocaleCookie(resp http.ResponseWriter, req *http.Request) {
+	SetSiteCookie(resp, req, "lang", "", -1)
 }
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 4f5f0383e9aa2..6408f2122bcbc 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3109,7 +3109,6 @@ config.provider_config = Provider Config
 config.cookie_name = Cookie Name
 config.gc_interval_time = GC Interval Time
 config.session_life_time = Session Life Time
-config.https_only = HTTPS Only
 config.cookie_life_time = Cookie Life Time
 
 config.picture_config = Picture and Avatar Configuration
diff --git a/routers/common/middleware.go b/routers/common/middleware.go
index 8a39dda179893..2a8f7e610d84b 100644
--- a/routers/common/middleware.go
+++ b/routers/common/middleware.go
@@ -101,15 +101,20 @@ func stripSlashesMiddleware(next http.Handler) http.Handler {
 }
 
 func Sessioner() func(next http.Handler) http.Handler {
-	return session.Sessioner(session.Options{
-		Provider:       setting.SessionConfig.Provider,
-		ProviderConfig: setting.SessionConfig.ProviderConfig,
-		CookieName:     setting.SessionConfig.CookieName,
-		CookiePath:     setting.SessionConfig.CookiePath,
-		Gclifetime:     setting.SessionConfig.Gclifetime,
-		Maxlifetime:    setting.SessionConfig.Maxlifetime,
-		Secure:         setting.SessionConfig.Secure,
-		SameSite:       setting.SessionConfig.SameSite,
-		Domain:         setting.SessionConfig.Domain,
-	})
+	return func(next http.Handler) http.Handler {
+		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+			handler := session.Sessioner(session.Options{
+				Provider:       setting.SessionConfig.Provider,
+				ProviderConfig: setting.SessionConfig.ProviderConfig,
+				CookieName:     setting.SessionConfig.CookieName,
+				CookiePath:     setting.SessionConfig.CookiePath,
+				Gclifetime:     setting.SessionConfig.Gclifetime,
+				Maxlifetime:    setting.SessionConfig.Maxlifetime,
+				Secure:         middleware.GetCookieSecure(req),
+				SameSite:       setting.SessionConfig.SameSite,
+				Domain:         setting.SessionConfig.Domain,
+			})
+			handler.ServeHTTP(resp, req) // handler.ServeHTTP undefined
+		})
+	}
 }
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index c70a2d1c95125..f7ad076a41a5c 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -159,7 +159,6 @@ func Config(ctx *context.Context) {
 		sessionCfg.CookiePath = realSession.CookiePath
 		sessionCfg.Gclifetime = realSession.Gclifetime
 		sessionCfg.Maxlifetime = realSession.Maxlifetime
-		sessionCfg.Secure = realSession.Secure
 		sessionCfg.Domain = realSession.Domain
 	}
 	sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig)
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index c20a45ebc9721..1a74e1292b4d6 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -104,7 +104,7 @@ func resetLocale(ctx *context.Context, u *user_model.User) error {
 		}
 	}
 
-	middleware.SetLocaleCookie(ctx.Resp, u.Language, 0)
+	middleware.SetLocaleCookie(ctx.Resp, ctx.Req, u.Language, 0)
 
 	if ctx.Locale.Language() != u.Language {
 		ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
@@ -123,13 +123,13 @@ func checkAutoLogin(ctx *context.Context) bool {
 
 	redirectTo := ctx.FormString("redirect_to")
 	if len(redirectTo) > 0 {
-		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
+		middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, redirectTo)
 	} else {
 		redirectTo = ctx.GetSiteCookie("redirect_to")
 	}
 
 	if isSucceed {
-		middleware.DeleteRedirectToCookie(ctx.Resp)
+		middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 		ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
 		return true
 	}
@@ -323,7 +323,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
 		}
 	}
 
-	middleware.SetLocaleCookie(ctx.Resp, u.Language, 0)
+	middleware.SetLocaleCookie(ctx.Resp, ctx.Req, u.Language, 0)
 
 	if ctx.Locale.Language() != u.Language {
 		ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
@@ -340,7 +340,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
 	}
 
 	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
-		middleware.DeleteRedirectToCookie(ctx.Resp)
+		middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 		if obeyRedirect {
 			ctx.RedirectToFirst(redirectTo)
 		}
@@ -371,7 +371,7 @@ func HandleSignOut(ctx *context.Context) {
 	ctx.DeleteSiteCookie(setting.CookieUserName)
 	ctx.DeleteSiteCookie(setting.CookieRememberName)
 	ctx.Csrf.DeleteCookie(ctx)
-	middleware.DeleteRedirectToCookie(ctx.Resp)
+	middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 }
 
 // SignOut sign out from login status
@@ -400,7 +400,7 @@ func SignUp(ctx *context.Context) {
 
 	redirectTo := ctx.FormString("redirect_to")
 	if len(redirectTo) > 0 {
-		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
+		middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, redirectTo)
 	}
 
 	ctx.HTML(http.StatusOK, tplSignUp)
@@ -735,7 +735,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
 
 	ctx.Flash.Success(ctx.Tr("auth.account_activated"))
 	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
-		middleware.DeleteRedirectToCookie(ctx.Resp)
+		middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 		ctx.RedirectToFirst(redirectTo)
 		return
 	}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 78dc84472a129..a9219443e95ef 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -855,7 +855,7 @@ func SignInOAuth(ctx *context.Context) {
 
 	redirectTo := ctx.FormString("redirect_to")
 	if len(redirectTo) > 0 {
-		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
+		middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, redirectTo)
 	}
 
 	// try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
@@ -1163,7 +1163,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
 		}
 
 		if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
-			middleware.DeleteRedirectToCookie(ctx.Resp)
+			middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 			ctx.RedirectToFirst(redirectTo)
 			return
 		}
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 00fc17f098019..116f0dca968e4 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -45,13 +45,13 @@ func SignInOpenID(ctx *context.Context) {
 
 	redirectTo := ctx.FormString("redirect_to")
 	if len(redirectTo) > 0 {
-		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
+		middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, redirectTo)
 	} else {
 		redirectTo = ctx.GetSiteCookie("redirect_to")
 	}
 
 	if isSucceed {
-		middleware.DeleteRedirectToCookie(ctx.Resp)
+		middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 		ctx.RedirectToFirst(redirectTo)
 		return
 	}
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 1432338e70832..f5eb9f4282b6a 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -338,7 +338,7 @@ func MustChangePasswordPost(ctx *context.Context) {
 	log.Trace("User updated password: %s", u.Name)
 
 	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
-		middleware.DeleteRedirectToCookie(ctx.Resp)
+		middleware.DeleteRedirectToCookie(ctx.Resp, ctx.Req)
 		ctx.RedirectToFirst(redirectTo)
 		return
 	}
diff --git a/routers/web/home.go b/routers/web/home.go
index b94e3e9eb593d..bf47767568f52 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -41,7 +41,7 @@ func Home(ctx *context.Context) {
 		} else if ctx.Doer.MustChangePassword {
 			ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
 			ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
-			middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
+			middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, setting.AppSubURL+ctx.Req.URL.RequestURI())
 			ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
 		} else {
 			user.Dashboard(ctx)
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 61089d0947b5f..5125a8b9009e5 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -407,7 +407,7 @@ func UpdateUserLang(ctx *context.Context) {
 	}
 
 	// Update the language to the one we just set
-	middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0)
+	middleware.SetLocaleCookie(ctx.Resp, ctx.Req, ctx.Doer.Language, 0)
 
 	log.Trace("User settings updated: %s", ctx.Doer.Name)
 	ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).Tr("settings.update_language_success"))
diff --git a/services/auth/auth.go b/services/auth/auth.go
index c7fdc56cbed07..880c65ba468c5 100644
--- a/services/auth/auth.go
+++ b/services/auth/auth.go
@@ -89,7 +89,7 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
 		}
 	}
 
-	middleware.SetLocaleCookie(resp, user.Language, 0)
+	middleware.SetLocaleCookie(resp, req, user.Language, 0)
 
 	// Clear whatever CSRF has right now, force to generate a new one
 	if ctx := gitea_context.GetWebContext(req); ctx != nil {
diff --git a/services/auth/middleware.go b/services/auth/middleware.go
index 4a0b613fa662f..6455559154bef 100644
--- a/services/auth/middleware.go
+++ b/services/auth/middleware.go
@@ -108,7 +108,7 @@ func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) {
 					ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
 					ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
 					if ctx.Req.URL.Path != "/user/events" {
-						middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
+						middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, setting.AppSubURL+ctx.Req.URL.RequestURI())
 					}
 					ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
 					return
@@ -136,7 +136,7 @@ func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) {
 		if options.SignInRequired {
 			if !ctx.IsSigned {
 				if ctx.Req.URL.Path != "/user/events" {
-					middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
+					middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, setting.AppSubURL+ctx.Req.URL.RequestURI())
 				}
 				ctx.Redirect(setting.AppSubURL + "/user/login")
 				return
@@ -151,7 +151,7 @@ func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) {
 		if !options.SignOutRequired && !ctx.IsSigned &&
 			len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
 			if ctx.Req.URL.Path != "/user/events" {
-				middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
+				middleware.SetRedirectToCookie(ctx.Resp, ctx.Req, setting.AppSubURL+ctx.Req.URL.RequestURI())
 			}
 			ctx.Redirect(setting.AppSubURL + "/user/login")
 			return
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 36d9bcb8a5e2d..cdaca7e830282 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -280,8 +280,6 @@
 				<dd>{{.SessionConfig.Gclifetime}} {{.locale.Tr "tool.raw_seconds"}}</dd>
 				<dt>{{.locale.Tr "admin.config.session_life_time"}}</dt>
 				<dd>{{.SessionConfig.Maxlifetime}} {{.locale.Tr "tool.raw_seconds"}}</dd>
-				<dt>{{.locale.Tr "admin.config.https_only"}}</dt>
-				<dd>{{if .SessionConfig.Secure}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 			</dl>
 		</div>
 

From b58bec737e41e8eb4ffee1fb83fc49d6fe7c2ef6 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:10:07 +0200
Subject: [PATCH 2/7] make it compile

---
 routers/common/middleware.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/routers/common/middleware.go b/routers/common/middleware.go
index 2a8f7e610d84b..4ad93c0c8951e 100644
--- a/routers/common/middleware.go
+++ b/routers/common/middleware.go
@@ -114,7 +114,7 @@ func Sessioner() func(next http.Handler) http.Handler {
 				SameSite:       setting.SessionConfig.SameSite,
 				Domain:         setting.SessionConfig.Domain,
 			})
-			handler.ServeHTTP(resp, req) // handler.ServeHTTP undefined
+			handler(next).ServeHTTP(resp, req)
 		})
 	}
 }

From ecebb8f69d2b846570cf51f0cfd536137e74079e Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:17:24 +0200
Subject: [PATCH 3/7] restore previous order

---
 modules/context/context.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/context/context.go b/modules/context/context.go
index 5b7b0e215a3ed..0db022e3de84c 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -160,10 +160,10 @@ func Contexter() func(next http.Handler) http.Handler {
 				Cookie:         setting.CSRFCookieName,
 				SetCookie:      true,
 				Secure:         middleware.GetCookieSecure(ctx.Req),
+				CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
 				Header:         "X-Csrf-Token",
 				CookieDomain:   setting.SessionConfig.Domain,
 				CookiePath:     setting.SessionConfig.CookiePath,
-				CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
 				SameSite:       setting.SessionConfig.SameSite,
 			}
 			ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)

From f3029542cdd3ea9a1f14e1bfd68461a8eb208a8b Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:35:24 +0200
Subject: [PATCH 4/7] fix lint

---
 modules/web/middleware/cookie.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go
index d96dbb198b05c..0405ff433b9be 100644
--- a/modules/web/middleware/cookie.go
+++ b/modules/web/middleware/cookie.go
@@ -37,9 +37,8 @@ func GetCookieSecure(req *http.Request) bool {
 	forwardedProto := req.Header.Get("x-forwarded-proto")
 	if forwardedProto != "" {
 		return forwardedProto == "https"
-	} else {
-		return req.TLS != nil
 	}
+	return req.TLS != nil
 }
 
 // SetSiteCookie returns given cookie value from request header.

From c625510baa96e1c8e8c03ebc39b4ed975d2cfc3e Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:36:10 +0200
Subject: [PATCH 5/7] Update modules/web/middleware/cookie.go

---
 modules/web/middleware/cookie.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go
index 0405ff433b9be..a4003659e7c61 100644
--- a/modules/web/middleware/cookie.go
+++ b/modules/web/middleware/cookie.go
@@ -43,7 +43,6 @@ func GetCookieSecure(req *http.Request) bool {
 
 // SetSiteCookie returns given cookie value from request header.
 func SetSiteCookie(resp http.ResponseWriter, req *http.Request, name, value string, maxAge int) {
-
 	cookie := &http.Cookie{
 		Name:     name,
 		Value:    url.QueryEscape(value),

From ac5dc18f9a0012f28cca33e50f2d98be90a857b0 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:38:50 +0200
Subject: [PATCH 6/7] also accept wss

---
 modules/web/middleware/cookie.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go
index a4003659e7c61..b725c816a9d93 100644
--- a/modules/web/middleware/cookie.go
+++ b/modules/web/middleware/cookie.go
@@ -36,7 +36,7 @@ func GetSiteCookie(req *http.Request, name string) string {
 func GetCookieSecure(req *http.Request) bool {
 	forwardedProto := req.Header.Get("x-forwarded-proto")
 	if forwardedProto != "" {
-		return forwardedProto == "https"
+		return forwardedProto == "https" || forwardedProto == "wss"
 	}
 	return req.TLS != nil
 }

From bcea3bd89e9c43627dd7e263912b71df9c91757b Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Thu, 7 Sep 2023 01:39:58 +0200
Subject: [PATCH 7/7] lower

---
 modules/web/middleware/cookie.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go
index b725c816a9d93..a13d470f2eca4 100644
--- a/modules/web/middleware/cookie.go
+++ b/modules/web/middleware/cookie.go
@@ -34,7 +34,7 @@ func GetSiteCookie(req *http.Request, name string) string {
 
 // GetCookieSecure returns whether the "Secure" attribute on a cookie should be set
 func GetCookieSecure(req *http.Request) bool {
-	forwardedProto := req.Header.Get("x-forwarded-proto")
+	forwardedProto := strings.ToLower(req.Header.Get("x-forwarded-proto"))
 	if forwardedProto != "" {
 		return forwardedProto == "https" || forwardedProto == "wss"
 	}