use actix_session::Session; use actix_web::{ dev::Payload, http::{header::LOCATION, StatusCode}, web::Data, FromRequest, HttpRequest, HttpResponse, ResponseError, }; use futures::future::LocalBoxFuture; use uuid::Uuid; use crate::{store::User, State}; #[derive(serde::Deserialize, serde::Serialize)] pub(crate) struct CookieData { accepted: bool, } impl CookieData { pub(crate) fn set_accepted(session: &Session) -> Result<(), SessionError> { session .set("accepted", CookieData { accepted: true }) .map_err(|_| SessionError::Set) } fn accepted(session: &Session) -> bool { session .get::("accepted") .map(|opt| opt.is_some()) .unwrap_or(false) } } #[derive(serde::Deserialize, serde::Serialize)] pub(crate) struct UserData { id: Uuid, } impl UserData { pub(crate) fn set_data(id: Uuid, session: &Session) -> Result<(), SessionError> { session .set("user-data", UserData { id }) .map_err(|_| SessionError::Set) } pub(crate) fn remove(session: &Session) { session.remove("user-data") } fn data(session: &Session) -> Result, actix_web::Error> { Ok(session.get::("user-data")?) } } pub struct AcceptedCookies(()); pub struct Authenticated { user: User, } impl Authenticated { pub fn user(self) -> User { self.user } } #[derive(Debug, thiserror::Error)] #[error("{0}")] struct ServerError(String); impl ResponseError for ServerError { fn status_code(&self) -> StatusCode { StatusCode::SEE_OTHER } fn error_response(&self) -> HttpResponse { HttpResponse::build(self.status_code()) .header(LOCATION, self.0.clone()) .finish() } } #[derive(Debug, thiserror::Error)] pub enum SessionError { #[error("Failed to set sesion data")] Set, } impl FromRequest for AcceptedCookies { type Error = actix_web::Error; type Future = LocalBoxFuture<'static, Result>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let session_fut = Session::extract(req); let state_fut = Data::::extract(req); Box::pin(async move { let session = session_fut.await?; let state = state_fut.await?; if CookieData::accepted(&session) { Ok(AcceptedCookies(())) } else { log::debug!("Browser has not accepted cookies"); Err(ServerError(state.pages.not_found_path()).into()) } }) } } impl FromRequest for Authenticated { type Error = actix_web::Error; type Future = LocalBoxFuture<'static, Result>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let session_fut = Session::extract(req); let state_fut = Data::::extract(req); Box::pin(async move { let session = session_fut.await?; let state = state_fut.await?; if let Some(UserData { id }) = UserData::data(&session)? { let res = state.user_store.user_by_id(id).await; let user_opt = match res { Ok(user_opt) => user_opt, Err(_) => { log::debug!("Error fetching user data from store"); return Err(ServerError(state.pages.internal_server_error_path()).into()); } }; if let Some(user) = user_opt { return Ok(Authenticated { user }); } } log::debug!("No user data found for browser"); Err(ServerError(state.pages.not_found_path()).into()) }) } }