1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-23 15:51:06 +01:00

Add the ability to change the keys (#348)

* feat: add the ability to change the session key store in redis

* feat: change everywhere the constants are used

* refactor: add formatting with cargo fmt

---------

Co-authored-by: Dany Gagnon <danygagnon@Danys-MacBook-Pro.local>
This commit is contained in:
Dany Gagnon 2024-02-15 05:14:17 -05:00 committed by GitHub
parent daffc24245
commit 5414e2655b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 15 deletions

View File

@ -9,6 +9,9 @@ pub(crate) struct Configuration {
pub(crate) on_logout: LogoutBehaviour,
pub(crate) login_deadline: Option<Duration>,
pub(crate) visit_deadline: Option<Duration>,
pub(crate) id_key: &'static str,
pub(crate) last_visit_unix_timestamp_key: &'static str,
pub(crate) login_unix_timestamp_key: &'static str,
}
impl Default for Configuration {
@ -17,6 +20,9 @@ impl Default for Configuration {
on_logout: LogoutBehaviour::PurgeSession,
login_deadline: None,
visit_deadline: None,
id_key: "actix_identity.user_id",
last_visit_unix_timestamp_key: "actix_identity.last_visited_at",
login_unix_timestamp_key: "actix_identity.logged_in_at",
}
}
}
@ -58,6 +64,24 @@ impl IdentityMiddlewareBuilder {
}
}
/// Set a custom key to identify the user in the session.
pub fn id_key(mut self, key: &'static str) -> Self {
self.configuration.id_key = key;
self
}
/// Set a custom key to store the last visited unix timestamp.
pub fn last_visit_unix_timestamp_key(mut self, key: &'static str) -> Self {
self.configuration.last_visit_unix_timestamp_key = key;
self
}
/// Set a custom key to store the login unix timestamp.
pub fn login_unix_timestamp_key(mut self, key: &'static str) -> Self {
self.configuration.login_unix_timestamp_key = key;
self
}
/// Determines how [`Identity::logout`](crate::Identity::logout) affects the current session.
///
/// By default, the current session is purged ([`LogoutBehaviour::PurgeSession`]).

View File

@ -82,6 +82,9 @@ pub(crate) struct IdentityInner {
pub(crate) logout_behaviour: LogoutBehaviour,
pub(crate) is_login_deadline_enabled: bool,
pub(crate) is_visit_deadline_enabled: bool,
pub(crate) id_key: &'static str,
pub(crate) last_visit_unix_timestamp_key: &'static str,
pub(crate) login_unix_timestamp_key: &'static str,
}
impl IdentityInner {
@ -101,15 +104,11 @@ impl IdentityInner {
/// Retrieve the user id attached to the current session.
fn get_identity(&self) -> Result<String, GetIdentityError> {
self.session
.get::<String>(ID_KEY)?
.get::<String>(self.id_key)?
.ok_or_else(|| MissingIdentityError.into())
}
}
pub(crate) const ID_KEY: &str = "actix_identity.user_id";
pub(crate) const LAST_VISIT_UNIX_TIMESTAMP_KEY: &str = "actix_identity.last_visited_at";
pub(crate) const LOGIN_UNIX_TIMESTAMP_KEY: &str = "actix_identity.logged_in_at";
impl Identity {
/// Return the user id associated to the current session.
///
@ -130,7 +129,7 @@ impl Identity {
pub fn id(&self) -> Result<String, GetIdentityError> {
self.0
.session
.get(ID_KEY)?
.get(self.0.id_key)?
.ok_or_else(|| LostIdentityError.into())
}
@ -153,13 +152,15 @@ impl Identity {
/// ```
pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> {
let inner = IdentityInner::extract(ext);
inner.session.insert(ID_KEY, id)?;
inner.session.insert(inner.id_key, id)?;
let now = OffsetDateTime::now_utc().unix_timestamp();
if inner.is_login_deadline_enabled {
inner.session.insert(LOGIN_UNIX_TIMESTAMP_KEY, now)?;
inner.session.insert(inner.login_unix_timestamp_key, now)?;
}
if inner.is_visit_deadline_enabled {
inner.session.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, now)?;
inner
.session
.insert(inner.last_visit_unix_timestamp_key, now)?;
}
inner.session.renew();
Ok(Self(inner))
@ -191,12 +192,12 @@ impl Identity {
self.0.session.purge();
}
LogoutBehaviour::DeleteIdentityKeys => {
self.0.session.remove(ID_KEY);
self.0.session.remove(self.0.id_key);
if self.0.is_login_deadline_enabled {
self.0.session.remove(LOGIN_UNIX_TIMESTAMP_KEY);
self.0.session.remove(self.0.login_unix_timestamp_key);
}
if self.0.is_visit_deadline_enabled {
self.0.session.remove(LAST_VISIT_UNIX_TIMESTAMP_KEY);
self.0.session.remove(self.0.last_visit_unix_timestamp_key);
}
}
}
@ -212,7 +213,7 @@ impl Identity {
Ok(self
.0
.session
.get(LOGIN_UNIX_TIMESTAMP_KEY)?
.get(self.0.login_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(SessionExpiryError)?)
@ -222,7 +223,7 @@ impl Identity {
Ok(self
.0
.session
.get(LAST_VISIT_UNIX_TIMESTAMP_KEY)?
.get(self.0.last_visit_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(SessionExpiryError)?)
@ -230,7 +231,9 @@ impl Identity {
pub(crate) fn set_last_visited_at(&self) -> Result<(), LoginError> {
let now = OffsetDateTime::now_utc().unix_timestamp();
self.0.session.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, now)?;
self.0
.session
.insert(self.0.last_visit_unix_timestamp_key, now)?;
Ok(())
}
}

View File

@ -114,6 +114,9 @@ where
logout_behaviour: configuration.on_logout.clone(),
is_login_deadline_enabled: configuration.login_deadline.is_some(),
is_visit_deadline_enabled: configuration.visit_deadline.is_some(),
id_key: configuration.id_key,
last_visit_unix_timestamp_key: configuration.last_visit_unix_timestamp_key,
login_unix_timestamp_key: configuration.login_unix_timestamp_key,
};
req.extensions_mut().insert(identity_inner);
enforce_policies(&req, &configuration);

View File

@ -28,6 +28,33 @@ async fn login_works() {
assert!(response.status().is_success());
}
#[actix_web::test]
async fn custom_keys_work_as_expected() {
let custom_id_key = "custom.user_id";
let custom_last_visited_key = "custom.last_visited_at";
let custom_logged_in_key = "custom.logged_in_at";
let app = TestApp::spawn_with_config(
IdentityMiddleware::builder()
.id_key(custom_id_key)
.last_visit_unix_timestamp_key(custom_last_visited_key)
.login_unix_timestamp_key(custom_logged_in_key),
);
let user_id = user_id();
let body = app.post_login(user_id.clone()).await;
assert_eq!(body.user_id, Some(user_id.clone()));
let response = app.get_identity_required().await;
assert!(response.status().is_success());
let response = app.post_logout().await;
assert!(response.status().is_success());
let response = app.get_identity_required().await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[actix_web::test]
async fn logging_in_again_replaces_the_current_identity() {
let app = TestApp::spawn();