mas_axum_utils/
session.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use mas_data_model::BrowserSession;
8use mas_storage::RepositoryAccess;
9use serde::{Deserialize, Serialize};
10use ulid::Ulid;
11
12use crate::cookies::CookieJar;
13
14/// An encrypted cookie to save the session ID
15#[derive(Serialize, Deserialize, Debug, Default, Clone)]
16pub struct SessionInfo {
17    current: Option<Ulid>,
18}
19
20impl SessionInfo {
21    /// Forge the cookie from a [`BrowserSession`]
22    #[must_use]
23    pub fn from_session(session: &BrowserSession) -> Self {
24        Self {
25            current: Some(session.id),
26        }
27    }
28
29    /// Mark the session as ended
30    #[must_use]
31    pub fn mark_session_ended(mut self) -> Self {
32        self.current = None;
33        self
34    }
35
36    /// Load the active [`BrowserSession`] from database
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if the underlying repository fails to load the session.
41    pub async fn load_active_session<E>(
42        &self,
43        repo: &mut impl RepositoryAccess<Error = E>,
44    ) -> Result<Option<BrowserSession>, E> {
45        let Some(session_id) = self.current else {
46            return Ok(None);
47        };
48
49        let maybe_session = repo
50            .browser_session()
51            .lookup(session_id)
52            .await?
53            // Ensure that the session is still active
54            .filter(BrowserSession::active);
55
56        Ok(maybe_session)
57    }
58
59    /// Get the current session ID, if any
60    #[must_use]
61    pub fn current_session_id(&self) -> Option<Ulid> {
62        self.current
63    }
64}
65
66pub trait SessionInfoExt {
67    #[must_use]
68    fn session_info(self) -> (SessionInfo, Self);
69
70    #[must_use]
71    fn update_session_info(self, info: &SessionInfo) -> Self;
72
73    #[must_use]
74    fn set_session(self, session: &BrowserSession) -> Self
75    where
76        Self: Sized,
77    {
78        let session_info = SessionInfo::from_session(session);
79        self.update_session_info(&session_info)
80    }
81}
82
83impl SessionInfoExt for CookieJar {
84    fn session_info(self) -> (SessionInfo, Self) {
85        let info = match self.load("session") {
86            Ok(Some(s)) => s,
87            Ok(None) => SessionInfo::default(),
88            Err(e) => {
89                tracing::error!("failed to load session cookie: {}", e);
90                SessionInfo::default()
91            }
92        };
93
94        let jar = self.update_session_info(&info);
95        (info, jar)
96    }
97
98    fn update_session_info(self, info: &SessionInfo) -> Self {
99        self.save("session", info, true)
100    }
101}