Migrate admin endpoint to JWT
This commit is contained in:
parent
d5e675b6dc
commit
f0d01a9735
@ -13,3 +13,7 @@ members = [
|
||||
"actors/fs_manager",
|
||||
"db-seed"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
|
@ -116,6 +116,8 @@ pub enum Error {
|
||||
Validate,
|
||||
#[error("Unable to validate token. Can't connect to database")]
|
||||
ValidateInternal,
|
||||
#[error("Token does not exists or some fields are incorrect")]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -247,18 +249,18 @@ pub(crate) async fn create_token(
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(Token, bool)>")]
|
||||
#[rtype(result = "Result<Token>")]
|
||||
pub struct Validate {
|
||||
pub token: AccessTokenString,
|
||||
}
|
||||
|
||||
token_async_handler!(Validate, validate, (Token, bool));
|
||||
token_async_handler!(Validate, validate, Token);
|
||||
|
||||
pub(crate) async fn validate(
|
||||
msg: Validate,
|
||||
db: Addr<Database>,
|
||||
config: SharedAppConfig,
|
||||
) -> Result<(Token, bool)> {
|
||||
) -> Result<Token> {
|
||||
use jwt::VerifyWithKey;
|
||||
|
||||
log::info!("Validating token {:?}", msg.token);
|
||||
@ -291,32 +293,32 @@ pub(crate) async fn validate(
|
||||
}
|
||||
|
||||
if !validate_pair(&claims, "cti", token.customer_id, validate_uuid) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "arl", token.role, |left, right| right == left) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "iss", &token.issuer, |left, right| right == left) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "sub", token.subject, validate_num) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "aud", token.audience, |left, right| right == left) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "exp", &token.expiration_time, validate_time) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "nbt", &token.not_before_time, validate_time) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
if !validate_pair(&claims, "iat", &token.issued_at_time, validate_time) {
|
||||
return Ok((token, false));
|
||||
return Err(Error::Invalid);
|
||||
}
|
||||
|
||||
log::info!("JWT token valid");
|
||||
Ok((token, true))
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn build_key(secret: String) -> Result<Hmac<Sha256>> {
|
||||
|
@ -1,15 +1,9 @@
|
||||
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 config::SharedAppConfig;
|
||||
use database_manager::{query_db, Database};
|
||||
use model::{Encrypt, PassHash};
|
||||
use actix_web::web::{scope, ServiceConfig};
|
||||
use actix_web::{get, HttpResponse};
|
||||
|
||||
use crate::routes;
|
||||
use crate::routes::{RequireLogin, Result};
|
||||
use crate::routes::Result;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! admin_send_db {
|
||||
@ -42,86 +36,6 @@ pub enum Error {
|
||||
Database(#[from] database_manager::Error),
|
||||
}
|
||||
|
||||
#[delete("logout")]
|
||||
async fn logout(session: Session) -> Result<Json<model::api::admin::LogoutResponse>> {
|
||||
session.require_admin()?;
|
||||
session.clear();
|
||||
|
||||
Ok(Json(model::api::admin::LogoutResponse {}))
|
||||
}
|
||||
|
||||
#[post("/sign-in")]
|
||||
async fn sign_in(
|
||||
session: Session,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<model::api::admin::SignInInput>,
|
||||
) -> Result<HttpResponse> {
|
||||
log::debug!("{:?}", payload);
|
||||
let db = db.into_inner();
|
||||
let user: model::FullAccount = query_db!(
|
||||
db,
|
||||
database_manager::AccountByIdentity {
|
||||
email: payload.email,
|
||||
login: payload.login,
|
||||
},
|
||||
routes::Error::Unauthorized
|
||||
);
|
||||
if let Err(e) = payload.password.validate(&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)))
|
||||
}
|
||||
}
|
||||
|
||||
// login_required
|
||||
#[post("/register")]
|
||||
async fn register(
|
||||
session: Session,
|
||||
Json(input): Json<model::api::admin::RegisterInput>,
|
||||
db: Data<Addr<Database>>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> Result<HttpResponse> {
|
||||
let mut response = model::api::admin::RegisterResponse::default();
|
||||
session.require_admin()?;
|
||||
|
||||
if input.password != input.password_confirmation {
|
||||
response
|
||||
.errors
|
||||
.push(model::api::admin::RegisterError::PasswordDiffer);
|
||||
}
|
||||
|
||||
let hash = match input.password.encrypt(&config.lock().web().pass_salt()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(routes::Error::Admin(Error::HashPass));
|
||||
}
|
||||
};
|
||||
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::CreateAccount {
|
||||
email: input.email,
|
||||
login: input.login,
|
||||
pass_hash: PassHash::from(hash),
|
||||
role: input.role,
|
||||
},
|
||||
super::Error::Admin(Error::Register),
|
||||
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()
|
||||
@ -132,9 +46,6 @@ async fn landing() -> Result<HttpResponse> {
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.service(
|
||||
scope("/admin")
|
||||
.service(sign_in)
|
||||
.service(logout)
|
||||
.service(register)
|
||||
.service(landing)
|
||||
.configure(api_v1::configure),
|
||||
);
|
||||
|
@ -4,7 +4,64 @@ mod products;
|
||||
mod stocks;
|
||||
mod uploads;
|
||||
|
||||
use actix_web::web::{scope, ServiceConfig};
|
||||
use actix::Addr;
|
||||
use actix_web::web::{scope, Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, post};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use database_manager::{query_db, Database};
|
||||
use model::Encrypt;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes;
|
||||
use crate::routes::{create_auth_pair, AdminError, AuthPair, RequireUser};
|
||||
|
||||
#[delete("/logout")]
|
||||
async fn logout(
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
) -> routes::Result<Json<model::api::admin::LogoutResponse>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
todo!("Destroy token")
|
||||
// Ok(Json(model::api::admin::LogoutResponse {}))
|
||||
}
|
||||
|
||||
#[post("/sign-in")]
|
||||
async fn sign_in(
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<model::api::admin::SignInInput>,
|
||||
) -> routes::Result<Json<model::api::SessionOutput>> {
|
||||
let db = db.into_inner();
|
||||
|
||||
let account: model::FullAccount = query_db!(
|
||||
db,
|
||||
database_manager::AccountByIdentity {
|
||||
login: payload.login,
|
||||
email: payload.email,
|
||||
},
|
||||
routes::Error::Admin(AdminError::DatabaseConnection)
|
||||
);
|
||||
if payload.password.validate(&account.pass_hash).is_err() {
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
|
||||
let role = account.role;
|
||||
|
||||
let AuthPair {
|
||||
access_token,
|
||||
access_token_string,
|
||||
_refresh_token: _,
|
||||
refresh_token_string,
|
||||
} = create_auth_pair(tm, account).await?;
|
||||
|
||||
Ok(Json(model::api::SessionOutput {
|
||||
access_token: access_token_string,
|
||||
refresh_token: refresh_token_string,
|
||||
exp: access_token.expiration_time,
|
||||
role,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.service(
|
||||
@ -13,6 +70,8 @@ pub fn configure(config: &mut ServiceConfig) {
|
||||
.configure(stocks::configure)
|
||||
.configure(accounts::configure)
|
||||
.configure(orders::configure)
|
||||
.configure(uploads::configure),
|
||||
.configure(uploads::configure)
|
||||
.service(sign_in)
|
||||
.service(logout),
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{get, patch, post, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use config::SharedAppConfig;
|
||||
use database_manager::Database;
|
||||
use model::{AccountId, AccountState, Encrypt, PasswordConfirmation};
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::routes::RequireUser;
|
||||
use crate::{admin_send_db, routes, Email, Login, PassHash, Password, Role};
|
||||
|
||||
#[get("/accounts")]
|
||||
pub async fn accounts(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
pub async fn accounts(
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let accounts = admin_send_db!(db, database_manager::AllAccounts);
|
||||
Ok(HttpResponse::Ok().json(accounts))
|
||||
}
|
||||
@ -30,12 +36,13 @@ pub struct UpdateAccountInput {
|
||||
|
||||
#[patch("/account")]
|
||||
pub async fn update_account(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<UpdateAccountInput>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let hash = match (payload.password, payload.password_confirmation) {
|
||||
(None, None) => None,
|
||||
@ -52,7 +59,7 @@ pub async fn update_account(
|
||||
return Err(routes::Error::Admin(routes::admin::Error::HashPass));
|
||||
}
|
||||
};
|
||||
Some(PassHash::from(hash))
|
||||
Some(PassHash::new(hash))
|
||||
}
|
||||
_ => {
|
||||
return Err(routes::Error::Admin(
|
||||
@ -75,37 +82,28 @@ pub async fn update_account(
|
||||
Ok(HttpResponse::Ok().json(account))
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct CreateAccountInput {
|
||||
pub email: Email,
|
||||
pub login: Login,
|
||||
pub password: Password,
|
||||
pub password_confirmation: PasswordConfirmation,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[post("/account")]
|
||||
pub async fn create_account(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<CreateAccountInput>,
|
||||
Json(payload): Json<model::api::admin::RegisterInput>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
) -> routes::Result<Json<model::api::admin::RegisterResponse>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
if payload.password != payload.password_confirmation {
|
||||
return Err(routes::Error::Admin(
|
||||
routes::admin::Error::DifferentPasswords,
|
||||
));
|
||||
return Err(routes::Error::Admin(Error::DifferentPasswords));
|
||||
}
|
||||
let hash = match payload.password.encrypt(&config.lock().web().pass_salt()) {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(routes::Error::Admin(routes::admin::Error::HashPass));
|
||||
return Err(routes::Error::Admin(Error::HashPass));
|
||||
}
|
||||
};
|
||||
|
||||
let account = admin_send_db!(
|
||||
let account: model::FullAccount = admin_send_db!(
|
||||
db,
|
||||
database_manager::CreateAccount {
|
||||
email: payload.email,
|
||||
@ -114,7 +112,10 @@ pub async fn create_account(
|
||||
role: payload.role,
|
||||
}
|
||||
);
|
||||
Ok(HttpResponse::Ok().json(account))
|
||||
Ok(Json(model::api::admin::RegisterResponse {
|
||||
errors: vec![],
|
||||
account: Some(account.into()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -1,17 +1,22 @@
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::get;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use database_manager::Database;
|
||||
use model::api::AccountOrders;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::routes::RequireUser;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
#[get("/orders")]
|
||||
async fn orders(session: Session, db: Data<Addr<Database>>) -> routes::Result<Json<AccountOrders>> {
|
||||
session.require_admin()?;
|
||||
async fn orders(
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
) -> routes::Result<Json<AccountOrders>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let orders: Vec<model::AccountOrder> = admin_send_db!(&db, database_manager::AllAccountOrders);
|
||||
let items: Vec<model::OrderItem> = admin_send_db!(db, database_manager::AllOrderItems);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use config::SharedAppConfig;
|
||||
use database_manager::Database;
|
||||
use model::{
|
||||
@ -10,18 +10,20 @@ use model::{
|
||||
};
|
||||
use search_manager::SearchManager;
|
||||
use serde::Deserialize;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::routes::RequireUser;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
#[get("/products")]
|
||||
async fn products(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> routes::Result<Json<api::Products>> {
|
||||
session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let public_path = {
|
||||
let l = config.lock();
|
||||
@ -55,11 +57,12 @@ pub struct UpdateProduct {
|
||||
|
||||
#[patch("/product")]
|
||||
async fn update_product(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<UpdateProduct>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let product = admin_send_db!(
|
||||
db,
|
||||
@ -89,12 +92,13 @@ pub struct CreateProduct {
|
||||
|
||||
#[post("/product")]
|
||||
async fn create_product(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
search: Data<Addr<SearchManager>>,
|
||||
Json(payload): Json<CreateProduct>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let product: model::Product = admin_send_db!(
|
||||
db.clone(),
|
||||
@ -138,11 +142,12 @@ pub struct DeleteProduct {
|
||||
|
||||
#[delete("/product")]
|
||||
async fn delete_product(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<DeleteProduct>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
let _ = session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let product = admin_send_db!(
|
||||
db,
|
||||
|
@ -1,21 +1,26 @@
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use database_manager::Database;
|
||||
use model::{ProductId, Quantity, QuantityUnit, StockId};
|
||||
use serde::Deserialize;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::routes::RequireUser;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
#[get("/stocks")]
|
||||
async fn stocks(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
async fn stocks(
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
) -> routes::Result<Json<Vec<model::Stock>>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let stocks = admin_send_db!(db, database_manager::AllStocks);
|
||||
Ok(HttpResponse::Created().json(stocks))
|
||||
Ok(Json(stocks))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -28,11 +33,12 @@ pub struct UpdateStock {
|
||||
|
||||
#[patch("/stock")]
|
||||
async fn update_stock(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<UpdateStock>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
) -> routes::Result<Json<model::Stock>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let stock = admin_send_db!(
|
||||
db,
|
||||
@ -43,7 +49,7 @@ async fn update_stock(
|
||||
quantity_unit: payload.quantity_unit
|
||||
}
|
||||
);
|
||||
Ok(HttpResponse::Created().json(stock))
|
||||
Ok(Json(stock))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -55,11 +61,12 @@ pub struct CreateStock {
|
||||
|
||||
#[post("/stock")]
|
||||
async fn create_stock(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<CreateStock>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let stock = admin_send_db!(
|
||||
db,
|
||||
@ -79,11 +86,12 @@ pub struct DeleteStock {
|
||||
|
||||
#[delete("/stock")]
|
||||
async fn delete_stock(
|
||||
session: Session,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<DeleteStock>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
) -> routes::Result<Json<Option<model::Stock>>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let stock = admin_send_db!(
|
||||
db,
|
||||
@ -91,7 +99,7 @@ async fn delete_stock(
|
||||
stock_id: payload.id
|
||||
}
|
||||
);
|
||||
Ok(HttpResponse::Created().json(stock))
|
||||
Ok(Json(stock))
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -2,9 +2,13 @@ use actix::Addr;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{post, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use database_manager::{query_db, Database};
|
||||
use fs_manager::FsManager;
|
||||
use futures_util::StreamExt;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::RequireUser;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UploadError {
|
||||
@ -21,7 +25,13 @@ async fn upload_product_image(
|
||||
mut payload: Multipart,
|
||||
fs: Data<Addr<FsManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
) -> HttpResponse {
|
||||
if credentials.require_admin(tm.into_inner()).await.is_err() {
|
||||
return HttpResponse::Unauthorized().finish();
|
||||
}
|
||||
|
||||
let mut name = None;
|
||||
while let Some(Ok(mut field)) = payload.next().await {
|
||||
let field_name = field.name();
|
||||
@ -106,7 +116,7 @@ async fn upload_product_image(
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
HttpResponse::NotImplemented().finish()
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -8,26 +8,24 @@ use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
use model::api::Failure;
|
||||
use model::{AccessTokenString, RecordId, Token};
|
||||
use token_manager::{query_tm, TokenManager};
|
||||
|
||||
pub use self::admin::Error as AdminError;
|
||||
pub use self::public::{Error as PublicError, V1Error, V1ShoppingCartError};
|
||||
use crate::routes;
|
||||
|
||||
pub trait RequireLogin {
|
||||
fn require_admin(&self) -> Result<RecordId>;
|
||||
fn require_admin(&self) -> Result<model::RecordId>;
|
||||
}
|
||||
|
||||
impl RequireLogin for Session {
|
||||
fn require_admin(&self) -> Result<RecordId> {
|
||||
fn require_admin(&self) -> Result<model::RecordId> {
|
||||
match self.get("admin_id") {
|
||||
Ok(Some(id)) => Ok(id),
|
||||
_ => {
|
||||
log::debug!("User is not logged as admin");
|
||||
log::debug!("User is not logged as an admin");
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
@ -39,8 +37,8 @@ pub enum Error {
|
||||
#[from(ignore)]
|
||||
Unauthorized,
|
||||
CriticalFailure,
|
||||
Admin(routes::admin::Error),
|
||||
Public(routes::public::Error),
|
||||
Admin(admin::Error),
|
||||
Public(public::Error),
|
||||
}
|
||||
|
||||
impl From<V1Error> for Error {
|
||||
@ -132,18 +130,75 @@ pub fn configure(config: &mut ServiceConfig) {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait RequireUser {
|
||||
async fn require_user(&self, tm: Arc<Addr<TokenManager>>) -> Result<(Token, bool)>;
|
||||
async fn require_user(&self, tm: Arc<Addr<TokenManager>>) -> Result<model::Token>;
|
||||
async fn require_admin(&self, tm: Arc<Addr<TokenManager>>) -> Result<model::Token>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequireUser for actix_web_httpauth::extractors::bearer::BearerAuth {
|
||||
async fn require_user(&self, tm: Arc<Addr<TokenManager>>) -> Result<(Token, bool)> {
|
||||
async fn require_user(&self, tm: Arc<Addr<TokenManager>>) -> Result<model::Token> {
|
||||
Ok(query_tm!(
|
||||
tm,
|
||||
token_manager::Validate {
|
||||
token: AccessTokenString::new(self.token()),
|
||||
token: model::AccessTokenString::new(self.token()),
|
||||
},
|
||||
Error::Unauthorized
|
||||
))
|
||||
}
|
||||
async fn require_admin(&self, tm: Arc<Addr<TokenManager>>) -> Result<model::Token> {
|
||||
let token: model::Token = query_tm!(
|
||||
tm,
|
||||
token_manager::Validate {
|
||||
token: model::AccessTokenString::new(self.token()),
|
||||
},
|
||||
Error::Unauthorized
|
||||
);
|
||||
if token.role == model::Role::Admin {
|
||||
Err(Error::Unauthorized)
|
||||
} else {
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
@ -8,10 +8,9 @@ use model::api;
|
||||
use payment_manager::{query_pay, PaymentManager};
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::public::api_v1::unrestricted::{create_auth_pair, AuthPair};
|
||||
use crate::routes::public::api_v1::{Error as ApiV1Error, ShoppingCartError};
|
||||
use crate::routes::public::Error as PublicError;
|
||||
use crate::routes::{RequireUser, Result};
|
||||
use crate::routes::{create_auth_pair, AuthPair, RequireUser, Result};
|
||||
use crate::{public_send_db, routes};
|
||||
|
||||
/// This requires [model::AccessTokenString] to be set as bearer
|
||||
@ -20,7 +19,7 @@ async fn verify_token(
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
credentials: BearerAuth,
|
||||
) -> routes::Result<String> {
|
||||
let _token = credentials.require_user(tm.into_inner()).await?.0;
|
||||
let _token = credentials.require_user(tm.into_inner()).await?;
|
||||
Ok("".into())
|
||||
}
|
||||
|
||||
@ -34,7 +33,6 @@ async fn refresh_token(
|
||||
let account_id: model::AccountId = credentials
|
||||
.require_user(tm.clone().into_inner())
|
||||
.await?
|
||||
.0
|
||||
.subject
|
||||
.into();
|
||||
let account: model::FullAccount = query_db!(
|
||||
@ -43,6 +41,8 @@ async fn refresh_token(
|
||||
routes::Error::Unauthorized
|
||||
);
|
||||
|
||||
let role = account.role;
|
||||
|
||||
let AuthPair {
|
||||
access_token,
|
||||
access_token_string,
|
||||
@ -50,10 +50,11 @@ async fn refresh_token(
|
||||
refresh_token_string,
|
||||
} = create_auth_pair(tm, account).await?;
|
||||
|
||||
Ok(Json(api::SessionOutput {
|
||||
Ok(Json(model::api::SessionOutput {
|
||||
access_token: access_token_string,
|
||||
refresh_token: refresh_token_string,
|
||||
exp: access_token.expiration_time,
|
||||
role,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -63,11 +64,11 @@ async fn shopping_cart(
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
credentials: BearerAuth,
|
||||
) -> Result<Json<api::ShoppingCart>> {
|
||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
||||
let token = credentials.require_user(tm.into_inner()).await?;
|
||||
let cart: model::ShoppingCart = query_db!(
|
||||
db,
|
||||
database_manager::EnsureActiveShoppingCart {
|
||||
buyer_id: token.subject.into(),
|
||||
buyer_id: token.account_id(),
|
||||
},
|
||||
routes::Error::Public(PublicError::ApiV1(ApiV1Error::ShoppingCart(
|
||||
ShoppingCartError::Ensure
|
||||
@ -94,12 +95,12 @@ async fn create_cart_item(
|
||||
credentials: BearerAuth,
|
||||
Json(payload): Json<api::CreateItemInput>,
|
||||
) -> Result<Json<api::CreateItemOutput>> {
|
||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
||||
let token = credentials.require_user(tm.into_inner()).await?;
|
||||
|
||||
let item: model::ShoppingCartItem = query_cart!(
|
||||
cart,
|
||||
cart_manager::AddItem {
|
||||
buyer_id: token.subject.into(),
|
||||
buyer_id: token.account_id(),
|
||||
product_id: payload.product_id,
|
||||
quantity: payload.quantity,
|
||||
quantity_unit: payload.quantity_unit,
|
||||
@ -121,12 +122,12 @@ async fn delete_cart_item(
|
||||
credentials: BearerAuth,
|
||||
Json(payload): Json<api::DeleteItemInput>,
|
||||
) -> Result<HttpResponse> {
|
||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
||||
let token = credentials.require_user(tm.into_inner()).await?;
|
||||
|
||||
let sc: model::ShoppingCart = query_db!(
|
||||
db,
|
||||
database_manager::EnsureActiveShoppingCart {
|
||||
buyer_id: token.subject.into(),
|
||||
buyer_id: token.account_id(),
|
||||
},
|
||||
routes::Error::Public(super::Error::RemoveItem.into()),
|
||||
routes::Error::Public(PublicError::DatabaseConnection)
|
||||
@ -161,9 +162,7 @@ pub(crate) async fn me(
|
||||
let account_id: model::AccountId = credentials
|
||||
.require_user(tm.into_inner())
|
||||
.await?
|
||||
.0
|
||||
.subject
|
||||
.into();
|
||||
.account_id();
|
||||
let account: model::FullAccount =
|
||||
public_send_db!(owned, db, database_manager::FindAccount { account_id });
|
||||
Ok(Json(account.into()))
|
||||
@ -177,7 +176,10 @@ pub(crate) async fn create_order(
|
||||
credentials: BearerAuth,
|
||||
payment: Data<Addr<PaymentManager>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
let subject = credentials.require_user(tm.into_inner()).await?.0.subject;
|
||||
let account_id = credentials
|
||||
.require_user(tm.into_inner())
|
||||
.await?
|
||||
.account_id();
|
||||
|
||||
let api::CreateOrderInput {
|
||||
email,
|
||||
@ -205,7 +207,7 @@ pub(crate) async fn create_order(
|
||||
language,
|
||||
},
|
||||
customer_ip: ip.to_string(),
|
||||
buyer_id: subject.into(),
|
||||
buyer_id: account_id,
|
||||
charge_client
|
||||
},
|
||||
routes::Error::Public(PublicError::DatabaseConnection)
|
||||
|
@ -3,14 +3,14 @@ use actix_web::web::{Data, Json, Path, Query, ServiceConfig};
|
||||
use actix_web::{get, post, HttpResponse};
|
||||
use config::SharedAppConfig;
|
||||
use database_manager::{query_db, Database};
|
||||
use model::{api, Encrypt};
|
||||
use model::Encrypt;
|
||||
use payment_manager::{PaymentManager, PaymentNotification};
|
||||
use search_manager::SearchManager;
|
||||
use token_manager::{query_tm, TokenManager};
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::public_send_db;
|
||||
use crate::routes::public::Error as PublicError;
|
||||
use crate::routes::{self, Result};
|
||||
use crate::routes::{self, create_auth_pair, AuthPair};
|
||||
|
||||
#[get("/search")]
|
||||
async fn search(
|
||||
@ -57,7 +57,7 @@ async fn search(
|
||||
async fn products(
|
||||
db: Data<Addr<Database>>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> Result<Json<api::Products>> {
|
||||
) -> routes::Result<Json<model::api::Products>> {
|
||||
let db = db.into_inner();
|
||||
let public_path = {
|
||||
let l = config.lock();
|
||||
@ -86,7 +86,7 @@ async fn product(
|
||||
path: Path<model::RecordId>,
|
||||
db: Data<Addr<Database>>,
|
||||
config: Data<SharedAppConfig>,
|
||||
) -> Result<Json<api::Product>> {
|
||||
) -> routes::Result<Json<model::api::Product>> {
|
||||
let product_id: model::ProductId = path.into_inner().into();
|
||||
let db = db.into_inner();
|
||||
let public_path = {
|
||||
@ -122,14 +122,15 @@ async fn product(
|
||||
}
|
||||
|
||||
#[get("/stocks")]
|
||||
async fn stocks(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
||||
public_send_db!(db.into_inner(), database_manager::AllStocks)
|
||||
async fn stocks(db: Data<Addr<Database>>) -> routes::Result<Json<Vec<model::Stock>>> {
|
||||
let stocks = public_send_db!(owned, db.into_inner(), database_manager::AllStocks);
|
||||
Ok(Json(stocks))
|
||||
}
|
||||
|
||||
#[post("/register")]
|
||||
pub async fn create_account(
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<api::CreateAccountInput>,
|
||||
Json(payload): Json<model::api::CreateAccountInput>,
|
||||
config: Data<SharedAppConfig>,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
) -> routes::Result<Json<model::api::SessionOutput>> {
|
||||
@ -160,6 +161,8 @@ pub async fn create_account(
|
||||
routes::Error::CriticalFailure
|
||||
);
|
||||
|
||||
let role = account.role;
|
||||
|
||||
let AuthPair {
|
||||
access_token,
|
||||
access_token_string,
|
||||
@ -167,61 +170,20 @@ pub async fn create_account(
|
||||
refresh_token_string,
|
||||
} = create_auth_pair(tm, account).await?;
|
||||
|
||||
Ok(Json(api::SessionOutput {
|
||||
Ok(Json(model::api::SessionOutput {
|
||||
access_token: access_token_string,
|
||||
refresh_token: refresh_token_string,
|
||||
exp: access_token.expiration_time,
|
||||
role,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) 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(crate) async fn create_auth_pair(
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
account: model::FullAccount,
|
||||
) -> routes::Result<AuthPair> {
|
||||
let (access_token, refresh_token) = query_tm!(
|
||||
multi,
|
||||
tm,
|
||||
routes::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(),
|
||||
})
|
||||
}
|
||||
|
||||
#[post("/sign-in")]
|
||||
async fn sign_in(
|
||||
Json(payload): Json<api::SignInInput>,
|
||||
Json(payload): Json<model::api::SignInInput>,
|
||||
db: Data<Addr<Database>>,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
) -> Result<Json<api::SessionOutput>> {
|
||||
) -> routes::Result<Json<model::api::SessionOutput>> {
|
||||
let db = db.into_inner();
|
||||
|
||||
let account: model::FullAccount = query_db!(
|
||||
@ -236,6 +198,8 @@ async fn sign_in(
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
|
||||
let role = account.role;
|
||||
|
||||
let AuthPair {
|
||||
access_token,
|
||||
access_token_string,
|
||||
@ -243,10 +207,11 @@ async fn sign_in(
|
||||
refresh_token_string,
|
||||
} = create_auth_pair(tm, account).await?;
|
||||
|
||||
Ok(Json(api::SessionOutput {
|
||||
Ok(Json(model::api::SessionOutput {
|
||||
access_token: access_token_string,
|
||||
refresh_token: refresh_token_string,
|
||||
exp: access_token.expiration_time,
|
||||
role,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -316,6 +316,7 @@ pub struct SessionOutput {
|
||||
pub access_token: AccessTokenString,
|
||||
pub refresh_token: RefreshTokenString,
|
||||
pub exp: NaiveDateTime,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -391,7 +392,6 @@ pub mod admin {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct RegisterResponse {
|
||||
pub success: bool,
|
||||
pub errors: Vec<RegisterError>,
|
||||
pub account: Option<Account>,
|
||||
}
|
||||
|
@ -664,6 +664,12 @@ impl PasswordConfirmation {
|
||||
#[serde(transparent)]
|
||||
pub struct PassHash(String);
|
||||
|
||||
impl PassHash {
|
||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<PasswordConfirmation> for Password {
|
||||
fn eq(&self, other: &PasswordConfirmation) -> bool {
|
||||
self.0 == other.0
|
||||
@ -977,6 +983,12 @@ pub struct Token {
|
||||
pub jwt_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn account_id(&self) -> AccountId {
|
||||
AccountId(self.subject)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
|
@ -31,6 +31,3 @@ rusty-money = { version = "0.4.1", features = ["iso"] }
|
||||
|
||||
thiserror = { version = "1.0.31" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
|
@ -3,6 +3,7 @@ use std::ops::FromResidual;
|
||||
|
||||
use seed::fetch::{FetchError, Request};
|
||||
|
||||
pub mod admin;
|
||||
pub mod public;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -12,6 +13,12 @@ pub enum NetRes<S> {
|
||||
Http(FetchError),
|
||||
}
|
||||
|
||||
impl<S> From<FetchError> for NetRes<S> {
|
||||
fn from(e: FetchError) -> Self {
|
||||
Self::Http(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromResidual<Result<Infallible, NetRes<S>>> for NetRes<S> {
|
||||
fn from_residual(residual: Result<Infallible, NetRes<S>>) -> Self {
|
||||
match residual {
|
||||
|
35
web/src/api/admin.rs
Normal file
35
web/src/api/admin.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use seed::fetch::{Header, Method, Request};
|
||||
|
||||
use crate::api::{perform, NetRes};
|
||||
|
||||
pub async fn sign_in(identity: String, password: model::Password) -> NetRes<model::Account> {
|
||||
use model::api::admin::SignInInput;
|
||||
|
||||
let input = if identity.contains('@') {
|
||||
SignInInput {
|
||||
login: None,
|
||||
email: Some(model::Email::new(identity)),
|
||||
password,
|
||||
}
|
||||
} else {
|
||||
SignInInput {
|
||||
login: Some(model::Login::new(identity)),
|
||||
email: None,
|
||||
password,
|
||||
}
|
||||
};
|
||||
perform(
|
||||
Request::new("/api/v1/sign-in")
|
||||
.method(Method::Post)
|
||||
.json(&input)
|
||||
.map_err(NetRes::Http)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// This request validates if session is still active
|
||||
/// It should be run from time to time just to check if user should be
|
||||
/// redirected to sign-in page
|
||||
pub async fn check_session() -> NetRes<String> {
|
||||
perform(Request::new("/api/v1/check").method(Method::Get)).await
|
||||
}
|
Loading…
Reference in New Issue
Block a user