mod api_v1; use std::sync::Arc; use actix::Addr; use actix_session::Session; use actix_web::web::{scope, Data, Json, ServiceConfig}; use actix_web::{delete, get, post, HttpResponse}; use serde::{Deserialize, Serialize}; use crate::database::{AccountByIdentity, Database}; use crate::logic::encrypt_password; use crate::model::{Account, Email, Login, PassHash, Password, PasswordConfirmation, Role}; use crate::routes::{RequireLogin, Result}; use crate::{database, model, routes, Config}; #[macro_export] macro_rules! admin_send_db { ($db: expr, $msg: expr) => {{ let db = $db; return match db.send($msg).await { Ok(Ok(res)) => Ok(HttpResponse::Ok().json(res)), Ok(Err(e)) => { log::error!("{}", e); Err(crate::routes::Error::Admin(Error::Database(e))) } Err(e) => { log::error!("{}", e); Err(crate::routes::Error::Admin(Error::DatabaseConnection)) } }; }}; } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Can't register new account")] Register, #[error("Can't hash password")] HashPass, #[error("Internal server error")] DatabaseConnection, #[error("{0}")] Database(#[from] database::Error), } #[derive(Serialize)] pub struct LogoutResponse {} #[delete("logout")] async fn logout(session: Session) -> Result { session.require_admin()?; session.clear(); Ok(HttpResponse::NotImplemented().body("")) } #[derive(Deserialize, Debug)] pub struct SignInInput { login: Option, email: Option, password: Password, } #[post("/sign-in")] async fn sign_in( session: Session, db: Data>, Json(payload): Json, ) -> Result { log::debug!("{:?}", payload); let db = db.into_inner(); let user: model::FullAccount = match db .send(AccountByIdentity { email: payload.email, login: payload.login, }) .await { Ok(Ok(user)) => user, Ok(Err(e)) => { log::error!("{}", e); return Err(routes::Error::Unauthorized); } Err(e) => { log::error!("{}", e); return Err(routes::Error::Unauthorized); } }; if let Err(e) = crate::logic::validate_password(&payload.password, &user.pass_hash) { log::error!("Password validation failed. {}", e); Err(routes::Error::Unauthorized) } else { if let Err(e) = session.insert("admin_id", *user.id) { log::error!("{:?}", e); } Ok(HttpResponse::Ok().json(model::Account::from(user))) } } #[derive(Deserialize)] pub struct RegisterInput { pub login: Login, pub email: Email, pub password: Password, pub password_confirmation: PasswordConfirmation, pub role: Role, } #[derive(Serialize, Default)] pub struct RegisterResponse { pub success: bool, pub errors: Vec, pub account: Option, } #[derive(Serialize)] pub enum RegisterError { PasswordDiffer, } // login_required #[post("/register")] async fn register( session: Session, Json(input): Json, db: Data>, config: Data>, ) -> Result { let mut response = RegisterResponse::default(); session.require_admin()?; if input.password != input.password_confirmation { response.errors.push(RegisterError::PasswordDiffer); } let hash = match encrypt_password(&input.password, &config.pass_salt) { Ok(s) => s, Err(e) => { log::error!("{e:?}"); return Err(routes::Error::Admin(Error::HashPass)); } }; match db .send(database::CreateAccount { email: input.email, login: input.login, pass_hash: PassHash(hash), role: input.role, }) .await { Ok(Ok(account)) => { response.account = Some(account.into()); } Ok(Err(e)) => { log::error!("{}", e); return Err(super::Error::Admin(Error::Register)); } Err(e) => { log::error!("{}", e); return Err(super::Error::Admin(Error::Register)); } }; response.success = response.errors.is_empty(); Ok(if response.success { HttpResponse::Ok().json(response) } else { HttpResponse::BadRequest().json(response) }) } #[get("")] async fn landing() -> Result { Ok(HttpResponse::NotImplemented() .append_header(("Content-Type", "text/html")) .body(include_str!("../../../assets/index.html"))) } pub fn configure(config: &mut ServiceConfig) { config.service( scope("/admin") .service(sign_in) .service(logout) .service(register) .service(landing) .configure(api_v1::configure), ); }