hyaenidae/accounts/src/forms/login.rs

146 lines
3.4 KiB
Rust

use crate::{to_cookie_page, to_home, AcceptedCookies, Authenticated, Error, State};
use actix_session::Session;
use actix_web::{error::BlockingError, web, HttpResponse};
pub type LoginPageArgs = (
Option<AcceptedCookies>,
Option<Authenticated>,
web::Data<State>,
);
pub type LoginArgs = (
Option<AcceptedCookies>,
Option<Authenticated>,
web::Data<State>,
Session,
web::Form<LoginForm>,
);
#[derive(Debug, serde::Deserialize)]
pub struct LoginForm {
username: String,
password: String,
}
#[derive(Debug)]
pub struct LoginState {
state: State,
form: Option<LoginForm>,
error: Option<LoginError>,
}
pub fn login_page(
(cookies, authenticated, state): LoginPageArgs,
) -> Result<LoginState, HttpResponse> {
if cookies.is_none() {
return Err(to_cookie_page(&state));
}
if authenticated.is_some() {
return Err(to_home(&state));
}
Ok(LoginState::new_empty(&state))
}
pub async fn login(
(cookies, authenticated, state, session, form): LoginArgs,
) -> Result<Result<LoginState, HttpResponse>, Error> {
if cookies.is_none() {
return Ok(Err(to_cookie_page(&state)));
}
if authenticated.is_some() {
return Ok(Err(to_home(&state)));
}
let form = form.into_inner();
match try_login(session, &form, &state).await? {
Ok(res) => Ok(Err(res)),
Err(e) => Ok(Ok(LoginState::new_from_request(&state, form, e))),
}
}
impl LoginState {
fn new_empty(state: &State) -> Self {
LoginState {
state: state.clone(),
form: None,
error: None,
}
}
fn new_from_request(state: &State, form: LoginForm, error: LoginError) -> Self {
LoginState {
state: state.clone(),
form: Some(form),
error: Some(error),
}
}
pub fn register_path(&self) -> String {
self.state.pages.register_path()
}
pub fn login_path(&self) -> String {
self.state.pages.login_path()
}
pub fn home_path(&self) -> String {
self.state.pages.home_path()
}
pub fn username(&self) -> Option<String> {
self.form.as_ref().map(|form| form.username.clone())
}
pub fn username_error(&self) -> Option<String> {
self.error.as_ref().and_then(|e| e.username())
}
pub fn password(&self) -> Option<String> {
self.form.as_ref().map(|form| form.password.clone())
}
}
#[derive(Debug)]
enum LoginError {
Invalid,
}
impl LoginError {
fn username(&self) -> Option<String> {
match self {
LoginError::Invalid => Some("Username or Password is incorrect".to_owned()),
}
}
}
async fn try_login(
session: Session,
form: &LoginForm,
state: &State,
) -> Result<Result<HttpResponse, LoginError>, Error> {
let res = state
.user_store
.exec(crate::store::Login {
username: form.username.clone(),
password: form.password.clone(),
})
.await;
let user = match res {
Ok(user) => user,
Err(BlockingError::Error(crate::store::StoreError::AuthenticationFailed))
| Err(BlockingError::Error(crate::store::StoreError::NoUser)) => {
return Ok(Err(LoginError::Invalid))
}
Err(e) => return Err(e.into()),
};
crate::extractors::UserData::set_data(user.id(), &session)?;
Ok(Ok(to_home(&state)))
}