Add auth to delete endpoints
This commit is contained in:
parent
79b45551fd
commit
68b04b4f4c
@ -6,7 +6,7 @@ pub fn migration() -> String {
|
||||
m.create_table("owners", |t| {
|
||||
t.add_column("id", types::primary().indexed(true));
|
||||
t.add_column("name", types::varchar(255));
|
||||
t.add_column("api_key", types::uuid());
|
||||
t.add_column("api_key", types::uuid().indexed(true));
|
||||
t.add_column("ip_address", types::custom("inet").nullable(true));
|
||||
t.add_column("mod_version", types::varchar(25));
|
||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||
@ -37,6 +37,7 @@ pub fn migration() -> String {
|
||||
m.create_table("merchandise", |t| {
|
||||
t.add_column("id", types::primary().indexed(true));
|
||||
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
|
||||
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
||||
t.add_column("mod_name", types::varchar(255));
|
||||
t.add_column("local_form_id", types::integer());
|
||||
t.add_column("quantity", types::integer());
|
||||
@ -51,6 +52,7 @@ pub fn migration() -> String {
|
||||
m.create_table("transactions", |t| {
|
||||
t.add_column("id", types::primary().indexed(true));
|
||||
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
|
||||
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
||||
t.add_column("merchandise_id", types::foreign("merchandise", "id"));
|
||||
t.add_column("customer_name", types::varchar(255));
|
||||
t.add_column("is_customer_npc", types::boolean());
|
||||
@ -63,6 +65,7 @@ pub fn migration() -> String {
|
||||
m.create_table("interior_ref_lists", |t| {
|
||||
t.add_column("id", types::primary().indexed(true));
|
||||
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
|
||||
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
|
||||
t.add_column("ref_list", types::custom("jsonb"));
|
||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||
t.add_column("updated_at", types::custom("timestamp(3)"));
|
||||
|
@ -56,6 +56,7 @@ pub fn delete_shop(
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::delete())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(with_env(env))
|
||||
.and_then(handlers::delete_shop)
|
||||
}
|
||||
@ -91,6 +92,7 @@ pub fn delete_owner(
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::delete())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(with_env(env))
|
||||
.and_then(handlers::delete_owner)
|
||||
}
|
||||
@ -127,6 +129,7 @@ pub fn delete_interior_ref_list(
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::delete())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(with_env(env))
|
||||
.and_then(handlers::delete_interior_ref_list)
|
||||
}
|
||||
|
@ -1,13 +1,35 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use ipnetwork::IpNetwork;
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::net::SocketAddr;
|
||||
use uuid::Uuid;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reply::{json, with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use super::models::{InteriorRefList, ListParams, Model, Owner, Shop};
|
||||
use super::problem::reject_anyhow;
|
||||
use super::problem::{forbidden_no_api_key, forbidden_no_owner, reject_anyhow};
|
||||
use super::Environment;
|
||||
|
||||
pub async fn authenticate(api_key: Option<Uuid>, db: &PgPool) -> Result<i32> {
|
||||
if let Some(api_key) = api_key {
|
||||
Ok(
|
||||
sqlx::query!("SELECT id FROM owners WHERE api_key = $1", api_key)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
if let sqlx::Error::RowNotFound = error {
|
||||
return forbidden_no_owner();
|
||||
}
|
||||
anyhow!(error)
|
||||
})?
|
||||
.id,
|
||||
)
|
||||
} else {
|
||||
Err(forbidden_no_api_key())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_shop(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
||||
let shop = Shop::get(&env.db, id).await.map_err(reject_anyhow)?;
|
||||
let reply = json(&shop);
|
||||
@ -36,8 +58,18 @@ pub async fn create_shop(shop: Shop, env: Environment) -> Result<impl Reply, Rej
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
pub async fn delete_shop(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
||||
Shop::delete(&env.db, id).await.map_err(reject_anyhow)?;
|
||||
pub async fn delete_shop(
|
||||
id: i32,
|
||||
api_key: Option<Uuid>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let owner_id = authenticate(api_key, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
dbg!(owner_id);
|
||||
Shop::delete(&env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@ -80,8 +112,18 @@ pub async fn create_owner(
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
pub async fn delete_owner(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
||||
Owner::delete(&env.db, id).await.map_err(reject_anyhow)?;
|
||||
pub async fn delete_owner(
|
||||
id: i32,
|
||||
api_key: Option<Uuid>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let owner_id = authenticate(api_key, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
dbg!(owner_id);
|
||||
Owner::delete(&env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@ -123,8 +165,16 @@ pub async fn create_interior_ref_list(
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
pub async fn delete_interior_ref_list(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
||||
InteriorRefList::delete(&env.db, id)
|
||||
pub async fn delete_interior_ref_list(
|
||||
id: i32,
|
||||
api_key: Option<Uuid>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let owner_id = authenticate(api_key, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
dbg!(owner_id);
|
||||
InteriorRefList::delete(&env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
|
@ -8,6 +8,7 @@ use tracing::instrument;
|
||||
|
||||
use super::ListParams;
|
||||
use super::Model;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||
// support user-defined types (`ref_list` Json field).
|
||||
@ -31,6 +32,7 @@ pub struct InteriorRef {
|
||||
pub struct InteriorRefList {
|
||||
pub id: Option<i32>,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub ref_list: Json<Vec<InteriorRef>>,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
@ -64,10 +66,11 @@ impl Model for InteriorRefList {
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
Self,
|
||||
"INSERT INTO interior_ref_lists
|
||||
(shop_id, ref_list, created_at, updated_at)
|
||||
VALUES ($1, $2, now(), now())
|
||||
(shop_id, owner_id, ref_list, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
self.shop_id,
|
||||
self.owner_id,
|
||||
self.ref_list,
|
||||
)
|
||||
.fetch_one(db)
|
||||
@ -75,12 +78,20 @@ impl Model for InteriorRefList {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
||||
Ok(
|
||||
sqlx::query!("DELETE FROM interior_ref_lists WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?,
|
||||
)
|
||||
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
let interior_ref_list =
|
||||
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if interior_ref_list.owner_id == owner_id {
|
||||
return Ok(
|
||||
sqlx::query!("DELETE FROM interior_ref_lists WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?,
|
||||
);
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
|
@ -24,6 +24,6 @@ where
|
||||
}
|
||||
async fn get(db: &PgPool, id: i32) -> Result<Self>;
|
||||
async fn save(self, db: &PgPool) -> Result<Self>;
|
||||
async fn delete(db: &PgPool, id: i32) -> Result<u64>;
|
||||
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64>;
|
||||
async fn list(db: &PgPool, list_params: ListParams) -> Result<Vec<Self>>;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use uuid::Uuid;
|
||||
|
||||
use super::ListParams;
|
||||
use super::Model;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Owner {
|
||||
@ -58,10 +59,17 @@ impl Model for Owner {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
||||
Ok(sqlx::query!("DELETE FROM owners WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?)
|
||||
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
let owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if owner.id == owner_id {
|
||||
Ok(sqlx::query!("DELETE FROM owners WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?)
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
|
@ -7,6 +7,7 @@ use tracing::instrument;
|
||||
|
||||
use super::ListParams;
|
||||
use super::Model;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Shop {
|
||||
@ -63,10 +64,17 @@ impl Model for Shop {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
||||
Ok(sqlx::query!("DELETE FROM shops WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?)
|
||||
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
let shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if shop.owner_id == owner_id {
|
||||
return Ok(sqlx::query!("DELETE FROM shops WHERE shops.id = $1", id)
|
||||
.execute(db)
|
||||
.await?);
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
|
@ -1,8 +1,30 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
use http_api_problem::HttpApiProblem;
|
||||
use tracing::error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::{reject, Rejection, Reply};
|
||||
|
||||
pub fn forbidden_permission() -> Error {
|
||||
anyhow!(
|
||||
HttpApiProblem::with_title_and_type_from_status(StatusCode::FORBIDDEN,)
|
||||
.set_detail("Api-Key does not have required permissions")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn forbidden_no_owner() -> Error {
|
||||
anyhow!(
|
||||
HttpApiProblem::with_title_and_type_from_status(StatusCode::FORBIDDEN,)
|
||||
.set_detail("Api-Key not recognized")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn forbidden_no_api_key() -> Error {
|
||||
anyhow!(
|
||||
HttpApiProblem::with_title_and_type_from_status(StatusCode::FORBIDDEN,)
|
||||
.set_detail("Api-Key header not present")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
||||
let error = match error.downcast::<HttpApiProblem>() {
|
||||
Ok(problem) => return problem,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"shop_id": 1,
|
||||
"owner_id": 1,
|
||||
"ref_list": [
|
||||
{
|
||||
"mod_name": "Skyrim.esm",
|
||||
|
5
test_data/owner2.json
Normal file
5
test_data/owner2.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Test Owner 2",
|
||||
"api_key": "84f03de0-225f-44c1-894d-d5a8c2f74803",
|
||||
"mod_version": "0.0.1"
|
||||
}
|
Loading…
Reference in New Issue
Block a user