1#![cfg_attr(test, allow(clippy::unwrap_used))]
2#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
63#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
64#![cfg_attr(docsrs, feature(doc_cfg))]
65
66use std::fmt::{self, Formatter};
67use std::time::Duration;
68
69use cookie::{Cookie, Key, KeyError, SameSite};
70use salvo_core::http::uri::Scheme;
71use salvo_core::{Depot, Error, FlowCtrl, Handler, Request, Response, async_trait};
72use saysion::base64::Engine as _;
73use saysion::base64::engine::general_purpose;
74use saysion::hmac::{Hmac, Mac};
75use saysion::sha2::Sha256;
76pub use saysion::{CookieStore, MemoryStore, Session, SessionStore};
77
78pub const SESSION_KEY: &str = "::salvo::session";
80const BASE64_DIGEST_LEN: usize = 44;
81
82pub trait SessionDepotExt {
84 fn set_session(&mut self, session: Session) -> &mut Self;
86 fn take_session(&mut self) -> Option<Session>;
88 fn session(&self) -> Option<&Session>;
90 fn session_mut(&mut self) -> Option<&mut Session>;
92}
93
94impl SessionDepotExt for Depot {
95 #[inline]
96 fn set_session(&mut self, session: Session) -> &mut Self {
97 self.insert(SESSION_KEY, session);
98 self
99 }
100 #[inline]
101 fn take_session(&mut self) -> Option<Session> {
102 self.remove(SESSION_KEY).ok()
103 }
104 #[inline]
105 fn session(&self) -> Option<&Session> {
106 self.get(SESSION_KEY).ok()
107 }
108 #[inline]
109 fn session_mut(&mut self) -> Option<&mut Session> {
110 self.get_mut(SESSION_KEY).ok()
111 }
112}
113
114pub struct HandlerBuilder<S> {
116 store: S,
117 cookie_path: String,
118 cookie_name: String,
119 cookie_domain: Option<String>,
120 session_ttl: Option<Duration>,
121 save_unchanged: bool,
122 same_site_policy: SameSite,
123 key: Key,
124 fallback_keys: Vec<Key>,
125}
126impl<S> fmt::Debug for HandlerBuilder<S>
127where
128 S: SessionStore + fmt::Debug,
129{
130 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
131 f.debug_struct("HandlerBuilder")
132 .field("store", &self.store)
133 .field("cookie_path", &self.cookie_path)
134 .field("cookie_name", &self.cookie_name)
135 .field("cookie_domain", &self.cookie_domain)
136 .field("session_ttl", &self.session_ttl)
137 .field("same_site_policy", &self.same_site_policy)
138 .field("key", &"..")
139 .field("fallback_keys", &"..")
140 .field("save_unchanged", &self.save_unchanged)
141 .finish()
142 }
143}
144
145impl<S> HandlerBuilder<S>
146where
147 S: SessionStore,
148{
149 #[inline]
163 #[must_use]
164 pub fn new(store: S, secret: &[u8]) -> Self {
165 Self::try_new(store, secret).expect("secret key must be at least 32 bytes")
166 }
167
168 #[inline]
181 pub fn try_new(store: S, secret: &[u8]) -> Result<Self, KeyError> {
182 let key = Key::try_from(secret)?;
183 Ok(Self {
184 store,
185 save_unchanged: true,
186 cookie_path: "/".into(),
187 cookie_name: "salvo.session.id".into(),
188 cookie_domain: None,
189 same_site_policy: SameSite::Lax,
190 session_ttl: Some(Duration::from_secs(24 * 60 * 60)),
191 key,
192 fallback_keys: vec![],
193 })
194 }
195
196 #[inline]
200 #[must_use]
201 pub fn cookie_path(mut self, cookie_path: impl Into<String>) -> Self {
202 self.cookie_path = cookie_path.into();
203 self
204 }
205
206 #[inline]
212 #[must_use]
213 pub fn session_ttl(mut self, session_ttl: Option<Duration>) -> Self {
214 self.session_ttl = session_ttl;
215 self
216 }
217
218 #[inline]
224 #[must_use]
225 pub fn cookie_name(mut self, cookie_name: impl Into<String>) -> Self {
226 self.cookie_name = cookie_name.into();
227 self
228 }
229
230 #[inline]
240 #[must_use]
241 pub fn save_unchanged(mut self, value: bool) -> Self {
242 self.save_unchanged = value;
243 self
244 }
245
246 #[inline]
251 #[must_use]
252 pub fn same_site_policy(mut self, policy: SameSite) -> Self {
253 self.same_site_policy = policy;
254 self
255 }
256
257 #[inline]
259 #[must_use]
260 pub fn cookie_domain(mut self, cookie_domain: impl AsRef<str>) -> Self {
261 self.cookie_domain = Some(cookie_domain.as_ref().to_owned());
262 self
263 }
264 #[inline]
266 #[must_use]
267 pub fn fallback_keys(mut self, keys: Vec<impl Into<Key>>) -> Self {
268 self.fallback_keys = keys.into_iter().map(|s| s.into()).collect();
269 self
270 }
271
272 #[inline]
274 #[must_use]
275 pub fn add_fallback_key(mut self, key: impl Into<Key>) -> Self {
276 self.fallback_keys.push(key.into());
277 self
278 }
279
280 pub fn build(self) -> Result<SessionHandler<S>, Error> {
282 let Self {
283 store,
284 save_unchanged,
285 cookie_path,
286 cookie_name,
287 cookie_domain,
288 session_ttl,
289 same_site_policy,
290 key,
291 fallback_keys,
292 } = self;
293 let hmac = Hmac::<Sha256>::new_from_slice(key.signing())
294 .map_err(|_| Error::Other("invalid key length".into()))?;
295 let fallback_hmacs = fallback_keys
296 .iter()
297 .map(|key| Hmac::<Sha256>::new_from_slice(key.signing()))
298 .collect::<Result<Vec<_>, _>>()
299 .map_err(|_| Error::Other("invalid key length".into()))?;
300 Ok(SessionHandler {
301 store,
302 save_unchanged,
303 cookie_path,
304 cookie_name,
305 cookie_domain,
306 session_ttl,
307 same_site_policy,
308 hmac,
309 fallback_hmacs,
310 })
311 }
312}
313
314pub struct SessionHandler<S> {
316 store: S,
317 cookie_path: String,
318 cookie_name: String,
319 cookie_domain: Option<String>,
320 session_ttl: Option<Duration>,
321 save_unchanged: bool,
322 same_site_policy: SameSite,
323 hmac: Hmac<Sha256>,
324 fallback_hmacs: Vec<Hmac<Sha256>>,
325}
326impl<S> fmt::Debug for SessionHandler<S>
327where
328 S: SessionStore + fmt::Debug,
329{
330 #[inline]
331 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
332 f.debug_struct("SessionHandler")
333 .field("store", &self.store)
334 .field("cookie_path", &self.cookie_path)
335 .field("cookie_name", &self.cookie_name)
336 .field("cookie_domain", &self.cookie_domain)
337 .field("session_ttl", &self.session_ttl)
338 .field("same_site_policy", &self.same_site_policy)
339 .field("key", &"..")
340 .field("fallback_keys", &"..")
341 .field("save_unchanged", &self.save_unchanged)
342 .finish()
343 }
344}
345#[async_trait]
346impl<S> Handler for SessionHandler<S>
347where
348 S: SessionStore + Send + Sync + 'static,
349{
350 async fn handle(
351 &self,
352 req: &mut Request,
353 depot: &mut Depot,
354 res: &mut Response,
355 ctrl: &mut FlowCtrl,
356 ) {
357 let cookie = req.cookies().get(&self.cookie_name);
358 let cookie_value = cookie.and_then(|cookie| self.verify_signature(cookie.value()).ok());
359
360 let mut session = self.load_or_create(cookie_value).await;
361
362 if let Some(ttl) = self.session_ttl {
363 session.expire_in(ttl);
364 }
365
366 depot.set_session(session);
367
368 ctrl.call_next(req, depot, res).await;
369 if ctrl.is_ceased() {
370 return;
371 }
372
373 let session = depot.take_session().expect("session should exist in depot");
374 if session.is_destroyed() {
375 if let Err(e) = self.store.destroy_session(session).await {
376 tracing::error!(error = ?e, "unable to destroy session");
377 }
378 res.remove_cookie(&self.cookie_name);
379 } else if self.save_unchanged || session.data_changed() {
380 match self.store.store_session(session).await {
381 Ok(cookie_value) => {
382 if let Some(cookie_value) = cookie_value {
383 let secure_cookie = req.uri().scheme() == Some(&Scheme::HTTPS);
384 let cookie = self.build_cookie(secure_cookie, cookie_value);
385 res.add_cookie(cookie);
386 }
387 }
388 Err(e) => {
389 tracing::error!(error = ?e, "store session error");
390 }
391 }
392 }
393 }
394}
395
396impl<S> SessionHandler<S>
397where
398 S: SessionStore + Send + Sync + 'static,
399{
400 pub fn builder(store: S, secret: &[u8]) -> HandlerBuilder<S> {
402 HandlerBuilder::new(store, secret)
403 }
404 #[inline]
405 async fn load_or_create(&self, cookie_value: Option<String>) -> Session {
406 let session = match cookie_value {
407 Some(cookie_value) => self.store.load_session(cookie_value).await.ok().flatten(),
408 None => None,
409 };
410
411 session
412 .and_then(|session| session.validate())
413 .unwrap_or_default()
414 }
415 fn verify_signature(&self, cookie_value: &str) -> Result<String, Error> {
421 if cookie_value.len() < BASE64_DIGEST_LEN {
422 return Err(Error::Other(
423 "length of value is <= BASE64_DIGEST_LEN".into(),
424 ));
425 }
426
427 let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
429 let digest = general_purpose::STANDARD
430 .decode(digest_str)
431 .map_err(|_| Error::Other("bad base64 digest".into()))?;
432
433 let mut hmac = self.hmac.clone();
435 hmac.update(value.as_bytes());
436 if hmac.verify_slice(&digest).is_ok() {
437 return Ok(value.to_owned());
438 }
439 for hmac in &self.fallback_hmacs {
440 let mut hmac = hmac.clone();
441 hmac.update(value.as_bytes());
442 if hmac.verify_slice(&digest).is_ok() {
443 return Ok(value.to_owned());
444 }
445 }
446 Err(Error::Other("value did not verify".into()))
447 }
448 fn build_cookie(&self, secure: bool, cookie_value: String) -> Cookie<'static> {
449 let mut cookie = Cookie::build((self.cookie_name.clone(), cookie_value))
450 .http_only(true)
451 .same_site(self.same_site_policy)
452 .secure(secure)
453 .path(self.cookie_path.clone())
454 .build();
455
456 if let Some(ttl) = self.session_ttl {
457 cookie.set_expires(Some((std::time::SystemTime::now() + ttl).into()));
458 }
459
460 if let Some(cookie_domain) = self.cookie_domain.clone() {
461 cookie.set_domain(cookie_domain)
462 }
463
464 self.sign_cookie(&mut cookie);
465
466 cookie
467 }
468 fn sign_cookie(&self, cookie: &mut Cookie<'_>) {
472 let mut mac = self.hmac.clone();
474 mac.update(cookie.value().as_bytes());
475
476 let mut new_value = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
478 new_value.push_str(cookie.value());
479 cookie.set_value(new_value);
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use salvo_core::http::Method;
486 use salvo_core::http::header::*;
487 use salvo_core::prelude::*;
488 use salvo_core::test::{ResponseExt, TestClient};
489
490 use super::*;
491
492 #[test]
493 fn test_session_data() {
494 let builder = SessionHandler::builder(
495 saysion::CookieStore,
496 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
497 )
498 .cookie_domain("test.domain")
499 .cookie_name("test_cookie")
500 .cookie_path("/abc")
501 .same_site_policy(SameSite::Strict)
502 .session_ttl(Some(Duration::from_secs(30)));
503 assert!(format!("{builder:?}").contains("test_cookie"));
504
505 let handler = builder.build().unwrap();
506 assert!(format!("{handler:?}").contains("test_cookie"));
507 assert_eq!(handler.cookie_domain, Some("test.domain".into()));
508 assert_eq!(handler.cookie_name, "test_cookie");
509 assert_eq!(handler.cookie_path, "/abc");
510 assert_eq!(handler.same_site_policy, SameSite::Strict);
511 assert_eq!(handler.session_ttl, Some(Duration::from_secs(30)));
512 }
513
514 #[tokio::test]
515 async fn test_session_login() {
516 #[handler]
517 pub async fn login(req: &mut Request, depot: &mut Depot, res: &mut Response) {
518 if req.method() == Method::POST {
519 let mut session = Session::new();
520 session
521 .insert("username", req.form::<String>("username").await.unwrap())
522 .unwrap();
523 depot.set_session(session);
524 res.render(Redirect::other("/"));
525 } else {
526 res.render(Text::Html("login page"));
527 }
528 }
529
530 #[handler]
531 pub async fn logout(depot: &mut Depot, res: &mut Response) {
532 if let Some(session) = depot.session_mut() {
533 session.remove("username");
534 }
535 res.render(Redirect::other("/"));
536 }
537
538 #[handler]
539 pub async fn home(depot: &mut Depot, res: &mut Response) {
540 let mut content = r#"home"#.into();
541 if let Some(session) = depot.session_mut()
542 && let Some(username) = session.get::<String>("username")
543 {
544 content = username;
545 }
546 res.render(Text::Html(content));
547 }
548
549 let session_handler = SessionHandler::builder(
550 MemoryStore::new(),
551 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
552 )
553 .build()
554 .unwrap();
555 let router = Router::new()
556 .hoop(session_handler)
557 .get(home)
558 .push(Router::with_path("login").get(login).post(login))
559 .push(Router::with_path("logout").get(logout));
560 let service = Service::new(router);
561
562 let response = TestClient::post("http://127.0.0.1:8698/login")
563 .raw_form("username=salvo")
564 .send(&service)
565 .await;
566 assert_eq!(response.status_code, Some(StatusCode::SEE_OTHER));
567 let cookie = response.headers().get(SET_COOKIE).unwrap();
568
569 let mut response = TestClient::get("http://127.0.0.1:8698/")
570 .add_header(COOKIE, cookie, true)
571 .send(&service)
572 .await;
573 assert_eq!(response.take_string().await.unwrap(), "salvo");
574
575 let response = TestClient::get("http://127.0.0.1:8698/logout")
576 .send(&service)
577 .await;
578 assert_eq!(response.status_code, Some(StatusCode::SEE_OTHER));
579
580 let mut response = TestClient::get("http://127.0.0.1:8698/")
581 .send(&service)
582 .await;
583 assert_eq!(response.take_string().await.unwrap(), "home");
584 }
585
586 #[test]
588 fn test_handler_builder_new() {
589 let builder = HandlerBuilder::new(
590 MemoryStore::new(),
591 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
592 );
593 assert_eq!(builder.cookie_path, "/");
594 assert_eq!(builder.cookie_name, "salvo.session.id");
595 assert!(builder.cookie_domain.is_none());
596 assert!(builder.save_unchanged);
597 assert_eq!(builder.same_site_policy, SameSite::Lax);
598 assert_eq!(builder.session_ttl, Some(Duration::from_secs(24 * 60 * 60)));
599 }
600
601 #[test]
602 fn test_handler_builder_cookie_path() {
603 let builder = HandlerBuilder::new(
604 MemoryStore::new(),
605 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
606 )
607 .cookie_path("/custom");
608 assert_eq!(builder.cookie_path, "/custom");
609 }
610
611 #[test]
612 fn test_handler_builder_session_ttl() {
613 let builder = HandlerBuilder::new(
614 MemoryStore::new(),
615 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
616 )
617 .session_ttl(Some(Duration::from_secs(3600)));
618 assert_eq!(builder.session_ttl, Some(Duration::from_secs(3600)));
619 }
620
621 #[test]
622 fn test_handler_builder_session_ttl_none() {
623 let builder = HandlerBuilder::new(
624 MemoryStore::new(),
625 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
626 )
627 .session_ttl(None);
628 assert!(builder.session_ttl.is_none());
629 }
630
631 #[test]
632 fn test_handler_builder_cookie_name() {
633 let builder = HandlerBuilder::new(
634 MemoryStore::new(),
635 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
636 )
637 .cookie_name("my_session");
638 assert_eq!(builder.cookie_name, "my_session");
639 }
640
641 #[test]
642 fn test_handler_builder_save_unchanged() {
643 let builder = HandlerBuilder::new(
644 MemoryStore::new(),
645 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
646 )
647 .save_unchanged(false);
648 assert!(!builder.save_unchanged);
649 }
650
651 #[test]
652 fn test_handler_builder_same_site_policy() {
653 let builder = HandlerBuilder::new(
654 MemoryStore::new(),
655 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
656 )
657 .same_site_policy(SameSite::None);
658 assert_eq!(builder.same_site_policy, SameSite::None);
659 }
660
661 #[test]
662 fn test_handler_builder_cookie_domain() {
663 let builder = HandlerBuilder::new(
664 MemoryStore::new(),
665 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
666 )
667 .cookie_domain("example.com");
668 assert_eq!(builder.cookie_domain, Some("example.com".to_owned()));
669 }
670
671 #[test]
672 fn test_handler_builder_fallback_keys() {
673 let builder = HandlerBuilder::new(
674 MemoryStore::new(),
675 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
676 )
677 .fallback_keys(vec![Key::from(
678 b"fallbackfallbackfallbackfallbackfallbackfallbackfallbackfallback" as &[u8],
679 )]);
680 assert_eq!(builder.fallback_keys.len(), 1);
681 }
682
683 #[test]
684 fn test_handler_builder_add_fallback_key() {
685 let builder = HandlerBuilder::new(
686 MemoryStore::new(),
687 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
688 )
689 .add_fallback_key(Key::from(
690 b"fallbackfallbackfallbackfallbackfallbackfallbackfallbackfallback" as &[u8],
691 ))
692 .add_fallback_key(Key::from(
693 b"anotherkeyanotherkeyanotherkeyanotherkeyanotherkeyanotherkeyanot" as &[u8],
694 ));
695 assert_eq!(builder.fallback_keys.len(), 2);
696 }
697
698 #[test]
699 fn test_handler_builder_build() {
700 let handler = HandlerBuilder::new(
701 MemoryStore::new(),
702 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
703 )
704 .build()
705 .unwrap();
706 assert_eq!(handler.cookie_path, "/");
707 assert_eq!(handler.cookie_name, "salvo.session.id");
708 }
709
710 #[test]
711 fn test_handler_builder_debug() {
712 let builder = HandlerBuilder::new(
713 MemoryStore::new(),
714 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
715 );
716 let debug_str = format!("{builder:?}");
717 assert!(debug_str.contains("HandlerBuilder"));
718 assert!(debug_str.contains("cookie_path"));
719 assert!(debug_str.contains("cookie_name"));
720 }
721
722 #[test]
723 fn test_handler_builder_chain() {
724 let handler = HandlerBuilder::new(
725 MemoryStore::new(),
726 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
727 )
728 .cookie_path("/app")
729 .cookie_name("app_session")
730 .cookie_domain("app.example.com")
731 .session_ttl(Some(Duration::from_secs(7200)))
732 .save_unchanged(false)
733 .same_site_policy(SameSite::Strict)
734 .build()
735 .unwrap();
736
737 assert_eq!(handler.cookie_path, "/app");
738 assert_eq!(handler.cookie_name, "app_session");
739 assert_eq!(handler.cookie_domain, Some("app.example.com".to_owned()));
740 assert_eq!(handler.session_ttl, Some(Duration::from_secs(7200)));
741 assert!(!handler.save_unchanged);
742 assert_eq!(handler.same_site_policy, SameSite::Strict);
743 }
744
745 #[test]
747 fn test_session_handler_builder() {
748 let handler = SessionHandler::builder(
749 MemoryStore::new(),
750 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
751 )
752 .build()
753 .unwrap();
754 assert_eq!(handler.cookie_name, "salvo.session.id");
755 }
756
757 #[test]
758 fn test_session_handler_debug() {
759 let handler = SessionHandler::builder(
760 MemoryStore::new(),
761 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
762 )
763 .build()
764 .unwrap();
765 let debug_str = format!("{handler:?}");
766 assert!(debug_str.contains("SessionHandler"));
767 assert!(debug_str.contains("cookie_path"));
768 }
769
770 #[test]
772 fn test_depot_set_session() {
773 let mut depot = Depot::new();
774 let session = Session::new();
775 depot.set_session(session);
776 assert!(depot.session().is_some());
777 }
778
779 #[test]
780 fn test_depot_take_session() {
781 let mut depot = Depot::new();
782 let session = Session::new();
783 depot.set_session(session);
784 let taken = depot.take_session();
785 assert!(taken.is_some());
786 assert!(depot.session().is_none());
787 }
788
789 #[test]
790 fn test_depot_session() {
791 let mut depot = Depot::new();
792 assert!(depot.session().is_none());
793
794 depot.set_session(Session::new());
795 assert!(depot.session().is_some());
796 }
797
798 #[test]
799 fn test_depot_session_mut() {
800 let mut depot = Depot::new();
801 depot.set_session(Session::new());
802
803 if let Some(session) = depot.session_mut() {
804 session.insert("key", "value").unwrap();
805 }
806
807 if let Some(session) = depot.session() {
808 assert_eq!(session.get::<String>("key"), Some("value".to_owned()));
809 }
810 }
811
812 #[tokio::test]
814 async fn test_session_destroy() {
815 #[handler]
816 pub async fn destroy_session(depot: &mut Depot, res: &mut Response) {
817 if let Some(session) = depot.session_mut() {
818 session.destroy();
819 }
820 res.render("destroyed");
821 }
822
823 let session_handler = SessionHandler::builder(
824 MemoryStore::new(),
825 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
826 )
827 .build()
828 .unwrap();
829
830 let router = Router::new()
831 .hoop(session_handler)
832 .push(Router::with_path("destroy").get(destroy_session));
833 let service = Service::new(router);
834
835 let response = TestClient::get("http://127.0.0.1:8698/destroy")
836 .send(&service)
837 .await;
838 assert_eq!(response.status_code, Some(StatusCode::OK));
839 }
840
841 #[tokio::test]
843 async fn test_session_save_unchanged_false() {
844 #[handler]
845 pub async fn no_change(depot: &mut Depot, res: &mut Response) {
846 let _ = depot.session();
848 res.render("no change");
849 }
850
851 let session_handler = SessionHandler::builder(
852 MemoryStore::new(),
853 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
854 )
855 .save_unchanged(false)
856 .build()
857 .unwrap();
858
859 let router = Router::new()
860 .hoop(session_handler)
861 .push(Router::with_path("nochange").get(no_change));
862 let service = Service::new(router);
863
864 let response = TestClient::get("http://127.0.0.1:8698/nochange")
865 .send(&service)
866 .await;
867 assert_eq!(response.status_code, Some(StatusCode::OK));
868 }
871
872 #[tokio::test]
874 async fn test_session_data_persistence() {
875 #[handler]
876 pub async fn set_data(depot: &mut Depot, res: &mut Response) {
877 if let Some(session) = depot.session_mut() {
878 session.insert("counter", 1).unwrap();
879 }
880 res.render("set");
881 }
882
883 #[handler]
884 pub async fn get_data(depot: &mut Depot, res: &mut Response) {
885 let counter = if let Some(session) = depot.session() {
886 session.get::<i32>("counter").unwrap_or(0)
887 } else {
888 0
889 };
890 res.render(format!("{counter}"));
891 }
892
893 let session_handler = SessionHandler::builder(
894 MemoryStore::new(),
895 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
896 )
897 .build()
898 .unwrap();
899
900 let router = Router::new()
901 .hoop(session_handler)
902 .push(Router::with_path("set").get(set_data))
903 .push(Router::with_path("get").get(get_data));
904 let service = Service::new(router);
905
906 let response = TestClient::get("http://127.0.0.1:8698/set")
908 .send(&service)
909 .await;
910 let cookie = response.headers().get(SET_COOKIE).unwrap();
911
912 let mut response = TestClient::get("http://127.0.0.1:8698/get")
914 .add_header(COOKIE, cookie, true)
915 .send(&service)
916 .await;
917 assert_eq!(response.take_string().await.unwrap(), "1");
918 }
919
920 #[test]
922 fn test_session_key_constant() {
923 assert_eq!(SESSION_KEY, "::salvo::session");
924 }
925
926 #[test]
928 fn test_base64_digest_len() {
929 assert_eq!(BASE64_DIGEST_LEN, 44);
930 }
931}