mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-23 15:51:06 +01:00
Feature: Add IdentityError to actix-identity crate. (#296)
* Add IdentityError to actix-identity crate. In order to let crates in the actix web ecosystem interact correctly with `actix_web::Error`, this commit introduces its own error type, replacing the previous usage of `anyhow::Error`. * Mend some clippy warnings on IdentityError. * Split identity error into more granular versions. - `MissingIdentityError` occurs whenever we attempt to gather information about an identity from a session, and fail. - `LoginError` occurs whenever we attempt to login via an identity, and fail. * Feedback for identity error implementation. - `IdentityError` -> `GetIdentityError` - Move error messages into Display impl where appropriate - Split `id` and `get_identity` errors into two types - Implement `source` on custom errors * Expand identity error types with struct markers. In order to get a little more future compatibility and reduce abstraction leaking, this commit introduces some contextual structs to our identity errors package. * Improve doc message for SessionExpiryError. Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> * Improve identity error docs and messaging. Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> * Expand LostIdentityError with placeholder. Adds a placeholder unit struct to the LostIdentityError variant of GetIdentityError, which should let us expand on that variant with extra context later if we like. * Add From coercion for LostIdentityError. Improve the ergonomics of using the LostIdentityError unit struct. * Update Cargo.toml * Update CHANGES.md * expose identity error module * fix error impl Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Co-authored-by: Rob Ede <robjtede@icloud.com>
This commit is contained in:
parent
708aa945dc
commit
1ed893a08c
@ -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]
|
||||
|
@ -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"] }
|
||||
|
156
actix-identity/src/error.rs
Normal file
156
actix-identity/src/error.rs
Normal file
@ -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<SessionInsertError> 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<LostIdentityError> for GetIdentityError {
|
||||
fn from(error: LostIdentityError) -> Self {
|
||||
Self::LostIdentityError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MissingIdentityError> for GetIdentityError {
|
||||
fn from(error: MissingIdentityError) -> Self {
|
||||
Self::MissingIdentityError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComponentRange> for GetIdentityError {
|
||||
fn from(error: ComponentRange) -> Self {
|
||||
Self::SessionExpiryError(SessionExpiryError(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionGetError> for GetIdentityError {
|
||||
fn from(source: SessionGetError) -> Self {
|
||||
Self::SessionGetError(source)
|
||||
}
|
||||
}
|
@ -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<String, anyhow::Error> {
|
||||
fn get_identity(&self) -> Result<String, GetIdentityError> {
|
||||
self.session
|
||||
.get::<String>(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::<String>(ID_KEY)?
|
||||
.ok_or_else(|| MissingIdentityError.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,10 +125,11 @@ impl Identity {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn id(&self) -> Result<String, anyhow::Error> {
|
||||
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<String, GetIdentityError> {
|
||||
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<Self, anyhow::Error> {
|
||||
pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> {
|
||||
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<Self, anyhow::Error> {
|
||||
pub(crate) fn extract(ext: &Extensions) -> Result<Self, GetIdentityError> {
|
||||
let inner = IdentityInner::extract(ext);
|
||||
inner.get_identity()?;
|
||||
Ok(Self(inner))
|
||||
}
|
||||
|
||||
pub(crate) fn logged_at(&self) -> Result<Option<OffsetDateTime>, anyhow::Error> {
|
||||
self.0
|
||||
pub(crate) fn logged_at(&self) -> Result<Option<OffsetDateTime>, 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<Option<OffsetDateTime>, anyhow::Error> {
|
||||
self.0
|
||||
pub(crate) fn last_visited_at(&self) -> Result<Option<OffsetDateTime>, 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(())
|
||||
|
@ -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<Identity, anyhow::Error>;
|
||||
fn get_identity(&self) -> Result<Identity, GetIdentityError>;
|
||||
}
|
||||
|
||||
impl IdentityExt for HttpRequest {
|
||||
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
|
||||
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
|
||||
Identity::extract(&self.extensions())
|
||||
}
|
||||
}
|
||||
|
||||
impl IdentityExt for ServiceRequest {
|
||||
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
|
||||
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
|
||||
Identity::extract(&self.extensions())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IdentityExt for GuardContext<'a> {
|
||||
fn get_identity(&self) -> Result<Identity, anyhow::Error> {
|
||||
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
|
||||
Identity::extract(&self.req_data())
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,7 @@
|
||||
#![warn(future_incompatible)]
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
mod identity;
|
||||
mod identity_ext;
|
||||
mod middleware;
|
||||
|
Loading…
Reference in New Issue
Block a user