use crate::{store::User, State}; use actix_session::{Session, UserSession}; use actix_web::{ dev::{Payload, Service, ServiceRequest, ServiceResponse, Transform}, http::{header::LOCATION, StatusCode}, web::Data, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, }; use event_listener::Event; use futures::future::{ok, LocalBoxFuture, Ready}; use std::{ cell::{Cell, RefCell}, rc::Rc, task::{Context, Poll}, }; use uuid::Uuid; #[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(()); #[derive(Clone)] 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 = req.get_session(); let state_fut = Data::::extract(req); Box::pin(async move { let state = state_fut.await?; if CookieData::accepted(&session) { Ok(AcceptedCookies(())) } else { Err(ServerError(state.pages.not_found_path()).into()) } }) } } impl FromRequest for User { type Error = actix_web::Error; type Future = LocalBoxFuture<'static, Result>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let fut = Authenticated::extract(req); Box::pin(async move { fut.await.map(|auth| auth.user()) }) } } 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 opt = req .extensions() .get::() .map(|auth_state| (auth_state.clone(), auth_state.2.listen())); let state_fut = Data::::extract(req); Box::pin(async move { let (auth_state, listen_fut) = match opt { Some(auth_state) => auth_state, None => { let state = state_fut.await?; return Err(ServerError(state.pages.not_found_path()).into()); } }; if let Some(auth) = auth_state.0.borrow().as_ref() { return Ok(auth.clone()); } if !auth_state.1.get() { listen_fut.await; if let Some(auth) = auth_state.0.borrow().as_ref() { return Ok(auth.clone()); } } let state = state_fut.await?; Err(ServerError(state.pages.not_found_path()).into()) }) } } pub struct Auth(pub State); pub struct AuthMiddleware(S, State); #[derive(Clone)] struct AuthState( Rc>>, Rc>, Rc, ); struct DropGuard(Rc, Rc>); fn auth_state() -> (AuthState, DropGuard) { let event = Rc::new(Event::new()); let state = Rc::new(RefCell::new(None)); let flag = Rc::new(Cell::new(false)); ( AuthState(state, Rc::clone(&flag), Rc::clone(&event)), DropGuard(event, flag), ) } impl Drop for DropGuard { fn drop(&mut self) { self.1.set(true); self.0.notify(usize::MAX) } } impl Transform for Auth where S: Service, Error = actix_web::Error>, S::Future: 'static, { type Request = S::Request; type Response = S::Response; type Error = S::Error; type InitError = (); type Transform = AuthMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(AuthMiddleware(service, self.0.clone())) } } impl Service for AuthMiddleware where S: Service, Error = actix_web::Error>, S::Future: 'static, { type Request = S::Request; type Response = S::Response; type Error = S::Error; type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.0.poll_ready(cx) } fn call(&mut self, req: Self::Request) -> Self::Future { let session = req.get_session(); let state = self.1.clone(); let (auth_state, drop_guard) = auth_state(); req.extensions_mut().insert(auth_state.clone()); let fut = self.0.call(req); Box::pin(async move { 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::warn!("Error fetching user data from store"); return Err(ServerError(state.pages.internal_server_error_path()).into()); } }; if let Some(user) = user_opt { if !user.suspended() { *auth_state.0.borrow_mut() = Some(Authenticated { user }); } } } drop(drop_guard); fut.await }) } }