From f4bcebdecdc523a99dace5f8de8b3fb227b827f0 Mon Sep 17 00:00:00 2001 From: Bart Willems Date: Sun, 29 Mar 2020 14:36:01 +0200 Subject: [PATCH] allow user to set the cookie HttpOnly policy for the redis session (#36) * allow user to set the cookie HttpOnly policy for the redis session Signed-off-by: Bart Willems --- actix-redis/CHANGES.md | 5 ++ actix-redis/Cargo.toml | 2 + actix-redis/examples/authentication.rs | 96 ++++++++++++++++++++++++++ actix-redis/src/session.rs | 10 ++- 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 actix-redis/examples/authentication.rs diff --git a/actix-redis/CHANGES.md b/actix-redis/CHANGES.md index 99f0df6b2..19dd99218 100644 --- a/actix-redis/CHANGES.md +++ b/actix-redis/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [Unreleased] + +* Added `cookie_http_only` functionality to RedisSession builder, setting this + to false allows JavaScript to access cookies. Defaults to true. + ## [0.9.0-alpha.1] * Update `actix` to 0.10.0-alpha.2 diff --git a/actix-redis/Cargo.toml b/actix-redis/Cargo.toml index ffcbfb182..bc3f0d955 100644 --- a/actix-redis/Cargo.toml +++ b/actix-redis/Cargo.toml @@ -48,3 +48,5 @@ serde_json = { version = "1.0.40", optional = true } [dev-dependencies] env_logger = "0.7" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/actix-redis/examples/authentication.rs b/actix-redis/examples/authentication.rs new file mode 100644 index 000000000..a34b55309 --- /dev/null +++ b/actix-redis/examples/authentication.rs @@ -0,0 +1,96 @@ +#[macro_use] +extern crate serde_derive; + +use actix_redis::RedisSession; +use actix_session::Session; +use actix_web::{ + cookie, middleware, web, App, Error, HttpResponse, HttpServer, Responder, +}; + +#[derive(Deserialize)] +struct Credentials { + username: String, + password: String, +} + +#[derive(Serialize)] +struct User { + id: i64, + username: String, + password: String, +} + +impl User { + fn authenticate(credentials: Credentials) -> Result { + // TODO: figure out why I keep getting hacked + if &credentials.password != "hunter2" { + return Err(HttpResponse::Unauthorized().json("Unauthorized")); + } + + Ok(User { + id: 42, + username: credentials.username, + password: credentials.password, + }) + } +} + +pub fn validate_session(session: &Session) -> Result { + let user_id: Option = session.get("user_id").unwrap_or(None); + + match user_id { + Some(id) => { + // keep the user's session alive + session.renew(); + Ok(id) + } + None => Err(HttpResponse::Unauthorized().json("Unauthorized")), + } +} + +async fn login( + credentials: web::Json, + session: Session, +) -> Result { + let credentials = credentials.into_inner(); + + match User::authenticate(credentials) { + Ok(user) => session.set("user_id", user.id).unwrap(), + Err(_) => return Err(HttpResponse::Unauthorized().json("Unauthorized")), + }; + + Ok("Welcome!") +} + +/// some protected resource +async fn secret(session: Session) -> Result { + // only allow access to this resource if the user has an active session + validate_session(&session)?; + + Ok("secret revealed") +} + +#[actix_rt::main] +async fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + // enable logger + .wrap(middleware::Logger::default()) + // cookie session middleware + .wrap( + RedisSession::new("127.0.0.1:6379", &[0; 32]) + // allow the cookie to be accessed from javascript + .cookie_http_only(false) + // allow the cookie only from the current domain + .cookie_same_site(cookie::SameSite::Strict), + ) + .route("/login", web::post().to(login)) + .route("/secret", web::get().to(secret)) + }) + .bind("0.0.0.0:8080")? + .run() + .await +} diff --git a/actix-redis/src/session.rs b/actix-redis/src/session.rs index 159d6d71b..1230109e4 100644 --- a/actix-redis/src/session.rs +++ b/actix-redis/src/session.rs @@ -42,6 +42,7 @@ impl RedisSession { secure: false, max_age: Some(Duration::days(7)), same_site: None, + http_only: Some(true), })) } @@ -89,6 +90,12 @@ impl RedisSession { self } + /// Set custom cookie HttpOnly policy + pub fn cookie_http_only(mut self, http_only: bool) -> Self { + Rc::get_mut(&mut self.0).unwrap().http_only = Some(http_only); + self + } + /// Set a custom cache key generation strategy, expecting session key as input pub fn cache_keygen(mut self, keygen: Box String>) -> Self { Rc::get_mut(&mut self.0).unwrap().cache_keygen = keygen; @@ -205,6 +212,7 @@ struct Inner { secure: bool, max_age: Option, same_site: Option, + http_only: Option, } impl Inner { @@ -278,7 +286,7 @@ impl Inner { let mut cookie = Cookie::new(self.name.clone(), value.clone()); cookie.set_path(self.path.clone()); cookie.set_secure(self.secure); - cookie.set_http_only(true); + cookie.set_http_only(self.http_only.unwrap_or(true)); if let Some(ref domain) = self.domain { cookie.set_domain(domain.clone());