fix: cookies Secure flag based on X-Forwarded-Proto, not dev_mode
Secure flag on session cookies broke HTTP LAN access — browsers refuse to send Secure cookies over plain HTTP, causing 401 redirect loop. Fix: check X-Forwarded-Proto header. Only set Secure when request came over HTTPS. HTTP on LAN works, HTTPS still gets Secure cookies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ab3310faac
commit
6487ae3673
@ -144,8 +144,19 @@ impl RpcHandler {
|
||||
Arc::clone(&self.mesh_service)
|
||||
}
|
||||
|
||||
fn cookie_suffix(&self) -> &'static str {
|
||||
if self.config.dev_mode { "" } else { "; Secure" }
|
||||
fn cookie_suffix_for_request(&self, headers: &hyper::header::HeaderMap) -> &'static str {
|
||||
// Only set Secure flag when the original request was over HTTPS.
|
||||
// Nginx sends X-Forwarded-Proto: https for HTTPS connections.
|
||||
// On LAN HTTP, Secure flag prevents browsers from sending cookies back.
|
||||
if self.config.dev_mode {
|
||||
return "";
|
||||
}
|
||||
if let Some(proto) = headers.get("x-forwarded-proto") {
|
||||
if proto.as_bytes() == b"https" {
|
||||
return "; Secure";
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
pub async fn handle(
|
||||
@ -155,6 +166,7 @@ impl RpcHandler {
|
||||
// Extract session cookie before consuming the request
|
||||
let (parts, body) = req.into_parts();
|
||||
let session_token = session::extract_session_cookie(&parts.headers);
|
||||
let secure_suffix = self.cookie_suffix_for_request(&parts.headers);
|
||||
|
||||
let body_bytes = hyper::body::to_bytes(body).await
|
||||
.context("Failed to read body")?;
|
||||
@ -327,6 +339,7 @@ impl RpcHandler {
|
||||
&login_params,
|
||||
&new_session_cookies,
|
||||
client_ip,
|
||||
secure_suffix,
|
||||
).await;
|
||||
|
||||
Ok(response)
|
||||
@ -372,6 +385,7 @@ impl RpcHandler {
|
||||
login_params: &Option<serde_json::Value>,
|
||||
new_session_cookies: &Option<(String, String)>,
|
||||
client_ip: std::net::IpAddr,
|
||||
secure_suffix: &str,
|
||||
) {
|
||||
// Track failed login attempts for rate limiting
|
||||
if method == "auth.login" && rpc_resp.error.is_some() {
|
||||
@ -391,8 +405,8 @@ impl RpcHandler {
|
||||
if let Ok(secret) = crate::totp::decrypt_secret(&totp_data, password) {
|
||||
let token = self.session_store.create_pending(secret).await;
|
||||
let csrf_token = derive_csrf_token(&token).await;
|
||||
self.set_session_cookie(response, &token);
|
||||
self.set_csrf_cookie(response, &csrf_token);
|
||||
self.set_session_cookie(response, &token, secure_suffix);
|
||||
self.set_csrf_cookie(response, &csrf_token, secure_suffix);
|
||||
let totp_body = serde_json::json!({
|
||||
"result": { "requires_totp": true },
|
||||
"error": null
|
||||
@ -406,9 +420,9 @@ impl RpcHandler {
|
||||
let token = self.session_store.create().await;
|
||||
let csrf_token = derive_csrf_token(&token).await;
|
||||
let remember_token = self.session_store.create_remember_token().await;
|
||||
self.set_session_cookie(response, &token);
|
||||
self.set_csrf_cookie(response, &csrf_token);
|
||||
self.set_remember_cookie(response, &remember_token);
|
||||
self.set_session_cookie(response, &token, secure_suffix);
|
||||
self.set_csrf_cookie(response, &csrf_token, secure_suffix);
|
||||
self.set_remember_cookie(response, &remember_token, secure_suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,9 +440,9 @@ impl RpcHandler {
|
||||
if let Some(new_token) = new_token_opt {
|
||||
let csrf_token = derive_csrf_token(&new_token).await;
|
||||
let remember_token = self.session_store.create_remember_token().await;
|
||||
self.set_session_cookie(response, &new_token);
|
||||
self.set_csrf_cookie(response, &csrf_token);
|
||||
self.set_remember_cookie(response, &remember_token);
|
||||
self.set_session_cookie(response, &new_token, secure_suffix);
|
||||
self.set_csrf_cookie(response, &csrf_token, secure_suffix);
|
||||
self.set_remember_cookie(response, &remember_token, secure_suffix);
|
||||
// Strip the token from the response body
|
||||
if let Some(result) = rpc_resp.result.as_mut() {
|
||||
if let Some(obj) = result.as_object_mut() {
|
||||
@ -445,8 +459,8 @@ impl RpcHandler {
|
||||
if let Some(token) = session_token {
|
||||
let new_token = self.session_store.rotate(token).await;
|
||||
let csrf_token = derive_csrf_token(&new_token).await;
|
||||
self.set_session_cookie(response, &new_token);
|
||||
self.set_csrf_cookie(response, &csrf_token);
|
||||
self.set_session_cookie(response, &new_token, secure_suffix);
|
||||
self.set_csrf_cookie(response, &csrf_token, secure_suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +469,6 @@ impl RpcHandler {
|
||||
if let Some(token) = session_token {
|
||||
self.session_store.remove(token).await;
|
||||
}
|
||||
let secure_suffix = if self.config.dev_mode { "" } else { "; Secure" };
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
cookie_header(&format!("session=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0{}", secure_suffix)),
|
||||
@ -468,29 +481,29 @@ impl RpcHandler {
|
||||
|
||||
// If session was auto-restored from remember-me, set new cookies
|
||||
if let Some((new_session, new_csrf)) = new_session_cookies {
|
||||
self.set_session_cookie(response, new_session);
|
||||
self.set_csrf_cookie(response, new_csrf);
|
||||
self.set_session_cookie(response, new_session, secure_suffix);
|
||||
self.set_csrf_cookie(response, new_csrf, secure_suffix);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_session_cookie(&self, response: &mut Response<hyper::Body>, token: &str) {
|
||||
fn set_session_cookie(&self, response: &mut Response<hyper::Body>, token: &str, secure_suffix: &str) {
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
cookie_header(&format!("session={}; HttpOnly; SameSite=Lax; Path=/{}", token, self.cookie_suffix())),
|
||||
cookie_header(&format!("session={}; HttpOnly; SameSite=Lax; Path=/{}", token, secure_suffix)),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_csrf_cookie(&self, response: &mut Response<hyper::Body>, csrf_token: &str) {
|
||||
fn set_csrf_cookie(&self, response: &mut Response<hyper::Body>, csrf_token: &str, secure_suffix: &str) {
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
cookie_header(&format!("csrf_token={}; SameSite=Lax; Path=/{}", csrf_token, self.cookie_suffix())),
|
||||
cookie_header(&format!("csrf_token={}; SameSite=Lax; Path=/{}", csrf_token, secure_suffix)),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_remember_cookie(&self, response: &mut Response<hyper::Body>, remember_token: &str) {
|
||||
fn set_remember_cookie(&self, response: &mut Response<hyper::Body>, remember_token: &str, secure_suffix: &str) {
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
cookie_header(&format!("remember={}; HttpOnly; SameSite=Lax; Path=/; Max-Age={}{}", remember_token, REMEMBER_TTL, self.cookie_suffix())),
|
||||
cookie_header(&format!("remember={}; HttpOnly; SameSite=Lax; Path=/; Max-Age={}{}", remember_token, REMEMBER_TTL, secure_suffix)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user