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