1use std::{convert::Infallible, future::Future, rc::Rc, time::SystemTime};
48
49use cookie::{Cookie, CookieJar, Key, SameSite};
50use derive_more::{Display, From};
51use futures::future::{Ready, ok};
52use serde::{Deserialize, Serialize};
53use time::Duration;
54
55use ntex::http::header::{self, HeaderValue};
56use ntex::http::{HttpMessage, Payload, error::HttpError};
57use ntex::service::{Middleware, Service, ServiceCtx};
58use ntex::util::Extensions;
59use ntex::web::{
60 DefaultError, ErrorRenderer, FromRequest, HttpRequest, WebRequest, WebResponse,
61 WebResponseError,
62};
63
64#[derive(Clone)]
91pub struct Identity(HttpRequest);
92
93impl Identity {
94 pub fn identity(&self) -> Option<String> {
97 Identity::get_identity(&self.0.extensions())
98 }
99
100 pub fn remember(&self, identity: String) {
102 if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
103 id.id = Some(identity);
104 id.changed = true;
105 }
106 }
107
108 pub fn forget(&self) {
111 if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
112 id.id = None;
113 id.changed = true;
114 }
115 }
116
117 fn get_identity(extensions: &Extensions) -> Option<String> {
118 if let Some(id) = extensions.get::<IdentityItem>() { id.id.clone() } else { None }
119 }
120}
121
122struct IdentityItem {
123 id: Option<String>,
124 changed: bool,
125}
126
127pub trait RequestIdentity {
132 fn get_identity(&self) -> Option<String>;
133}
134
135impl<T> RequestIdentity for T
136where
137 T: HttpMessage,
138{
139 fn get_identity(&self) -> Option<String> {
140 Identity::get_identity(&self.message_extensions())
141 }
142}
143
144impl<Err: ErrorRenderer> FromRequest<Err> for Identity {
160 type Error = Infallible;
161
162 #[inline]
163 async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Identity, Infallible> {
164 Ok(Identity(req.clone()))
165 }
166}
167
168#[allow(clippy::wrong_self_convention)]
169pub trait IdentityPolicy<Err>: Sized + 'static {
171 type Future: Future<Output = Result<Option<String>, Self::Error>>;
173
174 type ResponseFuture: Future<Output = Result<(), Self::Error>>;
176
177 type Error;
179
180 fn from_request(&self, request: &mut WebRequest<Err>) -> Self::Future;
182
183 fn to_response(
185 &self,
186 identity: Option<String>,
187 changed: bool,
188 response: &mut WebResponse,
189 ) -> Self::ResponseFuture;
190}
191
192pub struct IdentityService<T> {
206 backend: Rc<T>,
207}
208
209impl<T> IdentityService<T> {
210 pub fn new(backend: T) -> Self {
212 IdentityService { backend: Rc::new(backend) }
213 }
214}
215
216impl<S, C, T> Middleware<S, C> for IdentityService<T> {
217 type Service = IdentityServiceMiddleware<S, T>;
218
219 fn create(&self, service: S, _: C) -> Self::Service {
220 IdentityServiceMiddleware { service, backend: self.backend.clone() }
221 }
222}
223
224#[doc(hidden)]
225pub struct IdentityServiceMiddleware<S, T> {
226 backend: Rc<T>,
227 service: S,
228}
229
230impl<S: Clone, T> Clone for IdentityServiceMiddleware<S, T> {
231 fn clone(&self) -> Self {
232 Self { backend: self.backend.clone(), service: self.service.clone() }
233 }
234}
235
236impl<S, T, Err> Service<WebRequest<Err>> for IdentityServiceMiddleware<S, T>
237where
238 S: Service<WebRequest<Err>, Response = WebResponse> + 'static,
239 T: IdentityPolicy<Err>,
240 Err: ErrorRenderer,
241 Err::Container: From<S::Error>,
242 Err::Container: From<T::Error>,
243{
244 type Response = WebResponse;
245 type Error = S::Error;
246
247 ntex::forward_ready!(service);
248 ntex::forward_shutdown!(service);
249
250 async fn call(
251 &self,
252 mut req: WebRequest<Err>,
253 ctx: ServiceCtx<'_, Self>,
254 ) -> Result<Self::Response, Self::Error> {
255 match self.backend.from_request(&mut req).await {
256 Ok(id) => {
257 req.extensions_mut().insert(IdentityItem { id, changed: false });
258
259 let mut res = ctx.call(&self.service, req).await?;
261 let id = res.request().extensions_mut().remove::<IdentityItem>();
262
263 if let Some(id) = id {
264 match self.backend.to_response(id.id, id.changed, &mut res).await {
265 Ok(_) => Ok(res),
266 Err(e) => Ok(WebResponse::error_response::<Err, _>(res, e)),
267 }
268 } else {
269 Ok(res)
270 }
271 }
272 Err(err) => Ok(req.error_response(err)),
273 }
274 }
275}
276
277struct CookieIdentityInner {
278 key: Key,
279 key_v2: Key,
280 name: String,
281 path: String,
282 domain: Option<String>,
283 secure: bool,
284 max_age: Option<Duration>,
285 same_site: Option<SameSite>,
286 visit_deadline: Option<Duration>,
287 login_deadline: Option<Duration>,
288}
289
290#[derive(Deserialize, Serialize, Debug)]
291struct CookieValue {
292 identity: String,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 login_timestamp: Option<SystemTime>,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 visit_timestamp: Option<SystemTime>,
297}
298
299#[derive(Debug)]
300struct CookieIdentityExtention {
301 login_timestamp: Option<SystemTime>,
302}
303
304impl CookieIdentityInner {
305 fn new(key: &[u8]) -> CookieIdentityInner {
306 let key_v2: Vec<u8> = key.iter().chain([1, 0, 0, 0].iter()).cloned().collect();
307 CookieIdentityInner {
308 key: Key::derive_from(key),
309 key_v2: Key::derive_from(&key_v2),
310 name: "ntex-identity".to_owned(),
311 path: "/".to_owned(),
312 domain: None,
313 secure: true,
314 max_age: None,
315 same_site: None,
316 visit_deadline: None,
317 login_deadline: None,
318 }
319 }
320
321 fn set_cookie(
322 &self,
323 resp: &mut WebResponse,
324 value: Option<CookieValue>,
325 ) -> Result<(), CookieIdentityPolicyError> {
326 let add_cookie = value.is_some();
327 let val = value.map(|val| {
328 if !self.legacy_supported() {
329 serde_json::to_string(&val)
330 } else {
331 Ok(val.identity)
332 }
333 });
334 let mut cookie =
335 Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
336 cookie.set_path(self.path.clone());
337 cookie.set_secure(self.secure);
338 cookie.set_http_only(true);
339 cookie.set_max_age(self.max_age);
340
341 if let Some(ref domain) = self.domain {
342 cookie.set_domain(domain.clone());
343 }
344
345 if let Some(same_site) = self.same_site {
346 cookie.set_same_site(same_site);
347 }
348
349 let mut jar = CookieJar::new();
350 let key = if self.legacy_supported() { &self.key } else { &self.key_v2 };
351 if add_cookie {
352 jar.private_mut(key).add(cookie);
353 } else {
354 jar.add_original(cookie.clone());
355 jar.private_mut(key).remove(cookie);
356 }
357 for cookie in jar.delta() {
358 let val = HeaderValue::from_str(&cookie.to_string()).map_err(HttpError::from)?;
359 resp.headers_mut().append(header::SET_COOKIE, val);
360 }
361 Ok(())
362 }
363
364 fn load<Err>(&self, req: &WebRequest<Err>) -> Option<CookieValue> {
365 let cookie = req.cookie(&self.name)?;
366 let mut jar = CookieJar::new();
367 jar.add_original(cookie.clone());
368 let res = if self.legacy_supported() {
369 jar.private(&self.key).get(&self.name).map(|n| CookieValue {
370 identity: n.value().to_string(),
371 login_timestamp: None,
372 visit_timestamp: None,
373 })
374 } else {
375 None
376 };
377 res.or_else(|| jar.private(&self.key_v2).get(&self.name).and_then(|c| self.parse(c)))
378 }
379
380 fn parse(&self, cookie: Cookie) -> Option<CookieValue> {
381 let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
382 let now = SystemTime::now();
383 if let Some(visit_deadline) = self.visit_deadline
384 && now.duration_since(value.visit_timestamp?).ok()? > visit_deadline
385 {
386 return None;
387 }
388 if let Some(login_deadline) = self.login_deadline
389 && now.duration_since(value.login_timestamp?).ok()? > login_deadline
390 {
391 return None;
392 }
393 Some(value)
394 }
395
396 fn legacy_supported(&self) -> bool {
397 self.visit_deadline.is_none() && self.login_deadline.is_none()
398 }
399
400 fn always_update_cookie(&self) -> bool {
401 self.visit_deadline.is_some()
402 }
403
404 fn requires_oob_data(&self) -> bool {
405 self.login_deadline.is_some()
406 }
407}
408
409pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
432
433#[derive(Debug, Display, From)]
434pub enum CookieIdentityPolicyError {
435 Http(HttpError),
436 Json(serde_json::error::Error),
437}
438
439impl WebResponseError<DefaultError> for CookieIdentityPolicyError {}
440
441impl CookieIdentityPolicy {
442 pub fn new(key: &[u8]) -> Self {
446 CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
447 }
448
449 pub fn path<S: Into<String>>(mut self, value: S) -> Self {
451 Rc::get_mut(&mut self.0).unwrap().path = value.into();
452 self
453 }
454
455 pub fn name<S: Into<String>>(mut self, value: S) -> Self {
457 Rc::get_mut(&mut self.0).unwrap().name = value.into();
458 self
459 }
460
461 pub fn domain<S: Into<String>>(mut self, value: S) -> Self {
463 Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
464 self
465 }
466
467 pub fn secure(mut self, value: bool) -> Self {
472 Rc::get_mut(&mut self.0).unwrap().secure = value;
473 self
474 }
475
476 pub fn max_age(self, seconds: i64) -> Self {
478 self.max_age_time(Duration::seconds(seconds))
479 }
480
481 pub fn max_age_time(mut self, value: Duration) -> Self {
483 Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
484 self
485 }
486
487 pub fn same_site(mut self, same_site: SameSite) -> Self {
489 Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
490 self
491 }
492
493 pub fn visit_deadline(mut self, value: Duration) -> Self {
497 Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value);
498 self
499 }
500
501 pub fn login_deadline(mut self, value: Duration) -> Self {
505 Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value);
506 self
507 }
508}
509
510impl<Err: ErrorRenderer> IdentityPolicy<Err> for CookieIdentityPolicy {
511 type Error = CookieIdentityPolicyError;
512 type Future = Ready<Result<Option<String>, CookieIdentityPolicyError>>;
513 type ResponseFuture = Ready<Result<(), CookieIdentityPolicyError>>;
514
515 fn from_request(&self, req: &mut WebRequest<Err>) -> Self::Future {
516 ok(self.0.load(req).map(|CookieValue { identity, login_timestamp, .. }| {
517 if self.0.requires_oob_data() {
518 req.extensions_mut().insert(CookieIdentityExtention { login_timestamp });
519 }
520 identity
521 }))
522 }
523
524 fn to_response(
525 &self,
526 id: Option<String>,
527 changed: bool,
528 res: &mut WebResponse,
529 ) -> Self::ResponseFuture {
530 let _ = if changed {
531 let login_timestamp = SystemTime::now();
532 self.0.set_cookie(
533 res,
534 id.map(|identity| CookieValue {
535 identity,
536 login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
537 visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
538 }),
539 )
540 } else if self.0.always_update_cookie() && id.is_some() {
541 let visit_timestamp = SystemTime::now();
542 let login_timestamp = if self.0.requires_oob_data() {
543 let CookieIdentityExtention { login_timestamp: lt } =
544 res.request().extensions_mut().remove().unwrap();
545 lt
546 } else {
547 None
548 };
549 self.0.set_cookie(
550 res,
551 Some(CookieValue {
552 identity: id.unwrap(),
553 login_timestamp,
554 visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
555 }),
556 )
557 } else {
558 Ok(())
559 };
560 ok(())
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use std::borrow::Borrow;
567
568 use super::*;
569 use ntex::web::test::{self, TestRequest};
570 use ntex::web::{self, App, Error, HttpResponse, error};
571 use ntex::{http::StatusCode, service::Pipeline, service::fn_service, time};
572
573 const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
574 const COOKIE_NAME: &str = "ntex_auth";
575 const COOKIE_LOGIN: &str = "test";
576
577 #[ntex::test]
578 async fn test_identity() {
579 let srv = test::init_service(
580 App::new()
581 .wrap(IdentityService::new(
582 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
583 .domain("www.rust-lang.org")
584 .name(COOKIE_NAME)
585 .path("/")
586 .secure(true),
587 ))
588 .service(web::resource("/index").to(|id: Identity| async move {
589 if id.identity().is_some() {
590 HttpResponse::Created()
591 } else {
592 HttpResponse::Ok()
593 }
594 }))
595 .service(web::resource("/login").to(|id: Identity| async move {
596 id.remember(COOKIE_LOGIN.to_string());
597 HttpResponse::Ok()
598 }))
599 .service(web::resource("/logout").to(|id: Identity| async move {
600 if id.identity().is_some() {
601 id.forget();
602 HttpResponse::Ok()
603 } else {
604 HttpResponse::BadRequest()
605 }
606 })),
607 )
608 .await;
609
610 let resp = test::call_service(&srv, TestRequest::with_uri("/index").to_request()).await;
611 assert_eq!(resp.status(), StatusCode::OK);
612
613 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
614 assert_eq!(resp.status(), StatusCode::OK);
615 let c = resp.response().cookies().next().unwrap().into_owned();
616
617 let resp = test::call_service(
618 &srv,
619 TestRequest::with_uri("/index").cookie(c.clone()).to_request(),
620 )
621 .await;
622 assert_eq!(resp.status(), StatusCode::CREATED);
623
624 let resp = test::call_service(
625 &srv,
626 TestRequest::with_uri("/logout").cookie(c.clone()).to_request(),
627 )
628 .await;
629 assert_eq!(resp.status(), StatusCode::OK);
630 assert!(resp.headers().contains_key(header::SET_COOKIE))
631 }
632
633 #[ntex::test]
634 async fn test_identity_max_age_time() {
635 let duration = Duration::days(1);
636 let srv = test::init_service(
637 App::new()
638 .wrap(IdentityService::new(
639 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
640 .domain("www.rust-lang.org")
641 .name(COOKIE_NAME)
642 .path("/")
643 .max_age_time(duration)
644 .secure(true),
645 ))
646 .service(web::resource("/login").to(|id: Identity| async move {
647 id.remember("test".to_string());
648 HttpResponse::Ok()
649 })),
650 )
651 .await;
652 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
653 assert_eq!(resp.status(), StatusCode::OK);
654 assert!(resp.headers().contains_key(header::SET_COOKIE));
655 let c = resp.response().cookies().next().unwrap().to_owned();
656 assert_eq!(duration, c.max_age().unwrap());
657 }
658
659 #[ntex::test]
660 async fn test_identity_max_age() {
661 let seconds = 60;
662 let srv = test::init_service(
663 App::new()
664 .wrap(IdentityService::new(
665 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
666 .domain("www.rust-lang.org")
667 .name(COOKIE_NAME)
668 .path("/")
669 .max_age(seconds)
670 .secure(true),
671 ))
672 .service(web::resource("/login").to(|id: Identity| async move {
673 id.remember("test".to_string());
674 HttpResponse::Ok()
675 })),
676 )
677 .await;
678 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
679 assert_eq!(resp.status(), StatusCode::OK);
680 assert!(resp.headers().contains_key(header::SET_COOKIE));
681 let c = resp.response().cookies().next().unwrap().to_owned();
682 assert_eq!(Duration::seconds(seconds), c.max_age().unwrap());
683 }
684
685 async fn create_identity_server<
686 F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static,
687 >(
688 f: F,
689 ) -> Pipeline<
690 impl ntex::service::Service<ntex::http::Request, Response = WebResponse, Error = Error>,
691 > {
692 test::init_service(
693 App::new()
694 .wrap(IdentityService::new(f(CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
695 .secure(false)
696 .name(COOKIE_NAME))))
697 .service(web::resource("/").to(|id: Identity| async move {
698 let identity = id.identity();
699 if identity.is_none() {
700 id.remember(COOKIE_LOGIN.to_string())
701 }
702 web::types::Json(identity)
703 })),
704 )
705 .await
706 }
707
708 fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
709 let mut jar = CookieJar::new();
710 jar.private_mut(&Key::derive_from(&COOKIE_KEY_MASTER))
711 .add(Cookie::new(COOKIE_NAME, identity));
712 jar.get(COOKIE_NAME).unwrap().clone()
713 }
714
715 fn login_cookie(
716 identity: &'static str,
717 login_timestamp: Option<SystemTime>,
718 visit_timestamp: Option<SystemTime>,
719 ) -> Cookie<'static> {
720 let mut jar = CookieJar::new();
721 let key: Vec<u8> =
722 COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).copied().collect();
723 jar.private_mut(&Key::derive_from(&key)).add(Cookie::new(
724 COOKIE_NAME,
725 serde_json::to_string(&CookieValue {
726 identity: identity.to_string(),
727 login_timestamp,
728 visit_timestamp,
729 })
730 .unwrap(),
731 ));
732 jar.get(COOKIE_NAME).unwrap().clone()
733 }
734
735 async fn assert_logged_in(response: WebResponse, identity: Option<&str>) {
736 let bytes = test::read_body(response).await;
737 let resp: Option<String> = serde_json::from_slice(&bytes[..]).unwrap();
738 assert_eq!(resp.as_ref().map(|s| s.borrow()), identity);
739 }
740
741 fn assert_legacy_login_cookie(response: &mut WebResponse, identity: &str) {
742 let mut cookies = CookieJar::new();
743 for cookie in response.headers().get_all(header::SET_COOKIE) {
744 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
745 }
746 let cookie =
747 cookies.private(&Key::derive_from(&COOKIE_KEY_MASTER)).get(COOKIE_NAME).unwrap();
748 assert_eq!(cookie.value(), identity);
749 }
750
751 enum LoginTimestampCheck {
752 Incorrect,
753 New,
754 Old(SystemTime),
755 }
756
757 enum VisitTimeStampCheck {
758 NoTimestamp,
759 NewTimestamp,
760 }
761
762 fn assert_login_cookie(
763 response: &mut WebResponse,
764 identity: &str,
765 login_timestamp: LoginTimestampCheck,
766 visit_timestamp: VisitTimeStampCheck,
767 ) {
768 let mut cookies = CookieJar::new();
769 for cookie in response.headers().get_all(header::SET_COOKIE) {
770 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
771 }
772 let key: Vec<u8> =
773 COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).copied().collect();
774 let cookie = cookies.private(&Key::derive_from(&key)).get(COOKIE_NAME).unwrap();
775 let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
776 assert_eq!(cv.identity, identity);
777 let now = SystemTime::now();
778 let t30sec_ago = now - Duration::seconds(30);
779 match login_timestamp {
780 LoginTimestampCheck::Incorrect => assert_eq!(cv.login_timestamp, None),
781 LoginTimestampCheck::New => assert!(
782 t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now
783 ),
784 LoginTimestampCheck::Old(old_timestamp) => {
785 assert_eq!(cv.login_timestamp, Some(old_timestamp))
786 }
787 }
788 match visit_timestamp {
789 VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
790 VisitTimeStampCheck::NewTimestamp => assert!(
791 t30sec_ago <= cv.visit_timestamp.unwrap() && cv.visit_timestamp.unwrap() <= now
792 ),
793 }
794 }
795
796 fn assert_no_login_cookie(response: &mut WebResponse) {
797 let mut cookies = CookieJar::new();
798 for cookie in response.headers().get_all(header::SET_COOKIE) {
799 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
800 }
801 assert!(cookies.get(COOKIE_NAME).is_none());
802 }
803
804 #[ntex::test]
805 async fn test_identity_legacy_cookie_is_set() {
806 let srv = create_identity_server(|c| c).await;
807 let mut resp = test::call_service(&srv, TestRequest::with_uri("/").to_request()).await;
808 assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
809 assert_logged_in(resp, None).await;
810 }
811
812 #[ntex::test]
813 async fn test_identity_legacy_cookie_works() {
814 let srv = create_identity_server(|c| c).await;
815 let cookie = legacy_login_cookie(COOKIE_LOGIN);
816 let mut resp = test::call_service(
817 &srv,
818 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
819 )
820 .await;
821 assert_no_login_cookie(&mut resp);
822 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
823 }
824
825 #[ntex::test]
826 async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
827 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
828 let cookie = legacy_login_cookie(COOKIE_LOGIN);
829 let mut resp = test::call_service(
830 &srv,
831 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
832 )
833 .await;
834 assert_login_cookie(
835 &mut resp,
836 COOKIE_LOGIN,
837 LoginTimestampCheck::Incorrect,
838 VisitTimeStampCheck::NewTimestamp,
839 );
840 assert_logged_in(resp, None).await;
841 }
842
843 #[ntex::test]
844 async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
845 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
846 let cookie = legacy_login_cookie(COOKIE_LOGIN);
847 let mut resp = test::call_service(
848 &srv,
849 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
850 )
851 .await;
852 assert_login_cookie(
853 &mut resp,
854 COOKIE_LOGIN,
855 LoginTimestampCheck::New,
856 VisitTimeStampCheck::NoTimestamp,
857 );
858 assert_logged_in(resp, None).await;
859 }
860
861 #[ntex::test]
862 async fn test_identity_cookie_rejected_if_login_timestamp_needed() {
863 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
864 let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
865 let mut resp = test::call_service(
866 &srv,
867 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
868 )
869 .await;
870 assert_login_cookie(
871 &mut resp,
872 COOKIE_LOGIN,
873 LoginTimestampCheck::New,
874 VisitTimeStampCheck::NoTimestamp,
875 );
876 assert_logged_in(resp, None).await;
877 }
878
879 #[ntex::test]
880 async fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
881 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
882 let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
883 let mut resp = test::call_service(
884 &srv,
885 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
886 )
887 .await;
888 assert_login_cookie(
889 &mut resp,
890 COOKIE_LOGIN,
891 LoginTimestampCheck::Incorrect,
892 VisitTimeStampCheck::NewTimestamp,
893 );
894 assert_logged_in(resp, None).await;
895 }
896
897 #[ntex::test]
898 async fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
899 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
900 let cookie =
901 login_cookie(COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180)), None);
902 let mut resp = test::call_service(
903 &srv,
904 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
905 )
906 .await;
907 assert_login_cookie(
908 &mut resp,
909 COOKIE_LOGIN,
910 LoginTimestampCheck::New,
911 VisitTimeStampCheck::NoTimestamp,
912 );
913 assert_logged_in(resp, None).await;
914 }
915
916 #[ntex::test]
917 async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
918 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
919 let cookie =
920 login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now() - Duration::days(180)));
921 let mut resp = test::call_service(
922 &srv,
923 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
924 )
925 .await;
926 assert_login_cookie(
927 &mut resp,
928 COOKIE_LOGIN,
929 LoginTimestampCheck::Incorrect,
930 VisitTimeStampCheck::NewTimestamp,
931 );
932 assert_logged_in(resp, None).await;
933 }
934
935 #[ntex::test]
936 async fn test_identity_cookie_not_updated_on_login_deadline() {
937 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
938 let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
939 let mut resp = test::call_service(
940 &srv,
941 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
942 )
943 .await;
944 assert_no_login_cookie(&mut resp);
945 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
946 }
947
948 #[ntex::test]
950 async fn test_identity_cookie_updated_on_visit_deadline() {
951 let srv = create_identity_server(|c| {
952 c.visit_deadline(Duration::days(90)).login_deadline(Duration::days(90))
953 })
954 .await;
955 let timestamp = SystemTime::now() - Duration::days(1);
956 let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
957 let mut resp = test::call_service(
958 &srv,
959 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
960 )
961 .await;
962 assert_login_cookie(
963 &mut resp,
964 COOKIE_LOGIN,
965 LoginTimestampCheck::Old(timestamp),
966 VisitTimeStampCheck::NewTimestamp,
967 );
968 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
969 }
970
971 #[ntex::test]
972 async fn test_borrowed_mut_error() {
973 use futures::future::{Ready, ok};
974 use ntex::web::{DefaultError, Error};
975
976 struct Ident;
977 impl<Err: ErrorRenderer> IdentityPolicy<Err> for Ident {
978 type Error = Error;
979 type Future = Ready<Result<Option<String>, Error>>;
980 type ResponseFuture = Ready<Result<(), Error>>;
981
982 fn from_request(&self, _: &mut WebRequest<Err>) -> Self::Future {
983 ok(Some("test".to_string()))
984 }
985
986 fn to_response(
987 &self,
988 _: Option<String>,
989 _: bool,
990 _: &mut WebResponse,
991 ) -> Self::ResponseFuture {
992 ok(())
993 }
994 }
995
996 let srv: Pipeline<_> = IdentityServiceMiddleware {
997 backend: Rc::new(Ident),
998 service: fn_service(|_: WebRequest<DefaultError>| async move {
999 time::sleep(time::Seconds(100)).await;
1000 Err::<WebResponse, _>(error::ErrorBadRequest("error"))
1001 }),
1002 }
1003 .into();
1004
1005 let srv2 = srv.clone();
1006 let req = TestRequest::default().to_srv_request();
1007 ntex::rt::spawn(async move {
1008 let _ = srv2.call(req).await;
1009 });
1010 time::sleep(time::Millis(50)).await;
1011
1012 srv.ready().await.expect("srv to be ready");
1013 }
1014}