Move login/logout logic to separate module
This commit is contained in:
parent
70b417865f
commit
1647ca3838
|
@ -5,13 +5,12 @@ use axum::{
|
|||
RequestExt,
|
||||
};
|
||||
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
|
||||
use snafu::prelude::*;
|
||||
use tower_cookies::{Cookies, Cookie};
|
||||
use tower_cookies::Cookies;
|
||||
use yunohost_api::{Username, Password};
|
||||
|
||||
use crate::{
|
||||
error::*,
|
||||
state::{COOKIE_NAME, RoutableAppState, sessions::LoggedInUser},
|
||||
state::{COOKIE_NAME, RoutableAppState, LoggedInUser},
|
||||
};
|
||||
|
||||
#[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);
|
||||
let session = state.sessions.make_session(COOKIE_NAME, &form.username).await;
|
||||
cookies.add(session.cookie());
|
||||
Ok(format!("Welcome {}", &form.username))
|
||||
Ok(format!("Welcome {}", session.username()))
|
||||
} else {
|
||||
debug!("Login failed for user {}", &form.username);
|
||||
Ok(format!("Invalid login for {}", &form.username))
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
use axum::extract::State;
|
||||
use tower_cookies::{Cookies, Cookie};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::state::{
|
||||
COOKIE_NAME,
|
||||
RoutableAppState,
|
||||
sessions::LoggedOutUser,
|
||||
};
|
||||
use crate::state::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 {
|
||||
let cookie = user.cookie();
|
||||
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::*;
|
||||
|
||||
pub mod login;
|
||||
pub use login::LoggedInUser;
|
||||
pub mod logout;
|
||||
pub use logout::LoggedOutUser;
|
||||
pub mod sessions;
|
||||
use sessions::SessionManager;
|
||||
pub use sessions::{Session, SessionManager};
|
||||
|
||||
pub type RoutableAppState = Arc<AppState>;
|
||||
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
use axum::{
|
||||
extract::FromRequestParts,
|
||||
http::request::Parts,
|
||||
RequestPartsExt,
|
||||
};
|
||||
use ring::{hmac,rand};
|
||||
use snafu::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
use tokio::sync::RwLock;
|
||||
use tower_cookies::{Cookies, Cookie};
|
||||
use tower_cookies::Cookie;
|
||||
use yunohost_api::Username;
|
||||
|
||||
use crate::{
|
||||
error::*,
|
||||
state::{COOKIE_NAME, RoutableAppState},
|
||||
utils::time::now,
|
||||
};
|
||||
use crate::utils::time::now;
|
||||
|
||||
/// An error related to session management
|
||||
#[derive(Debug, Snafu)]
|
||||
|
@ -217,151 +208,3 @@ impl Session {
|
|||
).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…
Reference in New Issue
Block a user