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| {
|
m.create_table("owners", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("name", types::varchar(255));
|
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("ip_address", types::custom("inet").nullable(true));
|
||||||
t.add_column("mod_version", types::varchar(25));
|
t.add_column("mod_version", types::varchar(25));
|
||||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||||
@ -37,6 +37,7 @@ pub fn migration() -> String {
|
|||||||
m.create_table("merchandise", |t| {
|
m.create_table("merchandise", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("shop_id", types::foreign("shops", "id").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("mod_name", types::varchar(255));
|
||||||
t.add_column("local_form_id", types::integer());
|
t.add_column("local_form_id", types::integer());
|
||||||
t.add_column("quantity", types::integer());
|
t.add_column("quantity", types::integer());
|
||||||
@ -51,6 +52,7 @@ pub fn migration() -> String {
|
|||||||
m.create_table("transactions", |t| {
|
m.create_table("transactions", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("shop_id", types::foreign("shops", "id").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("merchandise_id", types::foreign("merchandise", "id"));
|
||||||
t.add_column("customer_name", types::varchar(255));
|
t.add_column("customer_name", types::varchar(255));
|
||||||
t.add_column("is_customer_npc", types::boolean());
|
t.add_column("is_customer_npc", types::boolean());
|
||||||
@ -63,6 +65,7 @@ pub fn migration() -> String {
|
|||||||
m.create_table("interior_ref_lists", |t| {
|
m.create_table("interior_ref_lists", |t| {
|
||||||
t.add_column("id", types::primary().indexed(true));
|
t.add_column("id", types::primary().indexed(true));
|
||||||
t.add_column("shop_id", types::foreign("shops", "id").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("ref_list", types::custom("jsonb"));
|
||||||
t.add_column("created_at", types::custom("timestamp(3)"));
|
t.add_column("created_at", types::custom("timestamp(3)"));
|
||||||
t.add_column("updated_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 {
|
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::delete())
|
.and(warp::delete())
|
||||||
|
.and(warp::header::optional("api-key"))
|
||||||
.and(with_env(env))
|
.and(with_env(env))
|
||||||
.and_then(handlers::delete_shop)
|
.and_then(handlers::delete_shop)
|
||||||
}
|
}
|
||||||
@ -91,6 +92,7 @@ pub fn delete_owner(
|
|||||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::delete())
|
.and(warp::delete())
|
||||||
|
.and(warp::header::optional("api-key"))
|
||||||
.and(with_env(env))
|
.and(with_env(env))
|
||||||
.and_then(handlers::delete_owner)
|
.and_then(handlers::delete_owner)
|
||||||
}
|
}
|
||||||
@ -127,6 +129,7 @@ pub fn delete_interior_ref_list(
|
|||||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::delete())
|
.and(warp::delete())
|
||||||
|
.and(warp::header::optional("api-key"))
|
||||||
.and(with_env(env))
|
.and(with_env(env))
|
||||||
.and_then(handlers::delete_interior_ref_list)
|
.and_then(handlers::delete_interior_ref_list)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,35 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
|
use sqlx::postgres::PgPool;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use uuid::Uuid;
|
||||||
use warp::http::StatusCode;
|
use warp::http::StatusCode;
|
||||||
use warp::reply::{json, with_header, with_status};
|
use warp::reply::{json, with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{Rejection, Reply};
|
||||||
|
|
||||||
use super::models::{InteriorRefList, ListParams, Model, Owner, Shop};
|
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;
|
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> {
|
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 shop = Shop::get(&env.db, id).await.map_err(reject_anyhow)?;
|
||||||
let reply = json(&shop);
|
let reply = json(&shop);
|
||||||
@ -36,8 +58,18 @@ pub async fn create_shop(shop: Shop, env: Environment) -> Result<impl Reply, Rej
|
|||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_shop(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
pub async fn delete_shop(
|
||||||
Shop::delete(&env.db, id).await.map_err(reject_anyhow)?;
|
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)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +112,18 @@ pub async fn create_owner(
|
|||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_owner(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
pub async fn delete_owner(
|
||||||
Owner::delete(&env.db, id).await.map_err(reject_anyhow)?;
|
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)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,8 +165,16 @@ pub async fn create_interior_ref_list(
|
|||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_interior_ref_list(id: i32, env: Environment) -> Result<impl Reply, Rejection> {
|
pub async fn delete_interior_ref_list(
|
||||||
InteriorRefList::delete(&env.db, id)
|
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
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
@ -8,6 +8,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use super::Model;
|
use super::Model;
|
||||||
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||||
// support user-defined types (`ref_list` Json field).
|
// support user-defined types (`ref_list` Json field).
|
||||||
@ -31,6 +32,7 @@ pub struct InteriorRef {
|
|||||||
pub struct InteriorRefList {
|
pub struct InteriorRefList {
|
||||||
pub id: Option<i32>,
|
pub id: Option<i32>,
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
|
pub owner_id: i32,
|
||||||
pub ref_list: Json<Vec<InteriorRef>>,
|
pub ref_list: Json<Vec<InteriorRef>>,
|
||||||
pub created_at: Option<NaiveDateTime>,
|
pub created_at: Option<NaiveDateTime>,
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
@ -64,10 +66,11 @@ impl Model for InteriorRefList {
|
|||||||
Ok(sqlx::query_as_unchecked!(
|
Ok(sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"INSERT INTO interior_ref_lists
|
"INSERT INTO interior_ref_lists
|
||||||
(shop_id, ref_list, created_at, updated_at)
|
(shop_id, owner_id, ref_list, created_at, updated_at)
|
||||||
VALUES ($1, $2, now(), now())
|
VALUES ($1, $2, $3, now(), now())
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
self.shop_id,
|
self.shop_id,
|
||||||
|
self.owner_id,
|
||||||
self.ref_list,
|
self.ref_list,
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
@ -75,12 +78,20 @@ impl Model for InteriorRefList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(db))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||||
Ok(
|
let interior_ref_list =
|
||||||
sqlx::query!("DELETE FROM interior_ref_lists WHERE id = $1", id)
|
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
|
||||||
.execute(db)
|
.fetch_one(db)
|
||||||
.await?,
|
.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))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
|
@ -24,6 +24,6 @@ where
|
|||||||
}
|
}
|
||||||
async fn get(db: &PgPool, id: i32) -> Result<Self>;
|
async fn get(db: &PgPool, id: i32) -> Result<Self>;
|
||||||
async fn save(self, db: &PgPool) -> 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>>;
|
async fn list(db: &PgPool, list_params: ListParams) -> Result<Vec<Self>>;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use super::Model;
|
use super::Model;
|
||||||
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Owner {
|
pub struct Owner {
|
||||||
@ -58,10 +59,17 @@ impl Model for Owner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(db))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||||
Ok(sqlx::query!("DELETE FROM owners WHERE id = $1", id)
|
let owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
|
||||||
.execute(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.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))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
|
@ -7,6 +7,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use super::Model;
|
use super::Model;
|
||||||
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Shop {
|
pub struct Shop {
|
||||||
@ -63,10 +64,17 @@ impl Model for Shop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(db))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
async fn delete(db: &PgPool, id: i32) -> Result<u64> {
|
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||||
Ok(sqlx::query!("DELETE FROM shops WHERE id = $1", id)
|
let shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
|
||||||
.execute(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.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))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
|
@ -1,8 +1,30 @@
|
|||||||
|
use anyhow::{anyhow, Error};
|
||||||
use http_api_problem::HttpApiProblem;
|
use http_api_problem::HttpApiProblem;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use warp::http::StatusCode;
|
use warp::http::StatusCode;
|
||||||
use warp::{reject, Rejection, Reply};
|
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 {
|
pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
||||||
let error = match error.downcast::<HttpApiProblem>() {
|
let error = match error.downcast::<HttpApiProblem>() {
|
||||||
Ok(problem) => return problem,
|
Ok(problem) => return problem,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"shop_id": 1,
|
"shop_id": 1,
|
||||||
|
"owner_id": 1,
|
||||||
"ref_list": [
|
"ref_list": [
|
||||||
{
|
{
|
||||||
"mod_name": "Skyrim.esm",
|
"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