what2watch/src/login.rs

262 lines
7.4 KiB
Rust
Raw Normal View History

2023-05-28 20:44:50 +00:00
use axum::{
http::StatusCode,
2023-05-29 00:55:16 +00:00
response::{IntoResponse, Redirect, Response},
Form,
2023-05-28 20:44:50 +00:00
};
2023-06-20 19:14:18 +00:00
use serde::{Deserialize, Serialize};
2023-05-28 20:44:50 +00:00
2023-12-25 00:21:55 +00:00
use crate::{
auth::{AuthError, AuthErrorKind, Credentials},
AuthSession, LoginPage, LogoutPage, LogoutSuccessPage,
};
2023-05-28 20:44:50 +00:00
//-************************************************************************
// Login error and success types
//-************************************************************************
2023-12-25 00:21:55 +00:00
impl IntoResponse for AuthError {
2023-05-28 20:44:50 +00:00
fn into_response(self) -> Response {
match self.0 {
2023-12-25 00:21:55 +00:00
AuthErrorKind::Internal => (
2023-05-28 20:44:50 +00:00
StatusCode::INTERNAL_SERVER_ERROR,
"An unknown error occurred; you cursed, brah?",
)
.into_response(),
2023-12-25 00:21:55 +00:00
AuthErrorKind::Unknown => (StatusCode::OK, "Not successful.").into_response(),
2023-05-28 20:44:50 +00:00
}
}
}
2023-06-20 19:14:18 +00:00
// for receiving form submissions
2023-12-19 00:48:54 +00:00
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
2023-06-20 19:14:18 +00:00
pub struct LoginPostForm {
pub username: String,
pub password: String,
2023-12-18 01:38:22 +00:00
pub destination: Option<String>,
}
impl From<LoginPostForm> for Credentials {
2023-12-19 00:48:54 +00:00
fn from(form: LoginPostForm) -> Self {
2023-12-18 01:38:22 +00:00
Self {
2023-12-19 00:48:54 +00:00
username: form.username,
password: form.password,
2023-12-18 01:38:22 +00:00
}
}
2023-06-20 19:14:18 +00:00
}
2023-05-28 20:44:50 +00:00
//-************************************************************************
// Login handlers
//-************************************************************************
/// Handle login queries
#[axum::debug_handler]
pub async fn post_login(
2023-12-18 00:41:04 +00:00
mut auth: AuthSession,
2023-12-19 00:48:54 +00:00
Form(mut login_form): Form<LoginPostForm>,
2023-12-25 00:21:55 +00:00
) -> Result<impl IntoResponse, AuthError> {
2023-12-19 00:48:54 +00:00
let dest = login_form.destination.take();
2023-12-25 00:21:55 +00:00
let user = match auth.authenticate(login_form.clone().into()).await {
Ok(Some(user)) => user,
Ok(None) => return Ok(LoginPage::default().into_response()),
Err(_) => return Err(AuthErrorKind::Internal.into()),
};
if auth.login(&user).await.is_err() {
return Err(AuthErrorKind::Internal.into());
}
if let Some(ref next) = dest {
Ok(Redirect::to(next).into_response())
} else {
Ok(Redirect::to("/").into_response())
2023-05-29 00:55:16 +00:00
}
2023-05-28 20:44:50 +00:00
}
pub async fn get_login() -> impl IntoResponse {
2023-06-20 19:14:18 +00:00
LoginPage::default()
2023-05-28 20:44:50 +00:00
}
2023-05-29 18:13:12 +00:00
pub async fn get_logout() -> impl IntoResponse {
2023-06-20 19:14:18 +00:00
LogoutPage
2023-05-29 18:13:12 +00:00
}
2023-12-18 00:41:04 +00:00
pub async fn post_logout(mut auth: AuthSession) -> impl IntoResponse {
match auth.logout().await {
2023-12-18 00:41:04 +00:00
Ok(_) => LogoutSuccessPage.into_response(),
Err(e) => {
tracing::debug!("{e}");
2023-12-25 00:21:55 +00:00
let e: AuthError = AuthErrorKind::Internal.into();
2023-12-18 00:41:04 +00:00
e.into_response()
}
}
2023-05-29 18:13:12 +00:00
}
//-************************************************************************
// tests
//-************************************************************************
#[cfg(test)]
mod test {
use crate::{
get_db_pool,
templates::{LoginPage, LogoutPage, LogoutSuccessPage, MainPage},
2023-07-28 23:15:27 +00:00
test_utils::{massage, server_with_pool, FORM_CONTENT_TYPE},
User,
};
const LOGIN_FORM: &str = "username=test_user&password=a";
#[test]
fn get_login() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s.get("/login").await;
2023-12-18 00:41:04 +00:00
let body = std::str::from_utf8(resp.as_bytes()).unwrap().to_string();
assert_eq!(body, LoginPage::default().to_string());
})
}
#[test]
fn post_login_success() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let body = massage(LOGIN_FORM);
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s
.post("/login")
.expect_failure()
.content_type(FORM_CONTENT_TYPE)
.bytes(body)
.await;
assert_eq!(resp.status_code(), 303);
})
}
#[test]
fn post_login_bad_user() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let form = "username=test_LOSER&password=aaaa";
let body = massage(form);
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s
.post("/login")
.expect_success()
.content_type(FORM_CONTENT_TYPE)
.bytes(body)
.await;
assert_eq!(resp.status_code(), 200);
})
}
#[test]
fn post_login_bad_password() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let form = "username=test_user&password=bbbb";
let body = massage(form);
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s
.post("/login")
.expect_success()
.content_type(FORM_CONTENT_TYPE)
.bytes(body)
.await;
assert_eq!(resp.status_code(), 200);
})
}
#[test]
fn get_logout() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s.get("/logout").await;
2023-12-18 00:41:04 +00:00
let body = std::str::from_utf8(resp.as_bytes()).unwrap().to_string();
assert_eq!(body, LogoutPage.to_string());
})
}
#[test]
fn post_logout_not_logged_in() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let resp = s.post("/logout").await;
resp.assert_status_ok();
2023-12-18 00:41:04 +00:00
let body = std::str::from_utf8(resp.as_bytes()).unwrap();
let default = LogoutSuccessPage.to_string();
assert_eq!(body, &default);
})
}
#[test]
fn post_logout_logged_in() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
// log in and prove it
let db = get_db_pool();
rt.block_on(async {
let s = server_with_pool(&db).await;
let body = massage(LOGIN_FORM);
let resp = s
.post("/login")
.expect_failure()
.content_type(FORM_CONTENT_TYPE)
.bytes(body)
.await;
assert_eq!(resp.status_code(), 303);
2023-07-28 23:15:27 +00:00
let user = User::try_get("test_user", &db).await.unwrap();
2023-12-25 00:21:55 +00:00
let logged_in = MainPage { user }.to_string();
let main_page = s.get("/").await;
2023-12-18 00:41:04 +00:00
let body = std::str::from_utf8(main_page.as_bytes()).unwrap();
assert_eq!(&logged_in, body);
let resp = s.post("/logout").await;
2023-12-18 00:41:04 +00:00
let body = std::str::from_utf8(resp.as_bytes()).unwrap();
let default = LogoutSuccessPage.to_string();
assert_eq!(body, &default);
})
}
}