1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-06-27 02:37:42 +02:00

Add a new configuration parameter to refresh the TTL of the session even if unchanged (#233)

Co-authored-by: Rob Ede <robjtede@icloud.com>
This commit is contained in:
Luca Palmieri
2022-07-03 21:18:14 +01:00
committed by GitHub
parent d09299390a
commit c2f068db66
13 changed files with 607 additions and 319 deletions

View File

@ -1,6 +1,7 @@
use std::convert::TryInto;
use time::Duration;
use actix_web::cookie::time::Duration;
use anyhow::Error;
use super::SessionKey;
use crate::storage::{
@ -34,9 +35,9 @@ use crate::storage::{
/// ```
///
/// # Limitations
/// Cookies are subject to size limits - we require session keys to be shorter than 4096 bytes. This
/// translates into a limit on the maximum size of the session state when using cookies as storage
/// backend.
/// Cookies are subject to size limits so we require session keys to be shorter than 4096 bytes.
/// This translates into a limit on the maximum size of the session state when using cookies as
/// storage backend.
///
/// The session cookie can always be inspected by end users via the developer tools exposed by their
/// browsers. We strongly recommend setting the policy to [`CookieContentSecurity::Private`] when
@ -45,7 +46,7 @@ use crate::storage::{
/// There is no way to invalidate a session before its natural expiry when using cookies as the
/// storage backend.
///
/// [`CookieContentSecurity::Private`]: crate::CookieContentSecurity::Private
/// [`CookieContentSecurity::Private`]: crate::config::CookieContentSecurity::Private
#[cfg_attr(docsrs, doc(cfg(feature = "cookie-session")))]
#[derive(Default)]
#[non_exhaustive]
@ -89,6 +90,10 @@ impl SessionStore for CookieSessionStore {
})
}
async fn update_ttl(&self, _session_key: &SessionKey, _ttl: &Duration) -> Result<(), Error> {
Ok(())
}
async fn delete(&self, _session_key: &SessionKey) -> Result<(), anyhow::Error> {
Ok(())
}

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use actix_web::cookie::time::Duration;
use derive_more::Display;
use time::Duration;
use super::SessionKey;
@ -36,6 +36,13 @@ pub trait SessionStore {
ttl: &Duration,
) -> Result<SessionKey, UpdateError>;
/// Updates the TTL of the session state associated to a pre-existing session key.
async fn update_ttl(
&self,
session_key: &SessionKey,
ttl: &Duration,
) -> Result<(), anyhow::Error>;
/// Deletes a session from the store.
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error>;
}

View File

@ -1,6 +1,7 @@
use actix::Addr;
use actix_redis::{resp_array, Command, RedisActor, RespValue};
use time::{self, Duration};
use actix_web::cookie::time::Duration;
use anyhow::Error;
use super::SessionKey;
use crate::storage::{
@ -238,6 +239,24 @@ impl SessionStore for RedisActorSessionStore {
}
}
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cmd = Command(resp_array![
"EXPIRE",
cache_key,
ttl.whole_seconds().to_string()
]);
match self.addr.send(cmd).await? {
Ok(RespValue::Integer(_)) => Ok(()),
val => Err(anyhow::anyhow!(
"Failed to update the session state TTL: {:?}",
val
)),
}
}
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
@ -258,9 +277,11 @@ impl SessionStore for RedisActorSessionStore {
}
#[cfg(test)]
mod test {
mod tests {
use std::collections::HashMap;
use actix_web::cookie::time::Duration;
use super::*;
use crate::test_helpers::acceptance_test_suite;
@ -286,7 +307,7 @@ mod test {
let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned();
let updated_session_key = store
.update(session_key, HashMap::new(), &time::Duration::seconds(1))
.update(session_key, HashMap::new(), &Duration::seconds(1))
.await
.unwrap();
assert_ne!(initial_session_key, updated_session_key.as_ref());

View File

@ -1,7 +1,8 @@
use std::sync::Arc;
use std::{convert::TryInto, sync::Arc};
use redis::{aio::ConnectionManager, Cmd, FromRedisValue, RedisResult, Value};
use time::{self, Duration};
use actix_web::cookie::time::Duration;
use anyhow::{Context, Error};
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};
use super::SessionKey;
use crate::storage::{
@ -28,6 +29,7 @@ use crate::storage::{
/// let secret_key = get_secret_key();
/// let redis_connection_string = "redis://127.0.0.1:6379";
/// let store = RedisSessionStore::new(redis_connection_string).await.unwrap();
///
/// HttpServer::new(move ||
/// App::new()
/// .wrap(SessionMiddleware::new(
@ -221,6 +223,21 @@ impl SessionStore for RedisSessionStore {
}
}
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.client
.clone()
.expire(
&cache_key,
ttl.whole_seconds().try_into().context(
"Failed to convert the state TTL into the number of whole seconds remaining",
)?,
)
.await?;
Ok(())
}
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.execute_command(redis::cmd("DEL").arg(&[&cache_key]))
@ -272,9 +289,10 @@ impl RedisSessionStore {
}
#[cfg(test)]
mod test {
mod tests {
use std::collections::HashMap;
use actix_web::cookie::time;
use redis::AsyncCommands;
use super::*;

View File

@ -2,16 +2,15 @@ use std::convert::TryFrom;
use derive_more::{Display, From};
/// A session key, the string stored in a client-side cookie to associate a user
/// with its session state on the backend.
/// A session key, the string stored in a client-side cookie to associate a user with its session
/// state on the backend.
///
/// ## Validation
///
/// Session keys are stored as cookies, therefore they cannot be arbitrary long.
/// We require session keys to be smaller than 4064 bytes.
/// # Validation
/// Session keys are stored as cookies, therefore they cannot be arbitrary long. Session keys are
/// required to be smaller than 4064 bytes.
///
/// ```rust
/// use std::convert::TryInto;
/// # use std::convert::TryInto;
/// use actix_session::storage::SessionKey;
///
/// let key: String = std::iter::repeat('a').take(4065).collect();
@ -24,15 +23,15 @@ pub struct SessionKey(String);
impl TryFrom<String> for SessionKey {
type Error = InvalidSessionKeyError;
fn try_from(v: String) -> Result<Self, Self::Error> {
if v.len() > 4064 {
fn try_from(val: String) -> Result<Self, Self::Error> {
if val.len() > 4064 {
return Err(anyhow::anyhow!(
"The session key is bigger than 4064 bytes, the upper limit on cookie content."
)
.into());
}
Ok(SessionKey(v))
Ok(SessionKey(val))
}
}
@ -43,8 +42,8 @@ impl AsRef<str> for SessionKey {
}
impl From<SessionKey> for String {
fn from(k: SessionKey) -> Self {
k.0
fn from(key: SessionKey) -> Self {
key.0
}
}