Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: extract session key #324

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actix-session/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] }
actix = { version = "0.13", default-features = false, optional = true }
actix-redis = { version = "0.12", optional = true }
futures-core = { version = "0.3.7", default-features = false, optional = true }
secrecy = "0.8"

# redis-rs-session
redis = { version = "0.22", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }
Expand Down
2 changes: 1 addition & 1 deletion actix-session/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ where
let (session_key, session_state) =
load_session_state(session_key, storage_backend.as_ref()).await?;

Session::set_session(&mut req, session_state);
Session::set_session(&mut req, session_state, session_key.clone());

let mut res = service.call(req).await?;
let (status, session_state) = Session::get_changes(&mut res);
Expand Down
11 changes: 11 additions & 0 deletions actix-session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use anyhow::Context;
use derive_more::{Display, From};
use serde::{de::DeserializeOwned, Serialize};

use crate::storage::SessionKey;

/// The primary interface to access and modify session state.
///
/// [`Session`] is an [extractor](#impl-FromRequest)—you can specify it as an input type for your
Expand Down Expand Up @@ -77,6 +79,7 @@ impl Default for SessionStatus {
struct SessionInner {
state: HashMap<String, String>,
status: SessionStatus,
session_key: Option<SessionKey>,
}

impl Session {
Expand All @@ -101,6 +104,12 @@ impl Session {
Ok(None)
}
}
/// Get a the session key itself from the overall session.
///
/// Retrieve the overall session key
pub fn get_session_key(&self) -> Option<SessionKey> {
self.0.borrow().session_key.clone()
}

/// Get all raw key-value data from the session.
///
Expand Down Expand Up @@ -221,10 +230,12 @@ impl Session {
pub(crate) fn set_session(
req: &mut ServiceRequest,
data: impl IntoIterator<Item = (String, String)>,
session_key: Option<SessionKey>,
) {
let session = Session::get_session(&mut req.extensions_mut());
let mut inner = session.0.borrow_mut();
inner.state.extend(data);
inner.session_key = session_key;
}

/// Returns session status and iterator of key-value pairs of changes.
Expand Down
3 changes: 2 additions & 1 deletion actix-session/src/storage/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::convert::TryInto;

use actix_web::cookie::time::Duration;
use anyhow::Error;
use secrecy::ExposeSecret;

use super::SessionKey;
use crate::storage::{
Expand Down Expand Up @@ -54,7 +55,7 @@ pub struct CookieSessionStore;
#[async_trait::async_trait(?Send)]
impl SessionStore for CookieSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
serde_json::from_str(session_key.as_ref())
serde_json::from_str(session_key.as_ref().expose_secret())
.map(Some)
.map_err(anyhow::Error::new)
.map_err(LoadError::Deserialization)
Expand Down
18 changes: 11 additions & 7 deletions actix-session/src/storage/redis_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use actix::Addr;
use actix_redis::{resp_array, Command, RedisActor, RespValue};
use actix_web::cookie::time::Duration;
use anyhow::Error;
use secrecy::ExposeSecret;

use super::SessionKey;
use crate::storage::{
Expand Down Expand Up @@ -121,7 +122,7 @@ impl RedisActorSessionStoreBuilder {
#[async_trait::async_trait(?Send)]
impl SessionStore for RedisActorSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());
let val = self
.addr
.send(Command(resp_array!["GET", cache_key]))
Expand Down Expand Up @@ -155,7 +156,7 @@ impl SessionStore for RedisActorSessionStore {
.map_err(Into::into)
.map_err(SaveError::Serialization)?;
let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let cmd = Command(resp_array![
"SET",
Expand Down Expand Up @@ -196,7 +197,7 @@ impl SessionStore for RedisActorSessionStore {
let body = serde_json::to_string(&session_state)
.map_err(Into::into)
.map_err(UpdateError::Serialization)?;
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let cmd = Command(resp_array![
"SET",
Expand Down Expand Up @@ -238,7 +239,7 @@ 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 cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let cmd = Command(resp_array![
"EXPIRE",
Expand All @@ -256,7 +257,7 @@ impl SessionStore for RedisActorSessionStore {
}

async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let res = self
.addr
Expand Down Expand Up @@ -303,11 +304,14 @@ mod tests {
async fn updating_of_an_expired_state_is_handled_gracefully() {
let store = redis_actor_store();
let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned();
let initial_session_key = session_key.as_ref().expose_secret().to_owned();
let updated_session_key = store
.update(session_key, HashMap::new(), &Duration::seconds(1))
.await
.unwrap();
assert_ne!(initial_session_key, updated_session_key.as_ref());
assert_ne!(
&initial_session_key,
updated_session_key.as_ref().expose_secret()
);
}
}
23 changes: 15 additions & 8 deletions actix-session/src/storage/redis_rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{convert::TryInto, sync::Arc};
use actix_web::cookie::time::Duration;
use anyhow::{Context, Error};
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};
use secrecy::ExposeSecret;

use super::SessionKey;
use crate::storage::{
Expand Down Expand Up @@ -135,7 +136,7 @@ impl RedisSessionStoreBuilder {
#[async_trait::async_trait(?Send)]
impl SessionStore for RedisSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let value: Option<String> = self
.execute_command(redis::cmd("GET").arg(&[&cache_key]))
Expand All @@ -160,7 +161,7 @@ impl SessionStore for RedisSessionStore {
.map_err(Into::into)
.map_err(SaveError::Serialization)?;
let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

self.execute_command(redis::cmd("SET").arg(&[
&cache_key,
Expand All @@ -186,7 +187,7 @@ impl SessionStore for RedisSessionStore {
.map_err(Into::into)
.map_err(UpdateError::Serialization)?;

let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

let v: redis::Value = self
.execute_command(redis::cmd("SET").arg(&[
Expand Down Expand Up @@ -222,7 +223,7 @@ 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());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());

self.client
.clone()
Expand All @@ -237,7 +238,7 @@ impl SessionStore for RedisSessionStore {
}

async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref().expose_secret());
self.execute_command(redis::cmd("DEL").arg(&[&cache_key]))
.await
.map_err(Into::into)
Expand Down Expand Up @@ -322,7 +323,10 @@ mod tests {
store
.client
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.set::<_, _, ()>(
session_key.as_ref().expose_secret(),
"random-thing-which-is-not-json",
)
.await
.unwrap();
assert!(matches!(
Expand All @@ -335,11 +339,14 @@ mod tests {
async fn updating_of_an_expired_state_is_handled_gracefully() {
let store = redis_store().await;
let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned();
let initial_session_key = session_key.as_ref().expose_secret().to_owned();
let updated_session_key = store
.update(session_key, HashMap::new(), &time::Duration::seconds(1))
.await
.unwrap();
assert_ne!(initial_session_key, updated_session_key.as_ref());
assert_ne!(
&initial_session_key,
updated_session_key.as_ref().expose_secret()
);
}
}
21 changes: 14 additions & 7 deletions actix-session/src/storage/session_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::convert::TryFrom;

use derive_more::{Display, From};
use secrecy::{ExposeSecret, Secret};

/// A session key, the string stored in a client-side cookie to associate a user with its session
/// state on the backend.
Expand All @@ -17,8 +18,15 @@ use derive_more::{Display, From};
/// let session_key: Result<SessionKey, _> = key.try_into();
/// assert!(session_key.is_err());
/// ```
#[derive(Debug, PartialEq, Eq)]
pub struct SessionKey(String);
#[derive(Debug, Clone)]
pub struct SessionKey(secrecy::Secret<String>);

impl SessionKey {
/// Convert the SessionKey into the inner Secret
pub fn into_inner(self) -> secrecy::Secret<String> {
self.0
}
}

impl TryFrom<String> for SessionKey {
type Error = InvalidSessionKeyError;
Expand All @@ -30,20 +38,19 @@ impl TryFrom<String> for SessionKey {
)
.into());
}

Ok(SessionKey(val))
Ok(SessionKey(Secret::new(val)))
}
}

impl AsRef<str> for SessionKey {
fn as_ref(&self) -> &str {
impl AsRef<secrecy::Secret<String>> for SessionKey {
fn as_ref(&self) -> &secrecy::Secret<String> {
&self.0
}
}

impl From<SessionKey> for String {
fn from(key: SessionKey) -> Self {
key.0
key.0.expose_secret().into()
}
}

Expand Down