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:
@ -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(())
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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::*;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user