Move login/logout logic to separate module
This commit is contained in:
		
							parent
							
								
									70b417865f
								
							
						
					
					
						commit
						1647ca3838
					
				@ -5,13 +5,12 @@ use axum::{
 | 
				
			|||||||
    RequestExt,
 | 
					    RequestExt,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
 | 
					use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
 | 
				
			||||||
use snafu::prelude::*;
 | 
					use tower_cookies::Cookies;
 | 
				
			||||||
use tower_cookies::{Cookies, Cookie};
 | 
					 | 
				
			||||||
use yunohost_api::{Username, Password};
 | 
					use yunohost_api::{Username, Password};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    error::*,
 | 
					    error::*,
 | 
				
			||||||
    state::{COOKIE_NAME, RoutableAppState, sessions::LoggedInUser},
 | 
					    state::{COOKIE_NAME, RoutableAppState, LoggedInUser},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, TryFromMultipart, Deserialize)]
 | 
					#[derive(Debug, TryFromMultipart, Deserialize)]
 | 
				
			||||||
@ -74,7 +73,7 @@ pub async fn route(user: Option<LoggedInUser>, cookies: Cookies, state: State<Ro
 | 
				
			|||||||
        debug!("Login was successful for user {}. Saving cookie now.", &form.username);
 | 
					        debug!("Login was successful for user {}. Saving cookie now.", &form.username);
 | 
				
			||||||
        let session = state.sessions.make_session(COOKIE_NAME, &form.username).await;
 | 
					        let session = state.sessions.make_session(COOKIE_NAME, &form.username).await;
 | 
				
			||||||
        cookies.add(session.cookie());
 | 
					        cookies.add(session.cookie());
 | 
				
			||||||
        Ok(format!("Welcome {}", &form.username))
 | 
					        Ok(format!("Welcome {}", session.username()))
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        debug!("Login failed for user {}", &form.username);
 | 
					        debug!("Login failed for user {}", &form.username);
 | 
				
			||||||
        Ok(format!("Invalid login for {}", &form.username))
 | 
					        Ok(format!("Invalid login for {}", &form.username))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,10 @@
 | 
				
			|||||||
use axum::extract::State;
 | 
					 | 
				
			||||||
use tower_cookies::{Cookies, Cookie};
 | 
					use tower_cookies::{Cookies, Cookie};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::borrow::Cow;
 | 
					use std::borrow::Cow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::state::{
 | 
					use crate::state::LoggedOutUser;
 | 
				
			||||||
    COOKIE_NAME,
 | 
					 | 
				
			||||||
    RoutableAppState,
 | 
					 | 
				
			||||||
    sessions::LoggedOutUser,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies, state: State<RoutableAppState>) -> String {
 | 
					pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies) -> String {
 | 
				
			||||||
    if let Some(user) = user {
 | 
					    if let Some(user) = user {
 | 
				
			||||||
        let cookie = user.cookie();
 | 
					        let cookie = user.cookie();
 | 
				
			||||||
        cookies.remove(Cookie::new(Cow::Owned(cookie.name().to_string()), Cow::Owned(cookie.value().to_string())));
 | 
					        cookies.remove(Cookie::new(Cow::Owned(cookie.name().to_string()), Cow::Owned(cookie.value().to_string())));
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										83
									
								
								src/state/login.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/state/login.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    extract::FromRequestParts,
 | 
				
			||||||
 | 
					    http::request::Parts,
 | 
				
			||||||
 | 
					    RequestPartsExt,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use snafu::prelude::*;
 | 
				
			||||||
 | 
					use tower_cookies::Cookies;
 | 
				
			||||||
 | 
					use yunohost_api::Username;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    error::*,
 | 
				
			||||||
 | 
					    state::{COOKIE_NAME, RoutableAppState, sessions::*},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct LoggedInUser {
 | 
				
			||||||
 | 
					    username: Username,
 | 
				
			||||||
 | 
					    timestamp: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LoggedInUser {
 | 
				
			||||||
 | 
					    pub fn new(username: Username, timestamp: i64) -> LoggedInUser {
 | 
				
			||||||
 | 
					        LoggedInUser {
 | 
				
			||||||
 | 
					            username,
 | 
				
			||||||
 | 
					            timestamp,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn username(&self) -> &Username {
 | 
				
			||||||
 | 
					        &self.username
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn timestamp(&self) -> i64 {
 | 
				
			||||||
 | 
					        self.timestamp
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<Session> for LoggedInUser {
 | 
				
			||||||
 | 
					    fn from(s: Session) -> LoggedInUser {
 | 
				
			||||||
 | 
					        LoggedInUser::new(s.username, s.timestamp)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for LoggedInUser {
 | 
				
			||||||
 | 
					    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        self.username.fmt(fmt)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl FromRequestParts<RoutableAppState> for LoggedInUser {
 | 
				
			||||||
 | 
					    type Rejection = Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
 | 
					        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
				
			||||||
 | 
					        trace!("[SESSION:{}/login] Checking if the user has a valid session", COOKIE_NAME);
 | 
				
			||||||
 | 
					        let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) {
 | 
				
			||||||
 | 
					            trace!("[SESSION:{}/login] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie);
 | 
				
			||||||
 | 
					            session_cookie
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            trace!("[SESSION:{}/login] 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:{}/login] User {} resumed session.", COOKIE_NAME, &session.username);
 | 
				
			||||||
 | 
					            return Ok(session.into());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            trace!("[SESSION:{}/login] User session is invalid or has expired.", COOKIE_NAME);
 | 
				
			||||||
 | 
					            return Err(Error::Session {
 | 
				
			||||||
 | 
					                source: SessionError::InvalidOrExpired {
 | 
				
			||||||
 | 
					                    cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/state/logout.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/state/logout.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					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<Session> for LoggedOutUser {
 | 
				
			||||||
 | 
					    fn from(s: Session) -> LoggedOutUser {
 | 
				
			||||||
 | 
					        LoggedOutUser::new(s)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl FromRequestParts<RoutableAppState> for LoggedOutUser {
 | 
				
			||||||
 | 
					    type Rejection = Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
 | 
					        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(),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -5,8 +5,12 @@ use std::sync::Arc;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::error::*;
 | 
					use crate::error::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod login;
 | 
				
			||||||
 | 
					pub use login::LoggedInUser;
 | 
				
			||||||
 | 
					pub mod logout;
 | 
				
			||||||
 | 
					pub use logout::LoggedOutUser;
 | 
				
			||||||
pub mod sessions;
 | 
					pub mod sessions;
 | 
				
			||||||
use sessions::SessionManager;
 | 
					pub use sessions::{Session, SessionManager};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type RoutableAppState = Arc<AppState>;
 | 
					pub type RoutableAppState = Arc<AppState>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,11 @@
 | 
				
			|||||||
use axum::{
 | 
					 | 
				
			||||||
    extract::FromRequestParts,
 | 
					 | 
				
			||||||
    http::request::Parts,
 | 
					 | 
				
			||||||
    RequestPartsExt,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use ring::{hmac,rand};
 | 
					use ring::{hmac,rand};
 | 
				
			||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use std::borrow::Cow;
 | 
					use std::borrow::Cow;
 | 
				
			||||||
use tokio::sync::RwLock;
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
use tower_cookies::{Cookies, Cookie};
 | 
					use tower_cookies::Cookie;
 | 
				
			||||||
use yunohost_api::Username;
 | 
					use yunohost_api::Username;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::utils::time::now;
 | 
				
			||||||
    error::*,
 | 
					 | 
				
			||||||
    state::{COOKIE_NAME, RoutableAppState},
 | 
					 | 
				
			||||||
    utils::time::now,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// An error related to session management
 | 
					/// An error related to session management
 | 
				
			||||||
#[derive(Debug, Snafu)]
 | 
					#[derive(Debug, Snafu)]
 | 
				
			||||||
@ -217,151 +208,3 @@ impl Session {
 | 
				
			|||||||
        ).finish()
 | 
					        ).finish()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Clone, Debug)]
 | 
					 | 
				
			||||||
pub struct LoggedInUser {
 | 
					 | 
				
			||||||
    username: Username,
 | 
					 | 
				
			||||||
    timestamp: i64,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl LoggedInUser {
 | 
					 | 
				
			||||||
    pub fn new(username: Username, timestamp: i64) -> LoggedInUser {
 | 
					 | 
				
			||||||
        LoggedInUser {
 | 
					 | 
				
			||||||
            username,
 | 
					 | 
				
			||||||
            timestamp,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn as_str(&self) -> &str {
 | 
					 | 
				
			||||||
        self.username.as_str()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn username(&self) -> &Username {
 | 
					 | 
				
			||||||
        &self.username
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn timestamp(&self) -> i64 {
 | 
					 | 
				
			||||||
        self.timestamp
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<Session> for LoggedInUser {
 | 
					 | 
				
			||||||
    fn from(s: Session) -> LoggedInUser {
 | 
					 | 
				
			||||||
        LoggedInUser {
 | 
					 | 
				
			||||||
            username: s.username,
 | 
					 | 
				
			||||||
            timestamp: s.timestamp,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::fmt::Display for LoggedInUser {
 | 
					 | 
				
			||||||
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        self.username.fmt(fmt)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[async_trait]
 | 
					 | 
				
			||||||
impl FromRequestParts<RoutableAppState> for LoggedInUser {
 | 
					 | 
				
			||||||
    type Rejection = Error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
					 | 
				
			||||||
        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
					 | 
				
			||||||
        trace!("[SESSION:{}/login] Checking if the user has a valid session", COOKIE_NAME);
 | 
					 | 
				
			||||||
        let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) {
 | 
					 | 
				
			||||||
            trace!("[SESSION:{}/login] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie);
 | 
					 | 
				
			||||||
            session_cookie
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            trace!("[SESSION:{}/login] 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:{}/login] User {} resumed session.", COOKIE_NAME, &session.username);
 | 
					 | 
				
			||||||
            return Ok(session.into());
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            trace!("[SESSION:{}/login] User session is invalid or has expired.", COOKIE_NAME);
 | 
					 | 
				
			||||||
            return Err(Error::Session {
 | 
					 | 
				
			||||||
                source: SessionError::InvalidOrExpired {
 | 
					 | 
				
			||||||
                    cookie_name: COOKIE_NAME.to_string(),
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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<Session> for LoggedOutUser {
 | 
					 | 
				
			||||||
    fn from(s: Session) -> LoggedOutUser {
 | 
					 | 
				
			||||||
        LoggedOutUser(s)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[async_trait]
 | 
					 | 
				
			||||||
impl FromRequestParts<RoutableAppState> for LoggedOutUser {
 | 
					 | 
				
			||||||
    type Rejection = Error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
					 | 
				
			||||||
        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(),
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user