use axum::{ extract::FromRequestParts, http::request::Parts, RequestPartsExt, }; use snafu::prelude::*; use std::borrow::Cow; use tower_cookies::{Cookies, Cookie}; use yunohost_api::Username; use crate::{ error::*, state::{COOKIE_NAME, RoutableAppState, sessions::*}, }; pub struct LoggedOutUser(Session); impl LoggedOutUser { pub fn new(session: Session) -> Self { Self(session) } pub fn username(&self) -> &Username { self.0.username() } pub fn cookie(&self) -> Cookie { Cookie::build( Cow::Owned(self.0.cookie_name.to_string()), Cow::Owned(self.0.content.to_string()), ).path( Cow::Owned("/".to_string()) ).finish() } } impl From for LoggedOutUser { fn from(s: Session) -> LoggedOutUser { LoggedOutUser::new(s) } } #[async_trait] impl FromRequestParts for LoggedOutUser { type Rejection = Error; async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result { let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?; trace!("[SESSION:{}/logout] Checking if the user has a valid session", COOKIE_NAME); trace!("{:#?}", parts.headers); let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) { trace!("[SESSION:{}/logout] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie); session_cookie } else { trace!("[SESSION:{}/logout] No current session cookie", COOKIE_NAME); return Err(Error::Session { source: SessionError::NoSession { cookie_name: COOKIE_NAME.to_string(), } }); }; if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? { debug!("[SESSION:{}/logout] User {} resumed session, requesting to log out.", COOKIE_NAME, &session.username); if state.sessions.invalidate_session(session.timestamp, &session.content).await { return Ok(session.into()); } else { warn!("[SESSION:{}/logout] User {} requested logout but was already logged out. Here's the session:\n{:#?}", COOKIE_NAME, &session.username, &session); warn!("This is probably a race condition but is completely harmless."); return Err(Error::Session { source: SessionError::InvalidOrExpired { cookie_name: COOKIE_NAME.to_string(), } }); } } else { trace!("[SESSION:{}/logout] User session is invalid or has expired.", COOKIE_NAME); return Err(Error::Session { source: SessionError::InvalidOrExpired { cookie_name: COOKIE_NAME.to_string(), } }); } } }