diff --git a/actix-identity/CHANGES.md b/actix-identity/CHANGES.md index 1f7c9538d..a3706f37c 100644 --- a/actix-identity/CHANGES.md +++ b/actix-identity/CHANGES.md @@ -2,8 +2,11 @@ ## Unreleased - 2022-xx-xx +- Replace use of `anyhow::Error` with specific error types. [#296] - Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency. +[#296]: https://github.com/actix/actix-extras/pull/296 + ## 0.5.2 - 2022-07-19 - Fix visit deadline. [#263] diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index 09212b69c..468159170 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -22,7 +22,6 @@ actix-session = "0.7" actix-utils = "3" actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] } -anyhow = "1" futures-core = "0.3.7" serde = { version = "1", features = ["derive"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] } diff --git a/actix-identity/src/error.rs b/actix-identity/src/error.rs new file mode 100644 index 000000000..b701db900 --- /dev/null +++ b/actix-identity/src/error.rs @@ -0,0 +1,156 @@ +//! Failure modes of identity operations. + +use std::fmt; + +use actix_session::{SessionGetError, SessionInsertError}; +use actix_web::{cookie::time::error::ComponentRange, http::StatusCode, ResponseError}; + +/// Error that can occur during login attempts. +#[derive(Debug)] +pub struct LoginError(SessionInsertError); + +impl fmt::Display for LoginError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for LoginError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } +} + +impl ResponseError for LoginError { + fn status_code(&self) -> StatusCode { + StatusCode::UNAUTHORIZED + } +} + +impl From for LoginError { + fn from(error: SessionInsertError) -> Self { + Self(error) + } +} + +/// Error encountered when working with a session that has expired. +#[derive(Debug)] +pub struct SessionExpiryError(ComponentRange); + +impl fmt::Display for SessionExpiryError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("The given session has expired and is no longer valid") + } +} + +impl std::error::Error for SessionExpiryError {} + +/// The identity information has been lost. +/// +/// Seeing this error in user code indicates a bug in actix-identity. +#[derive(Debug)] +#[non_exhaustive] +pub struct LostIdentityError; + +impl fmt::Display for LostIdentityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str( + "The identity information in the current session has disappeared \ + after having been successfully validated. This is likely to be a bug.", + ) + } +} + +impl std::error::Error for LostIdentityError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self) + } +} + +/// There is no identity information attached to the current session. +#[derive(Debug)] +#[non_exhaustive] +pub struct MissingIdentityError; + +impl fmt::Display for MissingIdentityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("There is no identity information attached to the current session.") + } +} + +impl std::error::Error for MissingIdentityError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self) + } +} + +/// Errors that can occur while retrieving an identity. +#[derive(Debug)] +#[non_exhaustive] +pub enum GetIdentityError { + /// The session has expired. + SessionExpiryError(SessionExpiryError), + + /// No identity is found in a session. + MissingIdentityError(MissingIdentityError), + + /// Failed to accessing the session store. + SessionGetError(SessionGetError), + + /// Identity info was lost after being validated. + /// + /// Seeing this error indicates a bug in actix-identity. + LostIdentityError(LostIdentityError), +} + +impl fmt::Display for GetIdentityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::SessionExpiryError(err) => write!(f, "{err}"), + Self::MissingIdentityError(err) => write!(f, "{err}"), + Self::SessionGetError(err) => write!(f, "{err}"), + Self::LostIdentityError(err) => write!(f, "{err}"), + } + } +} + +impl std::error::Error for GetIdentityError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::SessionExpiryError(err) => Some(err), + Self::MissingIdentityError(err) => Some(err), + Self::SessionGetError(err) => Some(err), + Self::LostIdentityError(err) => Some(err), + } + } +} + +impl ResponseError for GetIdentityError { + fn status_code(&self) -> StatusCode { + StatusCode::UNAUTHORIZED + } +} + +impl From for GetIdentityError { + fn from(error: LostIdentityError) -> Self { + Self::LostIdentityError(error) + } +} + +impl From for GetIdentityError { + fn from(error: MissingIdentityError) -> Self { + Self::MissingIdentityError(error) + } +} + +impl From for GetIdentityError { + fn from(error: ComponentRange) -> Self { + Self::SessionExpiryError(SessionExpiryError(error)) + } +} + +impl From for GetIdentityError { + fn from(source: SessionGetError) -> Self { + Self::SessionGetError(source) + } +} diff --git a/actix-identity/src/identity.rs b/actix-identity/src/identity.rs index 0b6342736..a991e646f 100644 --- a/actix-identity/src/identity.rs +++ b/actix-identity/src/identity.rs @@ -6,9 +6,11 @@ use actix_web::{ http::StatusCode, Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, }; -use anyhow::{anyhow, Context}; -use crate::config::LogoutBehaviour; +use crate::{ + config::LogoutBehaviour, + error::{GetIdentityError, LoginError, LostIdentityError, MissingIdentityError}, +}; /// A verified user identity. It can be used as a request extractor. /// @@ -95,13 +97,10 @@ impl IdentityInner { } /// Retrieve the user id attached to the current session. - fn get_identity(&self) -> Result { + fn get_identity(&self) -> Result { self.session - .get::(ID_KEY) - .context("Failed to deserialize the user identifier attached to the current session")? - .ok_or_else(|| { - anyhow!("There is no identity information attached to the current session") - }) + .get::(ID_KEY)? + .ok_or_else(|| MissingIdentityError.into()) } } @@ -126,10 +125,11 @@ impl Identity { /// } /// } /// ``` - pub fn id(&self) -> Result { - self.0.session.get(ID_KEY)?.ok_or_else(|| { - anyhow!("Bug: the identity information attached to the current session has disappeared") - }) + pub fn id(&self) -> Result { + self.0 + .session + .get(ID_KEY)? + .ok_or_else(|| LostIdentityError.into()) } /// Attach a valid user identity to the current session. @@ -149,7 +149,7 @@ impl Identity { /// HttpResponse::Ok() /// } /// ``` - pub fn login(ext: &Extensions, id: String) -> Result { + pub fn login(ext: &Extensions, id: String) -> Result { let inner = IdentityInner::extract(ext); inner.session.insert(ID_KEY, id)?; let now = OffsetDateTime::now_utc().unix_timestamp(); @@ -200,31 +200,31 @@ impl Identity { } } - pub(crate) fn extract(ext: &Extensions) -> Result { + pub(crate) fn extract(ext: &Extensions) -> Result { let inner = IdentityInner::extract(ext); inner.get_identity()?; Ok(Self(inner)) } - pub(crate) fn logged_at(&self) -> Result, anyhow::Error> { - self.0 + pub(crate) fn logged_at(&self) -> Result, GetIdentityError> { + Ok(self + .0 .session .get(LOGIN_UNIX_TIMESTAMP_KEY)? .map(OffsetDateTime::from_unix_timestamp) - .transpose() - .map_err(anyhow::Error::from) + .transpose()?) } - pub(crate) fn last_visited_at(&self) -> Result, anyhow::Error> { - self.0 + pub(crate) fn last_visited_at(&self) -> Result, GetIdentityError> { + Ok(self + .0 .session .get(LAST_VISIT_UNIX_TIMESTAMP_KEY)? .map(OffsetDateTime::from_unix_timestamp) - .transpose() - .map_err(anyhow::Error::from) + .transpose()?) } - pub(crate) fn set_last_visited_at(&self) -> Result<(), anyhow::Error> { + 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)?; Ok(()) diff --git a/actix-identity/src/identity_ext.rs b/actix-identity/src/identity_ext.rs index 431539a86..8c4bbad69 100644 --- a/actix-identity/src/identity_ext.rs +++ b/actix-identity/src/identity_ext.rs @@ -1,27 +1,27 @@ use actix_web::{dev::ServiceRequest, guard::GuardContext, HttpMessage, HttpRequest}; -use crate::Identity; +use crate::{error::GetIdentityError, Identity}; /// Helper trait to retrieve an [`Identity`] instance from various `actix-web`'s types. pub trait IdentityExt { /// Retrieve the identity attached to the current session, if available. - fn get_identity(&self) -> Result; + fn get_identity(&self) -> Result; } impl IdentityExt for HttpRequest { - fn get_identity(&self) -> Result { + fn get_identity(&self) -> Result { Identity::extract(&self.extensions()) } } impl IdentityExt for ServiceRequest { - fn get_identity(&self) -> Result { + fn get_identity(&self) -> Result { Identity::extract(&self.extensions()) } } impl<'a> IdentityExt for GuardContext<'a> { - fn get_identity(&self) -> Result { + fn get_identity(&self) -> Result { Identity::extract(&self.req_data()) } } diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index d2fbfff9a..f88c9ca23 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -91,6 +91,7 @@ #![warn(future_incompatible)] pub mod config; +pub mod error; mod identity; mod identity_ext; mod middleware;