mas_axum_utils/
session.rs1use chrono::{DateTime, Utc};
9use mas_data_model::BrowserSession;
10use mas_storage::RepositoryAccess;
11use serde::{Deserialize, Serialize};
12use ulid::Ulid;
13
14use crate::{cookies::CookieJar, log_context::RecordAsRequester};
15
16#[derive(Serialize, Deserialize, Debug, Default, Clone)]
18pub struct SessionInfo {
19 current: Option<Ulid>,
20
21 #[serde(default, skip_serializing_if = "Option::is_none")]
30 logged_out_at: Option<DateTime<Utc>>,
31}
32
33impl SessionInfo {
34 #[must_use]
36 pub fn from_session(session: &BrowserSession) -> Self {
37 Self {
38 current: Some(session.id),
39 logged_out_at: None,
40 }
41 }
42
43 #[must_use]
45 pub fn mark_session_ended(mut self, now: DateTime<Utc>) -> Self {
46 self.current = None;
47 self.logged_out_at = Some(now);
48 self
49 }
50
51 pub async fn load_active_session<E>(
57 &self,
58 repo: &mut impl RepositoryAccess<Error = E>,
59 ) -> Result<Option<BrowserSession>, E> {
60 let Some(session_id) = self.current else {
61 return Ok(None);
62 };
63
64 let maybe_session = repo
65 .browser_session()
66 .lookup(session_id)
67 .await?
68 .filter(BrowserSession::active);
70
71 if let Some(session) = &maybe_session {
72 session.maybe_record_as_requester();
73 }
74
75 Ok(maybe_session)
76 }
77
78 #[must_use]
80 pub fn current_session_id(&self) -> Option<Ulid> {
81 self.current
82 }
83
84 #[must_use]
90 pub fn logged_out_at(&self) -> Option<DateTime<Utc>> {
91 self.logged_out_at
92 }
93}
94
95pub trait SessionInfoExt {
96 #[must_use]
97 fn session_info(self) -> (SessionInfo, Self);
98
99 #[must_use]
100 fn update_session_info(self, info: &SessionInfo) -> Self;
101
102 #[must_use]
103 fn set_session(self, session: &BrowserSession) -> Self
104 where
105 Self: Sized,
106 {
107 let session_info = SessionInfo::from_session(session);
108 self.update_session_info(&session_info)
109 }
110}
111
112impl SessionInfoExt for CookieJar {
113 fn session_info(self) -> (SessionInfo, Self) {
114 let info = match self.load("session") {
115 Ok(Some(s)) => s,
116 Ok(None) => SessionInfo::default(),
117 Err(e) => {
118 tracing::error!("failed to load session cookie: {}", e);
119 SessionInfo::default()
120 }
121 };
122
123 let jar = self.update_session_info(&info);
124 (info, jar)
125 }
126
127 fn update_session_info(self, info: &SessionInfo) -> Self {
128 self.save("session", info, true)
129 }
130}