actix_extended_session/
session.rs

1use crate::config::SessionLifecycle;
2use actix_utils::future::{ready, Ready};
3use actix_web::{
4    body::BoxBody,
5    dev::{Extensions, Payload, ServiceRequest, ServiceResponse},
6    error::Error,
7    FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
8};
9use anyhow::Context;
10use derive_more::{Display, From};
11use serde::de::DeserializeOwned;
12use serde_json::{Map, Value};
13use std::{
14    cell::{Ref, RefCell},
15    error::Error as StdError,
16    mem,
17    rc::Rc,
18};
19
20/// The primary interface to access and modify session state.
21///
22/// [`Session`] is an [extractor](#impl-FromRequest)—you can specify it as an input type for your
23/// request handlers and it will be automatically extracted from the incoming request.
24///
25/// ```
26/// use serde_json::Value;
27/// use actix_extended_session::Session;
28///
29/// async fn index(session: Session) -> actix_web::Result<&'static str> {
30///     // access session data
31///     if let Some(count) = session.get::<i32>("counter")? {
32///         session.insert("counter", Value::from(count + 1));
33///     } else {
34///         session.insert("counter", Value::from(1));
35///     }
36///
37///     Ok("Welcome!")
38/// }
39/// # actix_web::web::to(index);
40/// ```
41///
42/// You can also retrieve a [`Session`] object from an `HttpRequest` or a `ServiceRequest` using
43/// [`SessionExt`].
44///
45/// [`SessionExt`]: crate::SessionExt
46#[derive(Clone)]
47pub struct Session(Rc<RefCell<SessionInner>>);
48
49/// Status of a [`Session`].
50#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub enum SessionStatus {
52    /// Session state has been updated - the changes will have to be persisted to the backend.
53    Changed,
54
55    /// The session has been flagged for deletion - the session cookie will be removed from
56    /// the client and the session state will be deleted from the session store.
57    ///
58    /// Most operations on the session after it has been marked for deletion will have no effect.
59    Purged,
60
61    /// The session has been flagged for renewal.
62    ///
63    /// The session key will be regenerated and the time-to-live of the session state will be
64    /// extended.
65    Renewed,
66
67    /// The session state has not been modified since its creation/retrieval.
68    #[default]
69    Unchanged,
70}
71
72#[derive(Default)]
73struct SessionInner {
74    state: Map<String, Value>,
75    status: SessionStatus,
76    lifecycle: SessionLifecycle,
77}
78
79impl Session {
80    /// Get a `value` from the session.
81    ///
82    /// It returns an error if it fails to parse as `T` the JSON value associated with `key`.
83    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, SessionGetError> {
84        if let Some(value) = self.0.borrow().state.get(key) {
85            Ok(Some(
86                serde_json::from_value::<T>(value.clone())
87                    .with_context(|| {
88                        format!(
89                            "Failed to deserialize the JSON-encoded session data attached to key \
90                            `{}` as a `{}` type",
91                            key,
92                            std::any::type_name::<T>()
93                        )
94                    })
95                    .map_err(SessionGetError)?,
96            ))
97        } else {
98            Ok(None)
99        }
100    }
101
102    /// Get all raw key-value data from the session.
103    ///
104    /// Note that values are JSON encoded.
105    pub fn entries(&self) -> Ref<'_, Map<String, Value>> {
106        Ref::map(self.0.borrow(), |inner| &inner.state)
107    }
108
109    /// Returns session status.
110    pub fn status(&self) -> SessionStatus {
111        Ref::map(self.0.borrow(), |inner| &inner.status).clone()
112    }
113
114    /// Inserts a key-value pair into the session.
115    pub fn insert(&self, key: impl Into<String>, value: Value) {
116        let mut inner = self.0.borrow_mut();
117
118        if inner.status != SessionStatus::Purged {
119            if inner.status != SessionStatus::Renewed {
120                inner.status = SessionStatus::Changed;
121            }
122
123            inner.state.insert(key.into(), value);
124        }
125    }
126
127    /// Remove value from the session.
128    ///
129    /// If present, the JSON encoded value is returned.
130    pub fn remove(&self, key: &str) -> Option<Value> {
131        let mut inner = self.0.borrow_mut();
132
133        if inner.status != SessionStatus::Purged {
134            if inner.status != SessionStatus::Renewed {
135                inner.status = SessionStatus::Changed;
136            }
137            return inner.state.remove(key);
138        }
139
140        None
141    }
142
143    /// Clear the session.
144    pub fn clear(&self) {
145        let mut inner = self.0.borrow_mut();
146
147        if inner.status != SessionStatus::Purged {
148            if inner.status != SessionStatus::Renewed {
149                inner.status = SessionStatus::Changed;
150            }
151            inner.state.clear()
152        }
153    }
154
155    /// Removes session both client and server side.
156    pub fn purge(&self) {
157        let mut inner = self.0.borrow_mut();
158        inner.status = SessionStatus::Purged;
159        inner.state.clear();
160    }
161
162    /// Renews the session key, assigning existing session state to new key.
163    pub fn renew(&self) {
164        let mut inner = self.0.borrow_mut();
165
166        if inner.status != SessionStatus::Purged {
167            inner.status = SessionStatus::Renewed;
168        }
169    }
170
171    /// Sets the lifecycle of the session cookie.
172    pub fn set_lifecycle(&self, next_lifecycle: SessionLifecycle) {
173        let mut inner = self.0.borrow_mut();
174
175        if inner.status != SessionStatus::Purged {
176            if inner.status != SessionStatus::Renewed {
177                inner.status = SessionStatus::Changed;
178            }
179
180            inner.lifecycle = next_lifecycle;
181        }
182    }
183
184    /// Returns the lifecycle of the session cookie.
185    pub fn get_lifecycle(&self) -> SessionLifecycle {
186        let inner = self.0.borrow_mut();
187        inner.lifecycle.to_owned()
188    }
189
190    /// Adds the given key-value pairs to the session on the request.
191    ///
192    /// Values that match keys already existing on the session will be overwritten.
193    #[allow(clippy::needless_pass_by_ref_mut)]
194    pub(crate) fn set_session(
195        req: &mut ServiceRequest,
196        data: impl IntoIterator<Item = (String, Value)>,
197        lifecycle: SessionLifecycle,
198    ) {
199        let session = Session::get_session(&mut req.extensions_mut());
200        let mut inner = session.0.borrow_mut();
201        inner.state.extend(data);
202        inner.lifecycle = lifecycle;
203    }
204
205    /// Returns session lifecycle, session status, and iterator of key-value pairs of changes.
206    ///
207    /// This is a destructive operation - the session state is removed from the request extensions
208    /// type-map, leaving behind a new empty map. It should only be used when the session is being
209    /// finalised (i.e. in `SessionMiddleware`).
210    #[allow(clippy::needless_pass_by_ref_mut)]
211    pub(crate) fn get_changes<B>(
212        res: &mut ServiceResponse<B>,
213    ) -> (SessionLifecycle, SessionStatus, Map<String, Value>) {
214        if let Some(s_impl) = res
215            .request()
216            .extensions()
217            .get::<Rc<RefCell<SessionInner>>>()
218        {
219            let state = mem::take(&mut s_impl.borrow_mut().state);
220            (
221                s_impl.borrow().lifecycle.clone(),
222                s_impl.borrow().status.clone(),
223                state,
224            )
225        } else {
226            (
227                SessionLifecycle::PersistentSession,
228                SessionStatus::Unchanged,
229                Map::new(),
230            )
231        }
232    }
233
234    pub(crate) fn get_session(extensions: &mut Extensions) -> Session {
235        if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
236            return Session(Rc::clone(s_impl));
237        }
238
239        let inner = Rc::new(RefCell::new(SessionInner::default()));
240        extensions.insert(inner.clone());
241
242        Session(inner)
243    }
244}
245
246/// Extractor implementation for [`Session`]s.
247///
248/// # Examples
249/// ```
250/// # use actix_web::*;
251/// use actix_extended_session::Session;
252/// use serde_json::Value;
253///
254/// #[get("/")]
255/// async fn index(session: Session) -> Result<impl Responder> {
256///     // access session data
257///     if let Some(count) = session.get::<i32>("counter")? {
258///         session.insert("counter", Value::from(count + 1));
259///     } else {
260///         session.insert("counter", Value::from(1));
261///     }
262///
263///     let count = session.get::<i32>("counter")?.unwrap();
264///     Ok(format!("Counter: {}", count))
265/// }
266/// ```
267impl FromRequest for Session {
268    type Error = Error;
269    type Future = Ready<Result<Session, Error>>;
270
271    #[inline]
272    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
273        ready(Ok(Session::get_session(&mut req.extensions_mut())))
274    }
275}
276
277/// Error returned by [`Session::get`].
278#[derive(Debug, Display, From)]
279#[display(fmt = "{_0}")]
280pub struct SessionGetError(anyhow::Error);
281
282impl StdError for SessionGetError {
283    fn source(&self) -> Option<&(dyn StdError + 'static)> {
284        Some(self.0.as_ref())
285    }
286}
287
288impl ResponseError for SessionGetError {
289    fn error_response(&self) -> HttpResponse<BoxBody> {
290        HttpResponse::new(self.status_code())
291    }
292}