use actix_session::Session;
use actix_utils::future::{ready, Ready};
use actix_web::{
cookie::time::OffsetDateTime,
dev::{Extensions, Payload},
http::StatusCode,
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse,
};
use crate::{
config::LogoutBehavior,
error::{
GetIdentityError, LoginError, LostIdentityError, MissingIdentityError, SessionExpiryError,
},
};
pub struct Identity(IdentityInner);
#[derive(Clone)]
pub(crate) struct IdentityInner {
pub(crate) session: Session,
pub(crate) logout_behavior: LogoutBehavior,
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 {
fn extract(ext: &Extensions) -> Self {
ext.get::<Self>()
.expect(
"No `IdentityInner` instance was found in the extensions attached to the \
incoming request. This usually means that `IdentityMiddleware` has not been \
registered as an application middleware via `App::wrap`. `Identity` cannot be used \
unless the identity machine is properly mounted: register `IdentityMiddleware` as \
a middleware for your application to fix this panic. If the problem persists, \
please file an issue on GitHub.",
)
.to_owned()
}
fn get_identity(&self) -> Result<String, GetIdentityError> {
self.session
.get::<String>(self.id_key)?
.ok_or_else(|| MissingIdentityError.into())
}
}
impl Identity {
pub fn id(&self) -> Result<String, GetIdentityError> {
self.0
.session
.get(self.0.id_key)?
.ok_or_else(|| LostIdentityError.into())
}
pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> {
let inner = IdentityInner::extract(ext);
inner.session.insert(inner.id_key, id)?;
let now = OffsetDateTime::now_utc().unix_timestamp();
if inner.is_login_deadline_enabled {
inner.session.insert(inner.login_unix_timestamp_key, now)?;
}
if inner.is_visit_deadline_enabled {
inner
.session
.insert(inner.last_visit_unix_timestamp_key, now)?;
}
inner.session.renew();
Ok(Self(inner))
}
pub fn logout(self) {
match self.0.logout_behavior {
LogoutBehavior::PurgeSession => {
self.0.session.purge();
}
LogoutBehavior::DeleteIdentityKeys => {
self.0.session.remove(self.0.id_key);
if self.0.is_login_deadline_enabled {
self.0.session.remove(self.0.login_unix_timestamp_key);
}
if self.0.is_visit_deadline_enabled {
self.0.session.remove(self.0.last_visit_unix_timestamp_key);
}
}
}
}
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>, GetIdentityError> {
Ok(self
.0
.session
.get(self.0.login_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(SessionExpiryError)?)
}
pub(crate) fn last_visited_at(&self) -> Result<Option<OffsetDateTime>, GetIdentityError> {
Ok(self
.0
.session
.get(self.0.last_visit_unix_timestamp_key)?
.map(OffsetDateTime::from_unix_timestamp)
.transpose()
.map_err(SessionExpiryError)?)
}
pub(crate) fn set_last_visited_at(&self) -> Result<(), LoginError> {
let now = OffsetDateTime::now_utc().unix_timestamp();
self.0
.session
.insert(self.0.last_visit_unix_timestamp_key, now)?;
Ok(())
}
}
impl FromRequest for Identity {
type Error = Error;
type Future = Ready<Result<Self, Self::Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(Identity::extract(&req.extensions()).map_err(|err| {
let res = actix_web::error::InternalError::from_response(
err,
HttpResponse::new(StatusCode::UNAUTHORIZED),
);
actix_web::Error::from(res)
}))
}
}