diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7ae3ac59b..c943393d0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -68,13 +68,13 @@ jobs: args: --all --all-features --no-fail-fast -- --nocapture - name: Generate coverage file - if: matrix.version == 'stable' && (github.ref == 'master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml --workspace --all-features - name: Upload to Codecov - if: matrix.version == 'stable' && (github.ref == 'master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') uses: codecov/codecov-action@v1 with: file: cobertura.xml diff --git a/actix-identity/CHANGES.md b/actix-identity/CHANGES.md index d79b5d8ff..7639f474b 100644 --- a/actix-identity/CHANGES.md +++ b/actix-identity/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx +* Add method to set HttpOnly flag on cookie identity. [#102] ## 0.3.0 - 2020-09-11 @@ -24,3 +25,8 @@ ## 0.1.0 - 2019-06-xx * Move identity middleware to separate crate + + + + +[#102]: https://github.com/actix/actix-extras/pull/102 diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 45a3a8f36..42060554e 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -47,7 +47,6 @@ //! } //! ``` -#![allow(clippy::needless_doctest_main)] #![deny(rust_2018_idioms)] use std::cell::RefCell; @@ -319,6 +318,7 @@ struct CookieIdentityInner { domain: Option, secure: bool, max_age: Option, + http_only: Option, same_site: Option, visit_deadline: Option, login_deadline: Option, @@ -327,14 +327,16 @@ struct CookieIdentityInner { #[derive(Deserialize, Serialize, Debug)] struct CookieValue { identity: String, + #[serde(skip_serializing_if = "Option::is_none")] login_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] visit_timestamp: Option, } #[derive(Debug)] -struct CookieIdentityExtention { +struct CookieIdentityExtension { login_timestamp: Option, } @@ -349,6 +351,7 @@ impl CookieIdentityInner { domain: None, secure: true, max_age: None, + http_only: None, same_site: None, visit_deadline: None, login_deadline: None, @@ -382,6 +385,10 @@ impl CookieIdentityInner { cookie.set_max_age(max_age); } + if let Some(http_only) = self.http_only { + cookie.set_http_only(http_only); + } + if let Some(same_site) = self.same_site { cookie.set_same_site(same_site); } @@ -524,6 +531,12 @@ impl CookieIdentityPolicy { self } + /// Sets the `http_only` field in the session cookie being built. + pub fn http_only(mut self, http_only: bool) -> Self { + Rc::get_mut(&mut self.0).unwrap().http_only = Some(http_only); + self + } + /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, same_site: SameSite) -> Self { Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); @@ -560,7 +573,7 @@ impl IdentityPolicy for CookieIdentityPolicy { }| { if self.0.requires_oob_data() { req.extensions_mut() - .insert(CookieIdentityExtention { login_timestamp }); + .insert(CookieIdentityExtension { login_timestamp }); } identity }, @@ -586,7 +599,7 @@ impl IdentityPolicy for CookieIdentityPolicy { } else if self.0.always_update_cookie() && id.is_some() { let visit_timestamp = SystemTime::now(); let login_timestamp = if self.0.requires_oob_data() { - let CookieIdentityExtention { + let CookieIdentityExtension { login_timestamp: lt, } = res.request().extensions_mut().remove().unwrap(); lt @@ -713,6 +726,37 @@ mod tests { assert_eq!(duration, c.max_age().unwrap()); } + #[actix_rt::test] + async fn test_http_only_same_site() { + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .http_only(true) + .same_site(SameSite::None), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })), + ) + .await; + + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()) + .await; + + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + + let c = resp.response().cookies().next().unwrap().to_owned(); + assert!(c.http_only().unwrap()); + assert_eq!(SameSite::None, c.same_site().unwrap()); + } + #[actix_rt::test] async fn test_identity_max_age() { let seconds = 60;