//! Request identity service for Actix applications. //! //! [**IdentityService**](struct.IdentityService.html) middleware can be //! used with different policies types to store identity information. //! //! By default, only cookie identity policy is implemented. Other backend //! implementations can be added separately. //! //! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) //! uses cookies as identity storage. //! //! To access current request identity //! [**Identity**](trait.Identity.html) extractor should be used. //! //! ```rust //! use actix_web::middleware::identity::Identity; //! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; //! use actix_web::*; //! //! fn index(id: Identity) -> String { //! // access request identity //! if let Some(id) = id.identity() { //! format!("Welcome! {}", id) //! } else { //! "Welcome Anonymous!".to_owned() //! } //! } //! //! fn login(id: Identity) -> HttpResponse { //! id.remember("User1".to_owned()); // <- remember identity //! HttpResponse::Ok().finish() //! } //! //! fn logout(id: Identity) -> HttpResponse { //! id.forget(); // <- remove identity //! HttpResponse::Ok().finish() //! } //! //! fn main() { //! let app = App::new().wrap(IdentityService::new( //! // <- create identity middleware //! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! .name("auth-cookie") //! .secure(false))) //! .service(web::resource("/index.html").to(index)) //! .service(web::resource("/login.html").to(login)) //! .service(web::resource("/logout.html").to(logout)); //! } //! ``` use std::cell::RefCell; use std::rc::Rc; use std::time::SystemTime; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; use serde::{Deserialize, Serialize}; use time::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; use crate::http::header::{self, HeaderValue}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{dev::Payload, FromRequest, HttpMessage, HttpRequest}; /// The extractor type to obtain your identity from a request. /// /// ```rust /// use actix_web::*; /// use actix_web::middleware::identity::Identity; /// /// fn index(id: Identity) -> Result { /// // access request identity /// if let Some(id) = id.identity() { /// Ok(format!("Welcome! {}", id)) /// } else { /// Ok("Welcome Anonymous!".to_owned()) /// } /// } /// /// fn login(id: Identity) -> HttpResponse { /// id.remember("User1".to_owned()); // <- remember identity /// HttpResponse::Ok().finish() /// } /// /// fn logout(id: Identity) -> HttpResponse { /// id.forget(); // <- remove identity /// HttpResponse::Ok().finish() /// } /// # fn main() {} /// ``` #[derive(Clone)] pub struct Identity(HttpRequest); impl Identity { /// Return the claimed identity of the user associated request or /// ``None`` if no identity can be found associated with the request. pub fn identity(&self) -> Option { if let Some(id) = self.0.extensions().get::() { id.id.clone() } else { None } } /// Remember identity. pub fn remember(&self, identity: String) { if let Some(id) = self.0.extensions_mut().get_mut::() { id.id = Some(identity); id.changed = true; } } /// This method is used to 'forget' the current identity on subsequent /// requests. pub fn forget(&self) { if let Some(id) = self.0.extensions_mut().get_mut::() { id.id = None; id.changed = true; } } } struct IdentityItem { id: Option, changed: bool, } /// Extractor implementation for Identity type. /// /// ```rust /// # use actix_web::*; /// use actix_web::middleware::identity::Identity; /// /// fn index(id: Identity) -> String { /// // access request identity /// if let Some(id) = id.identity() { /// format!("Welcome! {}", id) /// } else { /// "Welcome Anonymous!".to_owned() /// } /// } /// # fn main() {} /// ``` impl FromRequest for Identity { type Config = (); type Error = Error; type Future = Result; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { Ok(Identity(req.clone())) } } /// Identity policy definition. pub trait IdentityPolicy: Sized + 'static { /// The return type of the middleware type Future: IntoFuture, Error = Error>; /// The return type of the middleware type ResponseFuture: IntoFuture; /// Parse the session from request and load data from a service identity. fn from_request(&self, request: &mut ServiceRequest) -> Self::Future; /// Write changes to response fn to_response( &self, identity: Option, changed: bool, response: &mut ServiceResponse, ) -> Self::ResponseFuture; } /// Request identity middleware /// /// ```rust /// use actix_web::App; /// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// /// fn main() { /// let app = App::new().wrap(IdentityService::new( /// // <- create identity middleware /// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend /// .name("auth-cookie") /// .secure(false), /// )); /// } /// ``` pub struct IdentityService { backend: Rc, } impl IdentityService { /// Create new identity service with specified backend. pub fn new(backend: T) -> Self { IdentityService { backend: Rc::new(backend), } } } impl Transform for IdentityService where S: Service> + 'static, S::Future: 'static, S::Error: 'static, T: IdentityPolicy, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); type Transform = IdentityServiceMiddleware; type Future = FutureResult; fn new_transform(&self, service: S) -> Self::Future { ok(IdentityServiceMiddleware { backend: self.backend.clone(), service: Rc::new(RefCell::new(service)), }) } } #[doc(hidden)] pub struct IdentityServiceMiddleware { backend: Rc, service: Rc>, } impl Service for IdentityServiceMiddleware where B: 'static, S: Service> + 'static, S::Future: 'static, S::Error: 'static, T: IdentityPolicy, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.borrow_mut().poll_ready() } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let srv = self.service.clone(); let backend = self.backend.clone(); Box::new( self.backend.from_request(&mut req).into_future().then( move |res| match res { Ok(id) => { req.extensions_mut() .insert(IdentityItem { id, changed: false }); Either::A(srv.borrow_mut().call(req).and_then(move |mut res| { let id = res.request().extensions_mut().remove::(); if let Some(id) = id { return Either::A( backend .to_response(id.id, id.changed, &mut res) .into_future() .then(move |t| match t { Ok(_) => Ok(res), Err(e) => Ok(res.error_response(e)), }), ); } else { Either::B(ok(res)) } })) } Err(err) => Either::B(ok(req.error_response(err))), }, ), ) } } struct CookieIdentityInner { key: Key, key_v2: Key, name: String, path: String, domain: Option, secure: bool, max_age: Option, same_site: Option, visit_deadline: Option, login_deadline: Option, } #[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 { login_timestamp: Option, } impl CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner { let key_v2: Vec = key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); CookieIdentityInner { key: Key::from_master(key), key_v2: Key::from_master(&key_v2), name: "actix-identity".to_owned(), path: "/".to_owned(), domain: None, secure: true, max_age: None, same_site: None, visit_deadline: None, login_deadline: None, } } fn set_cookie( &self, resp: &mut ServiceResponse, value: Option, ) -> Result<()> { let add_cookie = value.is_some(); let val = value.map(|val| { if !self.legacy_supported() { serde_json::to_string(&val) } else { Ok(val.identity) } }); let mut cookie = Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?); cookie.set_path(self.path.clone()); cookie.set_secure(self.secure); cookie.set_http_only(true); if let Some(ref domain) = self.domain { cookie.set_domain(domain.clone()); } if let Some(max_age) = self.max_age { cookie.set_max_age(max_age); } if let Some(same_site) = self.same_site { cookie.set_same_site(same_site); } let mut jar = CookieJar::new(); let key = if self.legacy_supported() { &self.key } else { &self.key_v2 }; if add_cookie { jar.private(&key).add(cookie); } else { jar.add_original(cookie.clone()); jar.private(&key).remove(cookie); } for cookie in jar.delta() { let val = HeaderValue::from_str(&cookie.to_string())?; resp.headers_mut().append(header::SET_COOKIE, val); } Ok(()) } fn load(&self, req: &ServiceRequest) -> Option { let cookie = req.cookie(&self.name)?; let mut jar = CookieJar::new(); jar.add_original(cookie.clone()); let res = if self.legacy_supported() { jar.private(&self.key).get(&self.name).map(|n| CookieValue { identity: n.value().to_string(), login_timestamp: None, visit_timestamp: None, }) } else { None }; res.or_else(|| { jar.private(&self.key_v2) .get(&self.name) .and_then(|c| self.parse(c)) }) } fn parse(&self, cookie: Cookie) -> Option { let value: CookieValue = serde_json::from_str(cookie.value()).ok()?; let now = SystemTime::now(); if let Some(visit_deadline) = self.visit_deadline { if now.duration_since(value.visit_timestamp?).ok()? > visit_deadline.to_std().ok()? { return None; } } if let Some(login_deadline) = self.login_deadline { if now.duration_since(value.login_timestamp?).ok()? > login_deadline.to_std().ok()? { return None; } } Some(value) } fn legacy_supported(&self) -> bool { self.visit_deadline.is_none() && self.login_deadline.is_none() } fn always_update_cookie(&self) -> bool { self.visit_deadline.is_some() } fn requires_oob_data(&self) -> bool { self.login_deadline.is_some() } } /// Use cookies for request identity storage. /// /// The constructors take a key as an argument. /// This is the private key for cookie - when this value is changed, /// all identities are lost. The constructors will panic if the key is less /// than 32 bytes in length. /// /// # Example /// /// ```rust /// # extern crate actix_web; /// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// use actix_web::App; /// /// fn main() { /// let app = App::new().wrap(IdentityService::new( /// // <- create identity middleware /// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy /// .domain("www.rust-lang.org") /// .name("actix_auth") /// .path("/") /// .secure(true), /// )); /// } /// ``` pub struct CookieIdentityPolicy(Rc); impl CookieIdentityPolicy { /// Construct new `CookieIdentityPolicy` instance. /// /// Panics if key length is less than 32 bytes. pub fn new(key: &[u8]) -> CookieIdentityPolicy { CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) } /// Sets the `path` field in the session cookie being built. pub fn path>(mut self, value: S) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().path = value.into(); self } /// Sets the `name` field in the session cookie being built. pub fn name>(mut self, value: S) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().name = value.into(); self } /// Sets the `domain` field in the session cookie being built. pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); self } /// Sets the `secure` field in the session cookie being built. /// /// If the `secure` field is set, a cookie will only be transmitted when the /// connection is secure - i.e. `https` pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().secure = value; self } /// Sets the `max-age` field in the session cookie being built with given number of seconds. pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy { self.max_age_time(Duration::seconds(seconds)) } /// Sets the `max-age` field in the session cookie being built with `chrono::Duration`. pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); 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); self } /// Accepts only users whose cookie has been seen before the given deadline /// /// By default visit deadline is disabled. pub fn visit_deadline(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value); self } /// Accepts only users which has been authenticated before the given deadline /// /// By default login deadline is disabled. pub fn login_deadline(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value); self } } impl IdentityPolicy for CookieIdentityPolicy { type Future = Result, Error>; type ResponseFuture = Result<(), Error>; fn from_request(&self, req: &mut ServiceRequest) -> Self::Future { Ok(self.0.load(req).map( |CookieValue { identity, login_timestamp, .. }| { if self.0.requires_oob_data() { req.extensions_mut() .insert(CookieIdentityExtention { login_timestamp }); } identity }, )) } fn to_response( &self, id: Option, changed: bool, res: &mut ServiceResponse, ) -> Self::ResponseFuture { let _ = if changed { let login_timestamp = SystemTime::now(); self.0.set_cookie( res, id.map(|identity| CookieValue { identity, login_timestamp: self.0.login_deadline.map(|_| login_timestamp), visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp), }), ) } else if self.0.always_update_cookie() && id.is_some() { let visit_timestamp = SystemTime::now(); let mut login_timestamp = None; if self.0.requires_oob_data() { let CookieIdentityExtention { login_timestamp: lt, } = res.request().extensions_mut().remove().unwrap(); login_timestamp = lt; } self.0.set_cookie( res, Some(CookieValue { identity: id.unwrap(), login_timestamp, visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp), }), ) } else { Ok(()) }; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::http::StatusCode; use crate::test::{self, TestRequest}; use crate::{web, App, HttpResponse}; use std::borrow::Borrow; const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; const COOKIE_NAME: &'static str = "actix_auth"; const COOKIE_LOGIN: &'static str = "test"; #[test] fn test_identity() { 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("/") .secure(true), )) .service(web::resource("/index").to(|id: Identity| { if id.identity().is_some() { HttpResponse::Created() } else { HttpResponse::Ok() } })) .service(web::resource("/login").to(|id: Identity| { id.remember(COOKIE_LOGIN.to_string()); HttpResponse::Ok() })) .service(web::resource("/logout").to(|id: Identity| { if id.identity().is_some() { id.forget(); HttpResponse::Ok() } else { HttpResponse::BadRequest() } })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/index").to_request()); assert_eq!(resp.status(), StatusCode::OK); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); assert_eq!(resp.status(), StatusCode::OK); let c = resp.response().cookies().next().unwrap().to_owned(); let resp = test::call_service( &mut srv, TestRequest::with_uri("/index") .cookie(c.clone()) .to_request(), ); assert_eq!(resp.status(), StatusCode::CREATED); let resp = test::call_service( &mut srv, TestRequest::with_uri("/logout") .cookie(c.clone()) .to_request(), ); assert_eq!(resp.status(), StatusCode::OK); assert!(resp.headers().contains_key(header::SET_COOKIE)) } #[test] fn test_identity_max_age_time() { let duration = Duration::days(1); 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("/") .max_age_time(duration) .secure(true), )) .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); assert_eq!(resp.status(), StatusCode::OK); assert!(resp.headers().contains_key(header::SET_COOKIE)); let c = resp.response().cookies().next().unwrap().to_owned(); assert_eq!(duration, c.max_age().unwrap()); } #[test] fn test_identity_max_age() { let seconds = 60; 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("/") .max_age(seconds) .secure(true), )) .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); assert_eq!(resp.status(), StatusCode::OK); assert!(resp.headers().contains_key(header::SET_COOKIE)); let c = resp.response().cookies().next().unwrap().to_owned(); assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); } fn create_identity_server< F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static, >( f: F, ) -> impl actix_service::Service< Request = actix_http::Request, Response = ServiceResponse, Error = actix_http::Error, > { test::init_service( App::new() .wrap(IdentityService::new(f(CookieIdentityPolicy::new( &COOKIE_KEY_MASTER, ) .secure(false) .name(COOKIE_NAME)))) .service(web::resource("/").to(|id: Identity| { let identity = id.identity(); if identity.is_none() { id.remember(COOKIE_LOGIN.to_string()) } web::Json(identity) })), ) } fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> { let mut jar = CookieJar::new(); jar.private(&Key::from_master(&COOKIE_KEY_MASTER)) .add(Cookie::new(COOKIE_NAME, identity)); jar.get(COOKIE_NAME).unwrap().clone() } fn login_cookie( identity: &'static str, login_timestamp: Option, visit_timestamp: Option, ) -> Cookie<'static> { let mut jar = CookieJar::new(); let key: Vec = COOKIE_KEY_MASTER .iter() .chain([1, 0, 0, 0].iter()) .map(|e| *e) .collect(); jar.private(&Key::from_master(&key)).add(Cookie::new( COOKIE_NAME, serde_json::to_string(&CookieValue { identity: identity.to_string(), login_timestamp, visit_timestamp, }) .unwrap(), )); jar.get(COOKIE_NAME).unwrap().clone() } fn assert_logged_in(response: &mut ServiceResponse, identity: Option<&str>) { use bytes::BytesMut; use futures::Stream; let bytes = test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { b.extend(c); Ok::<_, Error>(b) })) .unwrap(); let resp: Option = serde_json::from_slice(&bytes[..]).unwrap(); assert_eq!(resp.as_ref().map(|s| s.borrow()), identity); } fn assert_legacy_login_cookie(response: &mut ServiceResponse, identity: &str) { let mut cookies = CookieJar::new(); for cookie in response.headers().get_all(header::SET_COOKIE) { cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); } let cookie = cookies .private(&Key::from_master(&COOKIE_KEY_MASTER)) .get(COOKIE_NAME) .unwrap(); assert_eq!(cookie.value(), identity); } enum LoginTimestampCheck { NoTimestamp, NewTimestamp, OldTimestamp(SystemTime), } enum VisitTimeStampCheck { NoTimestamp, NewTimestamp, } fn assert_login_cookie( response: &mut ServiceResponse, identity: &str, login_timestamp: LoginTimestampCheck, visit_timestamp: VisitTimeStampCheck, ) { let mut cookies = CookieJar::new(); for cookie in response.headers().get_all(header::SET_COOKIE) { cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); } let key: Vec = COOKIE_KEY_MASTER .iter() .chain([1, 0, 0, 0].iter()) .map(|e| *e) .collect(); let cookie = cookies .private(&Key::from_master(&key)) .get(COOKIE_NAME) .unwrap(); let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap(); assert_eq!(cv.identity, identity); let now = SystemTime::now(); let t30sec_ago = now - Duration::seconds(30).to_std().unwrap(); match login_timestamp { LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None), LoginTimestampCheck::NewTimestamp => assert!( t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now ), LoginTimestampCheck::OldTimestamp(old_timestamp) => { assert_eq!(cv.login_timestamp, Some(old_timestamp)) } } match visit_timestamp { VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None), VisitTimeStampCheck::NewTimestamp => assert!( t30sec_ago <= cv.visit_timestamp.unwrap() && cv.visit_timestamp.unwrap() <= now ), } } fn assert_no_login_cookie(response: &mut ServiceResponse) { let mut cookies = CookieJar::new(); for cookie in response.headers().get_all(header::SET_COOKIE) { cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); } assert!(cookies.get(COOKIE_NAME).is_none()); } #[test] fn test_identity_legacy_cookie_is_set() { let mut srv = create_identity_server(|c| c); let mut resp = test::call_service(&mut srv, TestRequest::with_uri("/").to_request()); assert_logged_in(&mut resp, None); assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); } #[test] fn test_identity_legacy_cookie_works() { let mut srv = create_identity_server(|c| c); let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); assert_no_login_cookie(&mut resp); } #[test] fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp, ); } #[test] fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp, ); } #[test] fn test_identity_cookie_rejected_if_login_timestamp_needed() { let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp, ); } #[test] fn test_identity_cookie_rejected_if_visit_timestamp_needed() { let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp, ); } #[test] fn test_identity_cookie_rejected_if_login_timestamp_too_old() { let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); let cookie = login_cookie( COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), None, ); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp, ); } #[test] fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); let cookie = login_cookie( COOKIE_LOGIN, None, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), ); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, None); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp, ); } #[test] fn test_identity_cookie_not_updated_on_login_deadline() { let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); assert_no_login_cookie(&mut resp); } #[test] fn test_identity_cookie_updated_on_visit_deadline() { let mut srv = create_identity_server(|c| { c.visit_deadline(Duration::days(90)) .login_deadline(Duration::days(90)) }); let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); assert_login_cookie( &mut resp, COOKIE_LOGIN, LoginTimestampCheck::OldTimestamp(timestamp), VisitTimeStampCheck::NewTimestamp, ); } }