ssowat-rs/src/routes/login.rs

88 lines
3.3 KiB
Rust
Raw Normal View History

2023-08-18 10:59:50 +02:00
use axum::{
2023-08-22 19:29:40 +02:00
extract::{FromRequest, Form, Json, State},
2023-08-18 10:59:50 +02:00
http::{self, Request, StatusCode},
response::{IntoResponse, Response},
RequestExt,
};
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
2023-08-22 19:29:40 +02:00
use snafu::prelude::*;
use tower_cookies::{Cookies, Cookie};
2023-08-22 17:00:55 +02:00
use yunohost_api::{Username, Password};
2023-08-22 19:29:40 +02:00
use crate::{
error::*,
routes::COOKIE_NAME,
state::RoutableAppState,
};
2023-08-18 10:59:50 +02:00
#[derive(Debug, TryFromMultipart, Deserialize)]
pub struct LoginForm {
2023-08-22 17:00:55 +02:00
username: Username,
2023-08-18 10:59:50 +02:00
#[allow(dead_code)]
2023-08-22 17:00:55 +02:00
password: Password,
2023-08-18 10:59:50 +02:00
}
#[async_trait]
impl<S, B> FromRequest<S, B> for LoginForm
where
Json<LoginForm>: FromRequest<(), B>,
Form<LoginForm>: FromRequest<(), B>,
TypedMultipart<LoginForm>: FromRequest<S, B>,
B::Data: Into<axum::body::Bytes>,
B::Error: Into<axum::BoxError> + Send + std::error::Error,
B: Send + 'static + axum::body::HttpBody,
S: Send
{
type Rejection = Response;
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
let headers = req.headers();
if let Some(mime) = headers.get(http::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
if mime.starts_with("application/json") {
let Json(login_form): Json<LoginForm> = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(login_form);
}
if mime.starts_with("application/x-www-form-urlencoded") {
let Form(login_form) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(login_form);
}
if mime.starts_with("multipart/form-data") {
let TypedMultipart(login_form): TypedMultipart<LoginForm> = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(login_form);
}
Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
} else {
Err("No POST Content-Type".into_response())
}
}
}
#[debug_handler]
2023-08-22 19:29:40 +02:00
pub async fn route(cookies: Cookies, state: State<RoutableAppState>, form: LoginForm) -> Result<String, Error> {
trace!("ROUTE: /login/");
if let Some(session_cookie) = cookies.get(COOKIE_NAME) {
trace!("User claims to have valid {} session: {}", COOKIE_NAME, &session_cookie);
if let Some(username) = state.sessions.verify_cookie(session_cookie.value()).await.context(SessionSnafu)? {
debug!("User claims were verified. They are identified as {}", &username);
return Ok(format!("Welcome back, {}! You were already logged in.", username));
}
debug!("User claims for a {} session were unfounded. Performing login again.", COOKIE_NAME);
}
debug!("Performing login attempt for user {}", &form.username);
// No cookie, or cookie is invalid. Perform login.
if state.check_login(&form.username, &form.password).await.unwrap() {
debug!("Login was successful for user {}. Saving cookie now.", &form.username);
let (cookie_name, cookie_value) = state.sessions.make_session(COOKIE_NAME, &form.username).await;
cookies.add(Cookie::new(cookie_name, cookie_value));
Ok(format!("Welcome {}", &form.username))
2023-08-22 17:00:55 +02:00
} else {
2023-08-22 19:29:40 +02:00
debug!("Login failed for user {}", &form.username);
Ok(format!("Invalid login for {}", &form.username))
2023-08-22 17:00:55 +02:00
}
2023-08-18 10:59:50 +02:00
}