Rocket - Multi-counter using secure cookies (in the client)



examples/rocket/multi-counter-using-encrypted-cookies/Cargo.toml
[package]
name = "multi-counter-using-encrypted-cookies"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rocket = { version = "0.5", features = ["secrets"] }

examples/rocket/multi-counter-using-encrypted-cookies/Rocket.toml
[debug]
secret_key = "qqrqdOg7fX4YNaDFzXf1mu6050BQ9okssS5sKkZFMVsd"

[release]
secret_key = "dOg7fX4YNaDFzXf1mu6050BQ9okssS5sKkZFMVsdpXg="

examples/rocket/multi-counter-using-encrypted-cookies/src/main.rs
#[macro_use]
extern crate rocket;

use rocket::http::CookieJar;

#[get("/")]
fn index(cookies: &CookieJar<'_>) -> String {
    let counter: u32 = match cookies.get_private("counter") {
        Some(cookie) => match cookie.value().parse() {
            Ok(val) => val,
            Err(_) => {
                eprintln!("Invalid value {} for the 'counter' cookie.", cookie.value());
                0
            }
        },
        None => 0,
    };
    let counter = counter + 1;
    cookies.add_private(("counter", counter.to_string()));

    format!("Counter: {}", counter)
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index])
}

#[cfg(test)]
mod tests;

examples/rocket/multi-counter-using-encrypted-cookies/src/tests.rs
use rocket::http::Status;
use rocket::local::blocking::Client;

#[test]
fn test_without_cookie() {
    let client = Client::tracked(super::rocket()).unwrap();
    let client = client;

    let response = client.get("/").dispatch();

    assert_eq!(response.status(), Status::Ok);

    // "counter=C3YSnaksM52BVeYOPjvcOp7pNRAez5ZB8aq+a+A%3D; HttpOnly; SameSite=Strict; Path=/; Expires=Mon, 15 Jan 2024 06:44:15 GMT"
    let cookie = response.headers().get_one("set-cookie").unwrap();
    assert!(cookie.contains("counter="));
    assert!(cookie.contains("; HttpOnly; SameSite=Strict; Path=/; Expires="));

    assert_eq!(response.into_string(), Some("Counter: 1".into()));
}

#[test]
fn test_with_cookie() {
    let client = Client::tracked(super::rocket()).unwrap();

    let response = client.get("/").private_cookie(("counter", "41")).dispatch();
    assert_eq!(response.status(), Status::Ok);

    assert_eq!(response.into_string(), Some("Counter: 42".into()));
}

#[test]
fn test_with_bad_cookie() {
    let client = Client::tracked(super::rocket()).unwrap();
    let response = client
        .get("/")
        .private_cookie(("counter", "bla"))
        .dispatch();

    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.into_string(), Some("Counter: 1".into()));
}

// I was expecting this too to work, but it does not, I get back "Counter 1" for the second request as well.
// #[test]
// fn test_counter() {
//     let client = Client::tracked(super::rocket()).unwrap();
//     let client = client;

//     let response = client.get("/").dispatch();

//     assert_eq!(response.status(), Status::Ok);

//     // "counter=C3YSnaksM52BVeYOPjvcOp7pNRAez5ZB8aq+a+A%3D; HttpOnly; SameSite=Strict; Path=/; Expires=Mon, 15 Jan 2024 06:44:15 GMT"
//     let cookie = response.headers().get_one("set-cookie").unwrap();
//     assert!(cookie.contains("counter="));
//     assert!(cookie.contains("; HttpOnly; SameSite=Strict; Path=/; Expires="));

//     let (head, _tail) = cookie.split_once(';').unwrap();
//     let (_head, tail) = head.split_once('=').unwrap();
//     let cookie_str = tail.to_string();

//     assert_eq!(response.into_string(), Some("Counter: 1".into()));

//     //assert_eq!(cookie_str, "");
//     let response = client.get("/").cookie(("counter", cookie_str)).dispatch();
//     assert_eq!(response.into_string(), Some("Counter: 2".into()));
// }