bazzar/api/src/routes/admin.rs

193 lines
5.0 KiB
Rust

mod api_v1;
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::config::SharedAppConfig;
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};
#[macro_export]
macro_rules! admin_send_db {
($db: expr, $msg: expr) => {{
let db = $db;
match db.send($msg).await {
Ok(Ok(res)) => res,
Ok(Err(e)) => {
log::error!("{}", e);
return Err(crate::routes::Error::Admin(Error::Database(e)));
}
Err(e) => {
log::error!("{}", e);
return 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("Password and password confirmation are different")]
DifferentPasswords,
#[error("{0}")]
Database(#[from] database::Error),
}
#[derive(Serialize)]
pub struct LogoutResponse {}
#[delete("logout")]
async fn logout(session: Session) -> Result<HttpResponse> {
session.require_admin()?;
session.clear();
Ok(HttpResponse::NotImplemented().body(""))
}
#[derive(Deserialize, Debug)]
pub struct SignInInput {
login: Option<Login>,
email: Option<Email>,
password: Password,
}
#[post("/sign-in")]
async fn sign_in(
session: Session,
db: Data<Addr<Database>>,
Json(payload): Json<SignInInput>,
) -> Result<HttpResponse> {
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<RegisterError>,
pub account: Option<Account>,
}
#[derive(Serialize)]
pub enum RegisterError {
PasswordDiffer,
}
// login_required
#[post("/register")]
async fn register(
session: Session,
Json(input): Json<RegisterInput>,
db: Data<Addr<Database>>,
config: Data<SharedAppConfig>,
) -> Result<HttpResponse> {
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.lock().web().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::from(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<HttpResponse> {
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),
);
}