Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ where
cookie.set_domain(domain);
}

// Secure must be set because the removal is a Set-Cookie,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment saying that secure must be true? If so, it doesn't look like the code change is actually enforcing that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's saying we must set secure based on the configured value. The wording was unclear. I've changed it to "Preserve the configured Secure attribute".

The reason is that some browsers enforce the cookie prefix rules for security, so if the user uses a cookie name like __Host-my-cookie, the browsers would reject the Set-Cookie if it doesn't have Secure. Since cookie removals are Set-Cookies too, they have to follow the same rules.

I think just using the user-configured secure would be enough, because the users would configure the attribute to whatever works for their use case (eg. secure=true for __Host-my-cookie, or secure=false for http), so the same attributes must also work for the removal Set-Cookie.

I've also added a test case for the secure=false case to better show we're not making it always true.

// and browsers reject `__Host-`/`__Secure-`-prefixed cookies without
// Secure.
cookie.set_secure(session_config.secure);

cookie_controller.remove(&cookies, cookie);
}

Expand Down Expand Up @@ -565,6 +570,17 @@ mod tests {
Ok(Response::new(Body::empty()))
}

async fn flush_handler(req: Request<Body>) -> anyhow::Result<Response<Body>> {
let session = req
.extensions()
.get::<Session>()
.ok_or(anyhow!("Missing session"))?;

session.flush().await?;

Ok(Response::new(Body::empty()))
}

#[tokio::test]
async fn basic_service_test() -> anyhow::Result<()> {
let session_store = MemoryStore::default();
Expand Down Expand Up @@ -890,6 +906,37 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn secure_removal_test() -> anyhow::Result<()> {
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store.clone()).with_secure(true);
let svc = ServiceBuilder::new()
.layer(session_layer)
.service_fn(handler);

let req = Request::builder().body(Body::empty())?;
let res = svc.oneshot(req).await?;

let session_cookie = res
.headers()
.get(http::header::SET_COOKIE)
.ok_or(anyhow!("Missing session cookie"))?;

let flush_layer = SessionManagerLayer::new(session_store).with_secure(true);
let flush_svc = ServiceBuilder::new()
.layer(flush_layer)
.service_fn(flush_handler);

let req = Request::builder()
.header(http::header::COOKIE, session_cookie)
.body(Body::empty())?;
let res = flush_svc.oneshot(req).await?;

assert!(cookie_value_matches(&res, |s| s.contains("Secure")));

Ok(())
}

#[tokio::test]
async fn path_test() -> anyhow::Result<()> {
let session_store = MemoryStore::default();
Expand Down