Implement all API according to swagger spec #27
|
@ -74,7 +74,7 @@ paths:
|
||||||
example: {}
|
example: {}
|
||||||
401:
|
401:
|
||||||
description: |-
|
description: |-
|
||||||
This error code is returned if one of the two conditions are met:
|
This error code is returned if one of these conditions are met:
|
||||||
|
|
||||||
1. The instance does not allow deleting files via API.
|
1. The instance does not allow deleting files via API.
|
||||||
2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key.
|
2. The file has been uploaded from another IP, which is not this one, and the API was not authorized via an API key.
|
||||||
|
@ -90,6 +90,9 @@ paths:
|
||||||
fid:
|
fid:
|
||||||
type: string
|
type: string
|
||||||
example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME
|
example: ID or name of the file. It is the NAME in file.blek.codes/uploads/NAME
|
||||||
|
api_key:
|
||||||
|
type: string
|
||||||
|
example: '123'
|
||||||
/api/files/upload:
|
/api/files/upload:
|
||||||
post:
|
post:
|
||||||
summary: Upload a file
|
summary: Upload a file
|
||||||
|
|
|
@ -49,10 +49,10 @@ fn check_api_pass(state: &SharedState, key: String) -> Result<(), WithStatus<Jso
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn function_disabled_err() -> WithStatus<Json> {
|
fn function_disabled_err(status: StatusCode) -> WithStatus<Json> {
|
||||||
warp::reply::with_status(
|
warp::reply::with_status(
|
||||||
json(&ErrorMessage::new(Error::APIFunctionDisabled)),
|
json(&ErrorMessage::new(Error::APIFunctionDisabled)),
|
||||||
StatusCode::SERVICE_UNAVAILABLE
|
status
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,56 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, net::IpAddr};
|
||||||
|
|
||||||
use warp::{reply::{Reply, json}, reject::Rejection, Filter, http::StatusCode};
|
use warp::{reply::{Reply, json, with_status}, reject::Rejection, Filter, http::StatusCode};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use warp_real_ip::real_ip;
|
||||||
|
|
||||||
use crate::web::{state::SharedState, rejection::HttpReject, api::types::{ErrorMessage, Error}};
|
use crate::{web::{state::SharedState, rejection::HttpReject, api::types::{ErrorMessage, Error}}, files::File};
|
||||||
|
|
||||||
use super::{function_disabled_err, check_api_enabled};
|
use super::{function_disabled_err, check_api_enabled};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct DeleteFunctionPayload {
|
pub struct DeleteFunctionPayload {
|
||||||
pub fid: String
|
pub fid: String,
|
||||||
|
pub api_key: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<Box<dyn Reply>, Rejection> {
|
pub async fn delete(state: SharedState, body: DeleteFunctionPayload, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
|
||||||
if let Err(res) = check_api_enabled(&state) {
|
if let Err(res) = check_api_enabled(&state) {
|
||||||
return Ok(Box::new(res));
|
return Ok(Box::new(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.config.api.delete) || (!state.config.api.enabled) {
|
if (!state.config.api.delete) || (!state.config.api.enabled) {
|
||||||
return Ok(Box::new(function_disabled_err()))
|
return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut sudo_authorized = false;
|
||||||
|
let mut blocked = false;
|
||||||
|
|
||||||
|
if let Some(keys) = state.config.api.apikeys.clone() {
|
||||||
|
if let Some(key) = body.api_key {
|
||||||
|
|
||||||
|
if keys.contains(&key) {
|
||||||
|
sudo_authorized = true;
|
||||||
|
blocked = false;
|
||||||
|
} else {
|
||||||
|
sudo_authorized = false;
|
||||||
|
blocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sudo_authorized = false;
|
||||||
|
blocked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! sudo_authorized {
|
||||||
|
if ip.is_none() { // need the ip if sudo is not authorized
|
||||||
|
blocked = true // to check if the file is the own file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ip = ip.unwrap();
|
||||||
|
|
||||||
let id = body.fid;
|
let id = body.fid;
|
||||||
let mut file = state.file_mgr.find_by_hash(id.clone())
|
let mut file = state.file_mgr.find_by_hash(id.clone())
|
||||||
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
||||||
|
@ -30,7 +60,7 @@ pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<B
|
||||||
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
.map_err(|x| HttpReject::StringError(x.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let None = file {
|
if let None = file.clone() {
|
||||||
return Ok(
|
return Ok(
|
||||||
Box::new(
|
Box::new(
|
||||||
warp::reply::with_status(
|
warp::reply::with_status(
|
||||||
|
@ -45,13 +75,47 @@ pub async fn delete(state: SharedState, body: DeleteFunctionPayload) -> Result<B
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let file: File = file.unwrap();
|
||||||
|
|
||||||
|
if let Some(uploader) = file.uploader_ip {
|
||||||
|
if uploader != ip && (!sudo_authorized) {
|
||||||
|
blocked = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if blocked {
|
||||||
|
return Ok(
|
||||||
|
Box::new(
|
||||||
|
with_status(
|
||||||
|
json(
|
||||||
|
&ErrorMessage {
|
||||||
|
error: Error::APIPasswordDenied,
|
||||||
|
details: Some(
|
||||||
|
"Request has been denied for one of the following reasons: password auth did not pass, file was uploaded by someone else, the instance does not allow deleting files via the API".into()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.delete(state).await;
|
||||||
|
|
||||||
Ok(Box::new(json(&HashMap::<(), ()>::new())))
|
Ok(Box::new(json(&HashMap::<(), ()>::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
pub fn delete_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
|
|
||||||
|
let proxy_ip = state.env.proxy_addr;
|
||||||
|
|
||||||
warp::path!("api" / "files" / "delete")
|
warp::path!("api" / "files" / "delete")
|
||||||
.map(move || state.clone())
|
.map(move || state.clone())
|
||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
|
.and(real_ip(vec![proxy_ip]))
|
||||||
.and_then(delete)
|
.and_then(delete)
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ pub async fn get_all(state: SharedState, ip: Option<IpAddr>) -> Result<Box<dyn R
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.config.api.get_all) || (!state.config.api.enabled) {
|
if (!state.config.api.get_all) || (!state.config.api.enabled) {
|
||||||
return Ok(Box::new(function_disabled_err()))
|
return Ok(Box::new(function_disabled_err(StatusCode::UNAUTHORIZED)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let found =
|
let found =
|
||||||
|
|
Loading…
Reference in New Issue