use std::str::FromStr; use actix_web::{get, http::StatusCode, post, web, HttpResponse, ResponseError, Scope}; use raise::yeet; use serde::Deserialize; use sqlx::MySqlPool; use tera::Tera; use thiserror::Error; use unic_langid::subtags::Language; use crate::resources::{languages, templates}; use crate::services::db; /// A request to login #[derive(Debug, Clone, Deserialize)] struct LoginRequest { username: Box, password: Box, } /// An error occurred when authenticating, because either the username or /// password was invalid. #[derive(Debug, Clone, Error)] enum LoginFailure { #[error("No user found with the given username")] UserNotFound { username: Box }, #[error("The given password is incorrect")] IncorrectPassword { username: Box }, } impl ResponseError for LoginFailure { fn status_code(&self) -> actix_web::http::StatusCode { match self { Self::UserNotFound { .. } => StatusCode::NOT_FOUND, Self::IncorrectPassword { .. } => StatusCode::UNAUTHORIZED, } } } /// Returns `200` if login was successful. /// Returns `404` if the username is invalid. /// Returns `401` if the password was invalid. #[post("/login")] async fn login( body: web::Json, conn: web::Data, ) -> Result { let conn = conn.get_ref(); let user = db::get_user_by_username(conn, &body.username) .await .unwrap(); let Some(user) = user else { yeet!(LoginFailure::UserNotFound{ username: body.username.clone() }); }; let good_password = user.check_password(&body.password).unwrap(); let response = if good_password { HttpResponse::Ok().finish() } else { yeet!(LoginFailure::IncorrectPassword { username: body.username.clone() }); }; Ok(response) } #[get("/login")] async fn login_page( tera: web::Data, translations: web::Data, ) -> HttpResponse { // TODO find a better way of doing this let language = Language::from_str("en").unwrap(); let page = templates::login_page(&tera, language, translations.get_ref().clone()).unwrap(); HttpResponse::Ok().content_type("text/html").body(page) } pub fn service() -> Scope { web::scope("").service(login).service(login_page) }