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