Better errors, move create auth pair

This commit is contained in:
eraden 2022-05-22 14:19:11 +02:00
parent 9882b575de
commit 28e9736562
41 changed files with 494 additions and 214 deletions

134
Cargo.lock generated
View File

@ -9,14 +9,13 @@ dependencies = [
"actix 0.13.0",
"actix-rt",
"bus",
"chrono",
"config",
"database_manager",
"log",
"model",
"pretty_env_logger",
"serde",
"thiserror",
"uuid 0.8.2",
]
[[package]]
@ -840,6 +839,7 @@ dependencies = [
"log",
"model",
"pretty_env_logger",
"serde",
"thiserror",
"uuid 0.8.2",
]
@ -1159,6 +1159,7 @@ dependencies = [
"model",
"pretty_env_logger",
"rand",
"serde",
"sqlx",
"sqlx-core",
"thiserror",
@ -1393,6 +1394,50 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fluent"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash",
"self_cell",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
dependencies = [
"thiserror",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -1443,6 +1488,7 @@ dependencies = [
"log",
"model",
"pretty_env_logger",
"serde",
"thiserror",
"tokio",
"uuid 0.8.2",
@ -1982,6 +2028,26 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "intl-memoizer"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf"
dependencies = [
"tinystr",
"unic-langid",
]
[[package]]
name = "ipnet"
version = "2.5.0"
@ -2063,6 +2129,21 @@ dependencies = [
"sha2",
]
[[package]]
name = "lang_provider"
version = "0.1.0"
dependencies = [
"actix 0.13.0",
"actix-rt",
"config",
"fluent",
"log",
"model",
"pretty_env_logger",
"thiserror",
"unic-langid",
]
[[package]]
name = "language-tags"
version = "0.3.2"
@ -2531,6 +2612,7 @@ dependencies = [
"log",
"model",
"pretty_env_logger",
"serde",
"thiserror",
"uuid 0.8.2",
]
@ -3076,6 +3158,12 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -3258,6 +3346,12 @@ dependencies = [
"web-sys",
]
[[package]]
name = "self_cell"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
[[package]]
name = "semver"
version = "0.9.0"
@ -3831,6 +3925,12 @@ dependencies = [
"syn",
]
[[package]]
name = "tinystr"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1"
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -3867,6 +3967,8 @@ dependencies = [
"config",
"database_manager",
"derive_more",
"futures",
"futures-util",
"hmac",
"jwt",
"log",
@ -3878,6 +3980,7 @@ dependencies = [
"serde",
"sha2",
"thiserror",
"tokio",
"uuid 0.8.2",
]
@ -4047,6 +4150,15 @@ dependencies = [
"unchecked-index",
]
[[package]]
name = "type-map"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
dependencies = [
"rustc-hash",
]
[[package]]
name = "typenum"
version = "1.15.0"
@ -4095,6 +4207,24 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-langid"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5"
dependencies = [
"unic-langid-impl",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d"
dependencies = [
"tinystr",
]
[[package]]
name = "unic-segment"
version = "0.9.0"

View File

@ -7,6 +7,7 @@ edition = "2021"
model = { path = "../../shared/model" }
config = { path = "../../shared/config" }
database_manager = { path = "../database_manager" }
#token_manager = { path = "../token_manager" }
bus = { path = "../../shared/bus" }
actix = { version = "0.13", features = [] }
@ -14,5 +15,7 @@ actix-rt = { version = "2.7", features = [] }
thiserror = { version = "1.0.31" }
serde = { version = "1.0.137", features = ["derive"] }
log = { version = "0.4", features = [] }
pretty_env_logger = { version = "0.4", features = [] }

View File

@ -1,11 +1,23 @@
use actix::Addr;
use config::SharedAppConfig;
use database_manager::query_db;
use model::{Email, Encrypt, FullAccount, Login, Password, Role};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "account")]
pub enum Error {
#[error("Unable to send or receive msg from database")]
DbCritical,
#[error("Failed to load account data")]
Account,
#[error("Failed to load account addresses")]
Addresses,
#[error("Unable to save record")]
Saving,
#[error("Unable to hash password")]
Hashing,
#[error("{0}")]
Db(#[from] database_manager::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@ -19,7 +31,8 @@ macro_rules! account_async_handler {
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
use actix::WrapFuture;
let db = self.db.clone();
Box::pin(async { $async(msg, db).await }.into_actor(self))
let config = self.config.clone();
Box::pin(async { $async(msg, db, config).await }.into_actor(self))
}
}
};
@ -60,14 +73,14 @@ macro_rules! query_account {
};
}
#[derive(Debug)]
pub struct AccountManager {
db: actix::Addr<database_manager::Database>,
db: Addr<database_manager::Database>,
config: SharedAppConfig,
}
impl AccountManager {
pub fn new(db: actix::Addr<database_manager::Database>) -> Self {
Self { db }
pub fn new(config: SharedAppConfig, db: Addr<database_manager::Database>) -> Self {
Self { config, db }
}
}
@ -76,7 +89,7 @@ impl actix::Actor for AccountManager {
}
pub struct MeResult {
pub account: model::FullAccount,
pub account: FullAccount,
pub addresses: Vec<model::Address>,
}
@ -88,8 +101,12 @@ pub struct Me {
account_async_handler!(Me, me, MeResult);
pub(crate) async fn me(msg: Me, db: actix::Addr<database_manager::Database>) -> Result<MeResult> {
let account: model::FullAccount = query_db!(
pub(crate) async fn me(
msg: Me,
db: Addr<database_manager::Database>,
_config: SharedAppConfig,
) -> Result<MeResult> {
let account: FullAccount = query_db!(
db,
database_manager::FindAccount {
account_id: msg.account_id
@ -105,3 +122,43 @@ pub(crate) async fn me(msg: Me, db: actix::Addr<database_manager::Database>) ->
);
Ok(MeResult { account, addresses })
}
#[derive(actix::Message)]
#[rtype(result = "Result<FullAccount>")]
pub struct CreateAccount {
pub email: Email,
pub login: Login,
pub password: Password,
pub role: Role,
}
account_async_handler!(CreateAccount, create_account, FullAccount);
pub(crate) async fn create_account(
msg: CreateAccount,
db: Addr<database_manager::Database>,
config: SharedAppConfig,
) -> Result<FullAccount> {
let hash = {
match msg.password.encrypt(&config.lock().web().pass_salt()) {
Ok(hash) => hash,
Err(e) => {
log::error!("{e:?}");
return Err(Error::Hashing);
}
}
};
let account: FullAccount = query_db!(
db,
database_manager::CreateAccount {
email: msg.email,
login: msg.login,
pass_hash: model::PassHash::new(hash),
role: msg.role,
},
Error::DbCritical,
Error::Saving
);
Ok(account)
}

View File

@ -4,7 +4,6 @@ use std::collections::HashSet;
use database_manager::{query_db, Database};
use model::{PaymentMethod, ShoppingCartId};
use serde::Serialize;
#[macro_export]
macro_rules! cart_async_handler {
@ -56,8 +55,8 @@ macro_rules! query_cart {
};
}
#[derive(Debug, thiserror::Error, Serialize)]
#[serde(rename_all = "kebab-case")]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "cart")]
pub enum Error {
#[error("System can't ensure shopping cart existence")]
ShoppingCartFailed,

View File

@ -28,3 +28,5 @@ fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid"], opt
rand = { version = "0.8.5", optional = true }
itertools = { version = "0.10.3" }
serde = { version = "1.0", features = ["derive"] }

View File

@ -7,7 +7,8 @@ use crate::{
ShoppingCartSetState,
};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "account-order")]
pub enum Error {
#[error("Can't create account order")]
CantCreate,

View File

@ -5,7 +5,8 @@ use sqlx::PgPool;
use crate::{db_async_handler, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case")]
pub enum Error {
#[error("Can't create account")]
CantCreate,

View File

@ -1,6 +1,6 @@
use crate::{db_async_handler, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Can't load account addresses")]
AccountAddresses,

View File

@ -42,10 +42,16 @@ macro_rules! db_async_handler {
};
($msg: ty, $async: ident, $res: ty, $inner_async: ident) => {
async fn $inner_async(msg: $msg, pool: sqlx::PgPool) -> Result<$res> {
let mut t = pool.begin().await?;
let mut t = pool.begin().await.map_err(|e| {
log::error!("{:?}", e);
$crate::Error::TransactionFailed
})?;
match $async(msg, &mut t).await {
Ok(res) => {
t.commit().await?;
t.commit().await.map_err(|e| {
log::error!("{:?}", e);
$crate::Error::TransactionFailed
})?;
Ok(res)
}
Err(e) => {
@ -116,10 +122,9 @@ macro_rules! query_db {
};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case")]
pub enum Error {
#[error("Failed to connect to database. {0:?}")]
Connect(#[from] sqlx::Error),
#[error("{0}")]
Account(#[from] accounts::Error),
#[error("{0}")]
@ -142,6 +147,8 @@ pub enum Error {
ProductPhoto(#[from] product_photos::Error),
#[error("{0}")]
AccountAddress(#[from] addresses::Error),
#[error("Failed to start or finish transaction")]
TransactionFailed,
}
pub type Result<T> = std::result::Result<T, Error>;
@ -161,10 +168,13 @@ impl Clone for Database {
}
impl Database {
pub async fn build(config: SharedAppConfig) -> Result<Self> {
pub async fn build(config: SharedAppConfig) -> Self {
let url = config.lock().database().url();
let pool = sqlx::PgPool::connect(&url).await.map_err(Error::Connect)?;
Ok(Database { pool })
let pool = PgPool::connect(&url).await.unwrap_or_else(|e| {
log::error!("Failed to connect to database. {e:?}");
std::process::exit(1);
});
Database { pool }
}
pub fn pool(&self) -> &PgPool {

View File

@ -6,7 +6,7 @@ use sqlx::PgPool;
use super::Result;
use crate::db_async_handler;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Can't create order item")]
CantCreate,

View File

@ -1,6 +1,6 @@
use crate::{MultiLoad, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Failed to create photo")]
Create,

View File

@ -1,6 +1,6 @@
use crate::{db_async_handler, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Failed to attach photo to product")]
Create,

View File

@ -9,7 +9,7 @@ use model::{
use super::Result;
use crate::MultiLoad;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Unable to load all products")]
All,

View File

@ -4,7 +4,7 @@ use sqlx::PgPool;
use super::Result;
use crate::db_async_handler;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Can't create shopping cart item")]
CantCreate,

View File

@ -4,7 +4,7 @@ use sqlx::PgPool;
use super::Result;
use crate::db_async_handler;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Can't create shopping cart")]
CantCreate,

View File

@ -4,7 +4,7 @@ use sqlx::PgPool;
use crate::{MultiLoad, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Unable to load all stocks")]
All,

View File

@ -4,7 +4,7 @@ use sqlx::PgPool;
use crate::{db_async_handler, Result};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Failed to save new token")]
Create,

View File

@ -20,7 +20,8 @@ macro_rules! mail_async_handler {
static STYLE: &str = include_str!("../assets/style.css");
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "email")]
pub enum Error {
#[error("Failed to render reset password template")]
ResetPassTemplate,

View File

@ -15,6 +15,8 @@ actix-rt = { version = "2.7", features = [] }
thiserror = { version = "1.0.31" }
serde = { version = "1.0", features = ["derive"] }
uuid = { version = "0.8", features = ["serde"] }
chrono = { version = "0.4", features = ["serde"] }

View File

@ -69,16 +69,17 @@ macro_rules! query_fs {
};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "fs")]
pub enum Error {
#[error("Can't access file system. Please check privileges")]
StorageUnavailable,
#[error("Can't write to file. Please check privileges. {0:?}")]
CantWrite(std::io::Error),
#[error("Can't write to file. Please check privileges.")]
CantWrite,
#[error("Can't write to file. There's no more space on disk")]
NoSpace,
#[error("Can't remove file. Please check privileges. {0:?}")]
CantRemove(std::io::Error),
#[error("Can't remove file. Please check privileges.")]
CantRemove,
#[error("Can't write to file. Invalid path, no filename")]
InvalidPath,
}
@ -141,7 +142,10 @@ pub(crate) async fn remove_file(msg: RemoveFile, config: SharedAppConfig) -> Res
{
Ok(_) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(Error::CantRemove(e)),
Err(e) => {
log::error!("{:?}", e);
Err(Error::CantRemove)
}
}
}
@ -201,7 +205,10 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
);
let mut file = match std::fs::File::create(&path) {
Ok(f) => f,
Err(e) => return Err(Error::CantWrite(e)),
Err(e) => {
log::error!("{:?}", e);
return Err(Error::CantWrite);
}
};
let mut counter = 0;
@ -213,7 +220,10 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
match file.write(&b) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::StorageFull => return Err(Error::NoSpace),
Err(e) => return Err(Error::CantWrite(e)),
Err(e) => {
log::error!("{:?}", e);
return Err(Error::CantWrite);
}
}
}
log::debug!("File {:?} successfully written", unique_name);

View File

@ -0,0 +1,19 @@
[package]
name = "lang_provider"
version = "0.1.0"
edition = "2021"
[dependencies]
model = { path = "../../shared/model" }
config = { path = "../../shared/config" }
actix = { version = "0.13", features = [] }
actix-rt = { version = "2.7", features = [] }
thiserror = { version = "1.0.31" }
log = { version = "0.4", features = [] }
pretty_env_logger = { version = "0.4", features = [] }
fluent = { version = "0.16.0" }
unic-langid = { version = "0.9.0" }

View File

@ -0,0 +1,7 @@
# Cart
shopping-cart-failed = "Failed to load shopping cart. Please try later."
cart-not-available = "Failed to load your shopping cart. Please try later."
cant-modify-item = "At least one item in cart can't be modify. Please try later."
cant-modify-cart = "Can't modify shopping cart. Please try later."
db = "There was an expected error. Please try later."
update-failed = "At least one item in cart can't be modify. Please try later."

View File

@ -0,0 +1,7 @@
# Cart
shopping-cart-failed = "Nastąpił błąd podczas wczytywania koszyka. Proszę spróbuj później."
cart-not-available = "Nastąpił błąd podczas wczytywania twojego koszyka. Proszę spróbuj później."
cant-modify-item = "Przynajmniej jeden przedmiot z twojego koszyka nie mógł być zmieniony. Proszę spróbuj później."
cant-modify-cart = "Nie mogliśmy zapisać zmian w twoim koszyku. Proszę spróbuj później."
db = "Nastąpił nieoczekiwany błąd. Proszę spróbuj później."
update-failed = "Przynajmniej jeden przedmiot z twojego koszyka nie mógł być zmieniony. Proszę spróbuj później."

View File

@ -13,6 +13,8 @@ actix-rt = { version = "2.7", features = [] }
thiserror = { version = "1.0.31" }
serde = { version = "1.0.137", features = ["derive"] }
uuid = { version = "0.8", features = ["serde"] }
chrono = { version = "0.4", features = ["serde"] }

View File

@ -19,7 +19,8 @@ macro_rules! order_async_handler {
};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "order")]
pub enum Error {
#[error("Database actor failed")]
DatabaseInternal,

View File

@ -61,14 +61,17 @@ macro_rules! query_pay {
pub type PayUClient = Arc<Mutex<pay_u::Client>>;
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "pay")]
pub enum Error {
#[error("{0}")]
PayU(#[from] pay_u::Error),
#[error("Failed to create order")]
CreateOrder,
#[error("Failed to create order. Shopping cart is not available")]
UnavailableShoppingCart,
#[error("Can't place order")]
PaymentFailed,
#[error("Order data is invalid. Please check order details")]
InvalidOrder,
}
pub type Result<T> = std::result::Result<T, Error>;
@ -81,7 +84,7 @@ pub struct PaymentManager {
}
impl PaymentManager {
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Result<Self> {
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Self {
let mut client = {
let l = config.lock();
let p = l.payment();
@ -91,12 +94,15 @@ impl PaymentManager {
p.payu_client_merchant_id(),
)
};
client.authorize().await?;
Ok(Self {
client.authorize().await.unwrap_or_else(|e| {
log::error!("{}", e);
std::process::exit(1);
});
Self {
client: Arc::new(Mutex::new(client)),
db,
config,
})
}
}
}
@ -255,7 +261,11 @@ pub(crate) async fn request_payment(
msg.customer_ip,
msg.currency,
format!("Order #{}", db_order.id),
)?
)
.map_err(|e| {
log::error!("{}", e);
Error::InvalidOrder
})?
.with_products(cart_products.into_iter().map(|p| {
pay_u::Product::new(
p.name.to_string(),
@ -270,7 +280,11 @@ pub(crate) async fn request_payment(
.with_notify_url(notify_uri)
.with_continue_url(continue_uri),
)
.await?
.await
.map_err(|e| {
log::error!("{}", e);
Error::PaymentFailed
})?
};
query_db!(

View File

@ -23,7 +23,8 @@ macro_rules! search_async_handler {
};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "search")]
pub enum Error {
#[error("Can't create index")]
CantCreate,

View File

@ -31,3 +31,7 @@ rand_core = { version = "0.6", features = ["std"] }
jwt = { version = "0.16", features = [] }
hmac = { version = "0.12", features = [] }
sha2 = { version = "0.10", features = [] }
tokio = { version = "1.17", features = ["full"] }
futures = { version = "0.3", features = [] }
futures-util = { version = "0.3", features = [] }

View File

@ -106,7 +106,8 @@ macro_rules! query_tm {
pub jti: uuid::Uuid,
}*/
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "token")]
pub enum Error {
#[error("Unable to save new token")]
Save,
@ -248,6 +249,62 @@ pub(crate) async fn create_token(
Ok((token, token_string))
}
pub struct AuthPair {
pub access_token: Token,
pub access_token_string: AccessTokenString,
pub _refresh_token: Token,
pub refresh_token_string: model::RefreshTokenString,
}
#[derive(Message)]
#[rtype(result = "Result<AuthPair>")]
pub struct CreatePair {
pub customer_id: uuid::Uuid,
pub role: Role,
pub id: AccountId,
}
token_async_handler!(CreatePair, create_pair, AuthPair);
pub(crate) async fn create_pair(
msg: CreatePair,
db: Addr<Database>,
config: SharedAppConfig,
) -> Result<AuthPair> {
let (access_token, refresh_token) = tokio::join!(
create_token(
CreateToken {
customer_id: msg.customer_id,
role: msg.role,
subject: msg.id,
audience: Some(model::Audience::Web),
exp: None
},
db.clone(),
config.clone()
),
create_token(
CreateToken {
customer_id: msg.customer_id,
role: msg.role,
subject: msg.id,
audience: Some(model::Audience::Web),
exp: Some((chrono::Utc::now() + chrono::Duration::days(31)).naive_utc())
},
db.clone(),
config.clone()
)
);
let (access_token, access_token_string): (Token, AccessTokenString) = access_token?;
let (refresh_token, refresh_token_string): (Token, AccessTokenString) = refresh_token?;
Ok(AuthPair {
access_token,
access_token_string,
_refresh_token: refresh_token,
refresh_token_string: refresh_token_string.into(),
})
}
#[derive(Message)]
#[rtype(result = "Result<Token>")]
pub struct Validate {

View File

@ -47,20 +47,20 @@ async fn server(opts: ServerOpts) -> Result<()> {
let app_config = config::default_load(&opts);
let db = database_manager::Database::build(app_config.clone())
.await?
.await
.start();
let token_manager = token_manager::TokenManager::new(app_config.clone(), db.clone()).start();
let order_manager = order_manager::OrderManager::new(app_config.clone(), db.clone()).start();
let payment_manager = payment_manager::PaymentManager::build(app_config.clone(), db.clone())
.await
.expect("Failed to start payment manager")
.start();
let search_manager = search_manager::SearchManager::new(app_config.clone()).start();
let fs_manager = fs_manager::FsManager::build(app_config.clone())
.await
.expect("Failed to initialize file system storage");
let cart_manager = cart_manager::CartManager::new(db.clone()).start();
let account_manager = account_manager::AccountManager::new(db.clone()).start();
let account_manager =
account_manager::AccountManager::new(app_config.clone(), db.clone()).start();
let addr = {
let l = app_config.lock();
let w = l.web();
@ -110,7 +110,7 @@ async fn migrate(opts: MigrateOpts) -> Result<()> {
use sqlx::migrate::MigrateError;
let config = config::default_load(&opts);
let db = database_manager::Database::build(config).await?;
let db = database_manager::Database::build(config).await;
let res: std::result::Result<(), MigrateError> =
sqlx::migrate!("../migrations").run(db.pool()).await;
match res {
@ -140,7 +140,7 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
}
let config = config::default_load(&opts);
let db = database_manager::Database::build(config.clone())
.await?
.await
.start();
let pass = match opts.pass_file {
Some(path) => std::fs::read_to_string(path).map_err(Error::PassFile)?,
@ -204,7 +204,7 @@ async fn reindex(opts: ReIndexOpts) -> Result<()> {
let config = config::default_load(&opts);
opts.update_config(&mut *config.lock());
let db = database_manager::Database::build(config.clone())
.await?
.await
.start();
let search = search_manager::SearchManager::new(config).start();
let products: Vec<model::Product> = db

View File

@ -13,7 +13,7 @@ use model::Encrypt;
use token_manager::TokenManager;
use crate::routes;
use crate::routes::{create_auth_pair, AdminError, AuthPair, RequireUser};
use crate::routes::RequireUser;
#[delete("/logout")]
async fn logout(
@ -40,26 +40,31 @@ async fn sign_in(
login: payload.login,
email: payload.email,
},
routes::Error::Admin(AdminError::DatabaseConnection)
routes::Error::CriticalFailure
);
if payload.password.validate(&account.pass_hash).is_err() {
return Err(routes::Error::Unauthorized);
}
let role = account.role;
let AuthPair {
let token_manager::AuthPair {
access_token,
access_token_string,
_refresh_token: _,
refresh_token_string,
} = create_auth_pair(tm, account).await?;
} = tm
.send(token_manager::CreatePair {
customer_id: account.customer_id,
role: account.role,
id: account.id,
})
.await
.map_err(|_| routes::Error::CriticalFailure)??;
Ok(Json(model::api::SessionOutput {
access_token: access_token_string,
refresh_token: refresh_token_string,
exp: access_token.expiration_time,
role,
role: account.role,
}))
}

View File

@ -6,7 +6,6 @@ use database_manager::Database;
use model::api::AccountOrders;
use token_manager::TokenManager;
use crate::routes::admin::Error;
use crate::routes::RequireUser;
use crate::{admin_send_db, routes};

View File

@ -12,7 +12,6 @@ use search_manager::SearchManager;
use serde::Deserialize;
use token_manager::TokenManager;
use crate::routes::admin::Error;
use crate::routes::RequireUser;
use crate::{admin_send_db, routes};

View File

@ -7,7 +7,6 @@ use model::{ProductId, Quantity, QuantityUnit, StockId};
use serde::Deserialize;
use token_manager::TokenManager;
use crate::routes::admin::Error;
use crate::routes::RequireUser;
use crate::{admin_send_db, routes};

View File

@ -1,9 +1,6 @@
mod api_v1;
use actix_web::web::{scope, ServiceConfig};
use actix_web::{get, HttpResponse};
use crate::routes::Result;
#[macro_export]
macro_rules! admin_send_db {
@ -12,17 +9,17 @@ macro_rules! admin_send_db {
Ok(Ok(res)) => res,
Ok(Err(e)) => {
log::error!("{}", e);
return Err(crate::routes::Error::Admin(Error::Database(e)));
return Err(crate::routes::Error::from(e));
}
Err(e) => {
log::error!("{}", e);
return Err(crate::routes::Error::Admin(Error::DatabaseConnection));
return Err(crate::routes::Error::CriticalFailure);
}
}
}};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Can't register new account")]
Register,
@ -36,17 +33,6 @@ pub enum Error {
Database(#[from] database_manager::Error),
}
#[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(landing)
.configure(api_v1::configure),
);
config.service(scope("/admin").configure(api_v1::configure));
}

View File

@ -8,7 +8,7 @@ use actix::Addr;
use actix_session::Session;
use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
use actix_web::web::{Data, ServiceConfig};
use actix_web::web::ServiceConfig;
use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError};
use model::api::Failure;
use token_manager::{query_tm, TokenManager};
@ -32,19 +32,23 @@ impl RequireLogin for Session {
}
}
#[derive(Debug, derive_more::From)]
#[derive(Debug, serde::Serialize, derive_more::From)]
#[serde(rename_all = "kebab-case")]
pub enum Error {
#[from(ignore)]
Unauthorized,
CriticalFailure,
Admin(admin::Error),
Public(public::Error),
}
impl From<V1Error> for Error {
fn from(v1: V1Error) -> Self {
Self::Public(PublicError::ApiV1(v1))
}
Admin(admin::Error),
Account(account_manager::Error),
Cart(cart_manager::Error),
Database(database_manager::Error),
Email(email_manager::Error),
Fs(fs_manager::Error),
Order(order_manager::Error),
Pay(payment_manager::Error),
Search(search_manager::Error),
Token(token_manager::Error),
}
impl From<V1ShoppingCartError> for Error {
@ -56,12 +60,30 @@ impl From<V1ShoppingCartError> for Error {
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let msg = match self {
Error::Unauthorized => String::from("Unauthorized"),
Error::Admin(e) => format!("{e}"),
Error::Public(e) => format!("{e}"),
Error::Unauthorized => serde_json::to_string(&Failure {
errors: vec![String::from("Unauthorized")],
})
.unwrap_or_default(),
Error::Admin(e) => serde_json::to_string(&Failure {
errors: vec![format!("{}", e)],
})
.unwrap_or_default(),
Error::Public(e) => serde_json::to_string(&Failure {
errors: vec![format!("{}", e)],
})
.unwrap_or_default(),
Error::CriticalFailure => String::from("Something went wrong"),
Error::Account(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Cart(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Database(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Email(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Fs(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Order(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Pay(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Search(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Token(_e) => serde_json::to_string(&self).unwrap_or_default(),
};
f.write_str(&serde_json::to_string(&Failure { errors: vec![msg] }).unwrap())
f.write_str(&msg)
}
}
@ -74,6 +96,15 @@ impl ResponseError for Error {
}
Error::Admin(_) => StatusCode::BAD_REQUEST,
Error::Public(_) => StatusCode::BAD_REQUEST,
Error::Account(_) => StatusCode::BAD_REQUEST,
Error::Cart(_) => StatusCode::BAD_REQUEST,
Error::Database(_) => StatusCode::BAD_REQUEST,
Error::Email(_) => StatusCode::BAD_REQUEST,
Error::Fs(_) => StatusCode::BAD_REQUEST,
Error::Order(_) => StatusCode::BAD_REQUEST,
Error::Pay(_) => StatusCode::BAD_REQUEST,
Error::Search(_) => StatusCode::BAD_REQUEST,
Error::Token(_) => StatusCode::BAD_REQUEST,
}
}
}
@ -82,41 +113,9 @@ impl Responder for Error {
type Body = BoxBody;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
match self {
Error::Public(PublicError::DatabaseConnection) | Error::CriticalFailure => {
HttpResponse::InternalServerError()
.content_type("application/json")
.json(Failure {
errors: vec![format!("{}", self)],
})
}
Error::Unauthorized => HttpResponse::Unauthorized()
.content_type("application/json")
.json(Failure {
errors: vec![format!("{}", self)],
}),
Error::Public(PublicError::Database(..)) | Error::Admin(..) => {
HttpResponse::BadRequest()
.content_type("application/json")
.json(Failure {
errors: vec![format!("{}", self)],
})
}
Error::Public(PublicError::ApiV1(V1Error::ShoppingCart(ref e))) => match e {
V1ShoppingCartError::Ensure => HttpResponse::InternalServerError()
.content_type("application/json")
.json(Failure {
errors: vec![format!("{}", self)],
}),
},
Error::Public(PublicError::ApiV1(
V1Error::ModifyItem | V1Error::RemoveItem | V1Error::AddOrder,
)) => HttpResponse::BadRequest()
.content_type("application/json")
.json(Failure {
errors: vec![format!("{}", self)],
}),
}
HttpResponse::build(self.status_code())
.content_type("application/json")
.body(format!("{}", self))
}
}
@ -160,45 +159,3 @@ impl RequireUser for actix_web_httpauth::extractors::bearer::BearerAuth {
}
}
}
pub struct AuthPair {
pub access_token: model::Token,
pub access_token_string: model::AccessTokenString,
pub _refresh_token: model::Token,
pub refresh_token_string: model::RefreshTokenString,
}
pub async fn create_auth_pair(
tm: Data<Addr<TokenManager>>,
account: model::FullAccount,
) -> Result<AuthPair> {
let (access_token, refresh_token) = query_tm!(
multi,
tm,
Error::Public(PublicError::DatabaseConnection),
token_manager::CreateToken {
customer_id: account.customer_id,
role: account.role,
subject: account.id,
audience: Some(model::Audience::Web),
exp: None
},
token_manager::CreateToken {
customer_id: account.customer_id,
role: account.role,
subject: account.id,
audience: Some(model::Audience::Web),
exp: Some((chrono::Utc::now() + chrono::Duration::days(31)).naive_utc())
}
);
let (access_token, access_token_string): (model::Token, model::AccessTokenString) =
access_token?;
let (refresh_token, refresh_token_string): (model::Token, model::AccessTokenString) =
refresh_token?;
Ok(AuthPair {
access_token,
access_token_string,
_refresh_token: refresh_token,
refresh_token_string: refresh_token_string.into(),
})
}

View File

@ -3,13 +3,13 @@ mod unrestricted;
use actix_web::web::{scope, ServiceConfig};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, serde::Serialize, serde::Deserialize, thiserror::Error)]
pub enum ShoppingCartError {
#[error("Shopping cart can't be found or created")]
Ensure,
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, serde::Serialize, serde::Deserialize, thiserror::Error)]
pub enum Error {
#[error("{0}")]
ShoppingCart(ShoppingCartError),

View File

@ -12,7 +12,7 @@ use token_manager::TokenManager;
use crate::routes;
use crate::routes::public::api_v1::{Error as ApiV1Error, ShoppingCartError};
use crate::routes::public::Error as PublicError;
use crate::routes::{create_auth_pair, AuthPair, RequireUser, Result};
use crate::routes::{RequireUser, Result};
/// This requires [model::AccessTokenString] to be set as bearer
#[post("/token/verify")]
@ -42,20 +42,25 @@ async fn refresh_token(
routes::Error::Unauthorized
);
let role = account.role;
let AuthPair {
let token_manager::AuthPair {
access_token,
access_token_string,
_refresh_token: _,
refresh_token_string,
} = create_auth_pair(tm, account).await?;
} = tm
.send(token_manager::CreatePair {
customer_id: account.customer_id,
role: account.role,
id: account.id,
})
.await
.map_err(|_| routes::Error::CriticalFailure)??;
Ok(Json(model::api::SessionOutput {
access_token: access_token_string,
refresh_token: refresh_token_string,
exp: access_token.expiration_time,
role,
role: account.role,
}))
}

View File

@ -10,7 +10,7 @@ use token_manager::TokenManager;
use crate::public_send_db;
use crate::routes::public::Error as PublicError;
use crate::routes::{self, create_auth_pair, AuthPair};
use crate::routes::{self};
#[get("/search")]
async fn search(
@ -134,11 +134,6 @@ pub async fn create_account(
config: Data<SharedAppConfig>,
tm: Data<Addr<TokenManager>>,
) -> routes::Result<Json<model::api::SessionOutput>> {
if payload.password != payload.password_confirmation {
return Err(routes::Error::Admin(
routes::admin::Error::DifferentPasswords,
));
}
let hash = {
match payload.password.encrypt(&config.lock().web().pass_salt()) {
Ok(hash) => hash,
@ -149,32 +144,35 @@ pub async fn create_account(
}
};
let account: model::FullAccount = query_db!(
db,
database_manager::CreateAccount {
let account: model::FullAccount = db
.send(database_manager::CreateAccount {
email: payload.email,
login: payload.login,
pass_hash: model::PassHash::from(hash),
pass_hash: model::PassHash::new(hash),
role: model::Role::User,
},
passthrough routes::Error::Public,
routes::Error::CriticalFailure
);
})
.await
.map_err(|_| routes::Error::CriticalFailure)??;
let role = account.role;
let AuthPair {
let token_manager::AuthPair {
access_token,
access_token_string,
_refresh_token: _,
refresh_token_string,
} = create_auth_pair(tm, account).await?;
} = tm
.send(token_manager::CreatePair {
customer_id: account.customer_id,
role: account.role,
id: account.id,
})
.await
.map_err(|_| routes::Error::CriticalFailure)??;
Ok(Json(model::api::SessionOutput {
access_token: access_token_string,
refresh_token: refresh_token_string,
exp: access_token.expiration_time,
role,
role: account.role,
}))
}
@ -198,20 +196,25 @@ async fn sign_in(
return Err(routes::Error::Unauthorized);
}
let role = account.role;
let AuthPair {
let token_manager::AuthPair {
access_token,
access_token_string,
_refresh_token: _,
refresh_token_string,
} = create_auth_pair(tm, account).await?;
} = tm
.send(token_manager::CreatePair {
customer_id: account.customer_id,
role: account.role,
id: account.id,
})
.await
.map_err(|_| routes::Error::CriticalFailure)??;
Ok(Json(model::api::SessionOutput {
access_token: access_token_string,
refresh_token: refresh_token_string,
exp: access_token.expiration_time,
role,
role: account.role,
}))
}

View File

@ -44,7 +44,7 @@ macro_rules! public_send_db {
}};
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("{0}")]
ApiV1(#[from] api_v1::Error),
@ -56,7 +56,7 @@ pub enum Error {
#[get("/")]
async fn landing() -> HttpResponse {
HttpResponse::NotImplemented().body("")
HttpResponse::NotImplemented().finish()
}
#[get("/config")]
@ -90,7 +90,7 @@ macro_rules! serve_svg {
($name: expr) => {
HttpResponse::Ok()
.append_header(("Content-Type", "image/svg+xml"))
.body(include_bytes!(concat!("../../assets/svg/", $name, ".svg")).to_vec())
.body(include_bytes!(concat!("../../../assets/svg/", $name, ".svg")).to_vec())
};
}

View File

@ -417,7 +417,6 @@ pub struct CreateAccountInput {
pub email: Email,
pub login: Login,
pub password: Password,
pub password_confirmation: PasswordConfirmation,
}
#[derive(Serialize, Deserialize, Debug)]