@@ -12,6 +12,7 @@ import com.panomc.platform.util.Regexes
1212import com.panomc.platform.util.TextUtil
1313import io.vertx.core.http.Cookie
1414import io.vertx.core.http.CookieSameSite
15+ import io.vertx.core.http.HttpServerRequest
1516import io.vertx.ext.web.RoutingContext
1617import io.vertx.sqlclient.SqlClient
1718import org.springframework.beans.factory.config.ConfigurableBeanFactory
@@ -123,14 +124,7 @@ class AuthProvider(
123124 val response = routingContext.response()
124125 val request = routingContext.request()
125126 val domain = resolveCookieDomain(routingContext)
126- val forwardedProto = request.getHeader(" X-Forwarded-Proto" )
127- ?.split(" ," )
128- ?.firstOrNull()
129- ?.trim()
130- ?.lowercase()
131-
132- // Secure flag should follow the effective request protocol, not website-url config.
133- val isSecure = request.isSSL || forwardedProto == " https"
127+ val isSecure = effectiveConnectionIsSecure(request)
134128
135129 val authTokenCookie = Cookie .cookie(getJwtCookieName(isSecure), authToken)
136130 val csrfTokenCookie = Cookie .cookie(getCsrfCookieName(isSecure), csrfToken)
@@ -170,6 +164,61 @@ class AuthProvider(
170164 return true
171165 }
172166
167+ /* *
168+ * TLS is often terminated before Vert.x; infer HTTPS only from the connection and trusted proxy headers.
169+ * If a proxy explicitly says `http`, cookies stay non-secure. No `website-url` involvement.
170+ */
171+ private fun effectiveConnectionIsSecure (request : HttpServerRequest ): Boolean {
172+ if (request.isSSL) {
173+ return true
174+ }
175+
176+ val xfp = commaSeparatedTokens(request.getHeader(" X-Forwarded-Proto" ))
177+ if (xfp.any { it == " https" }) {
178+ return true
179+ }
180+ if (xfp.any { it == " http" }) {
181+ return false
182+ }
183+
184+ val xForwardedProtocol = commaSeparatedTokens(request.getHeader(" X-Forwarded-Protocol" ))
185+ if (xForwardedProtocol.any { it == " https" }) {
186+ return true
187+ }
188+ if (xForwardedProtocol.any { it == " http" }) {
189+ return false
190+ }
191+
192+ when (request.getHeader(" CF-Connecting-Proto" )?.trim()?.lowercase()) {
193+ " https" -> return true
194+ " http" -> return false
195+ else -> { /* continue */ }
196+ }
197+
198+ if (forwardedHeaderHasProtoHttps(request.getHeader(" Forwarded" ))) {
199+ return true
200+ }
201+
202+ return false
203+ }
204+
205+ private fun commaSeparatedTokens (header : String? ): List <String > =
206+ header?.split(" ," )?.map { it.trim().lowercase() }?.filter { it.isNotEmpty() }.orEmpty()
207+
208+ private fun forwardedHeaderHasProtoHttps (header : String? ): Boolean {
209+ if (header.isNullOrBlank()) {
210+ return false
211+ }
212+ return header.split(" ," ).any { part ->
213+ part.split(" ;" ).any { param ->
214+ val kv = param.trim().split(" =" , limit = 2 )
215+ kv.size == 2 &&
216+ kv[0 ].trim().equals(" proto" , ignoreCase = true ) &&
217+ kv[1 ].trim().equals(" https" , ignoreCase = true )
218+ }
219+ }
220+ }
221+
173222 private fun resolveCookieDomain (routingContext : RoutingContext ): String? {
174223 val remoteIP = getRemoteIP(routingContext)
175224
0 commit comments