2021-03-17 05:55:04 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2021-03-23 01:13:32 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-03-25 00:56:16 +01:00
|
|
|
use warp::{http::StatusCode, reply::Reply, reply::Response, Rejection};
|
2021-03-12 05:11:12 +01:00
|
|
|
|
2021-03-12 06:40:24 +01:00
|
|
|
use super::errors::Error;
|
2021-03-12 05:11:12 +01:00
|
|
|
use super::handlers;
|
2021-03-24 00:02:53 +01:00
|
|
|
use super::models;
|
2021-03-12 05:11:12 +01:00
|
|
|
use super::storage;
|
|
|
|
|
2021-03-12 08:55:37 +01:00
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
|
|
pub struct RpcCall {
|
|
|
|
pub endpoint: String,
|
|
|
|
pub body: String,
|
2021-03-17 05:55:04 +01:00
|
|
|
pub method: String,
|
2021-03-25 01:38:06 +01:00
|
|
|
pub headers: HashMap<String, String>,
|
2021-03-12 08:55:37 +01:00
|
|
|
}
|
|
|
|
|
2021-03-18 05:04:56 +01:00
|
|
|
pub async fn handle_rpc_call(rpc_call: RpcCall) -> Result<Response, Rejection> {
|
|
|
|
// Get a connection pool for the given room
|
2021-03-18 05:53:24 +01:00
|
|
|
let room_id = match get_room_id(&rpc_call) {
|
|
|
|
Some(room_id) => room_id,
|
2021-03-23 03:08:44 +01:00
|
|
|
None => {
|
|
|
|
println!("Missing room ID.");
|
2021-03-25 00:56:16 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-23 03:08:44 +01:00
|
|
|
}
|
2021-03-18 05:53:24 +01:00
|
|
|
};
|
2021-03-23 05:39:42 +01:00
|
|
|
let pool = storage::pool_by_room_id(&room_id);
|
2021-03-23 23:27:21 +01:00
|
|
|
// Check that the endpoint is a valid URI and deconstruct it into a path
|
|
|
|
// and query parameters.
|
2021-03-24 03:54:00 +01:00
|
|
|
// Adding "http://placeholder.io" in front of the endpoint is a workaround
|
2021-03-23 23:27:21 +01:00
|
|
|
// for the fact that the URL crate doesn't accept relative URLs. There are
|
|
|
|
// other (cleaner) ways to fix this but they tend to be much more complex.
|
2021-03-23 23:12:54 +01:00
|
|
|
let raw_uri = format!("http://placeholder.io/{}", rpc_call.endpoint.trim_start_matches("/"));
|
|
|
|
let path: String = match raw_uri.parse::<http::Uri>() {
|
|
|
|
Ok(uri) => uri.path().trim_start_matches("/").to_string(),
|
2021-03-12 05:11:12 +01:00
|
|
|
Err(e) => {
|
2021-03-23 23:12:54 +01:00
|
|
|
println!("Couldn't parse URI from: {} due to error: {}.", &raw_uri, e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let query_params: HashMap<String, String> = match url::Url::parse(&raw_uri) {
|
|
|
|
Ok(url) => url.query_pairs().into_owned().collect(),
|
|
|
|
Err(e) => {
|
|
|
|
println!("Couldn't parse URL from: {} due to error: {}.", &raw_uri, e);
|
2021-03-12 08:55:37 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
};
|
2021-03-17 23:35:51 +01:00
|
|
|
// Get the auth token if possible
|
|
|
|
let auth_token = get_auth_token(&rpc_call);
|
2021-03-12 05:11:12 +01:00
|
|
|
// Switch on the HTTP method
|
|
|
|
match rpc_call.method.as_ref() {
|
2021-03-25 04:05:46 +01:00
|
|
|
"GET" => return handle_get_request(rpc_call, &path, auth_token, query_params, &pool).await,
|
2021-03-23 23:12:54 +01:00
|
|
|
"POST" => return handle_post_request(rpc_call, &path, auth_token, &pool).await,
|
|
|
|
"DELETE" => return handle_delete_request(rpc_call, &path, auth_token, &pool).await,
|
2021-03-12 05:11:12 +01:00
|
|
|
_ => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Ignoring RPC call with invalid or unused HTTP method: {}.", rpc_call.method);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 00:56:16 +01:00
|
|
|
async fn handle_get_request(
|
2021-03-25 04:05:46 +01:00
|
|
|
rpc_call: RpcCall, path: &str, auth_token: Option<String>,
|
2021-03-25 01:38:06 +01:00
|
|
|
query_params: HashMap<String, String>, pool: &storage::DatabaseConnectionPool,
|
2021-03-25 00:56:16 +01:00
|
|
|
) -> Result<Response, Rejection> {
|
2021-03-25 04:05:46 +01:00
|
|
|
// Handle routes that don't require authorization first
|
2021-03-25 00:17:47 +01:00
|
|
|
if path == "auth_token_challenge" {
|
|
|
|
let challenge = handlers::get_auth_token_challenge(query_params, pool).await?;
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
struct Response {
|
|
|
|
status_code: u16,
|
2021-03-25 01:38:06 +01:00
|
|
|
challenge: models::Challenge,
|
2021-03-25 00:17:47 +01:00
|
|
|
}
|
2021-03-25 00:56:16 +01:00
|
|
|
let response = Response { status_code: StatusCode::OK.as_u16(), challenge };
|
2021-03-25 00:17:47 +01:00
|
|
|
return Ok(warp::reply::json(&response).into_response());
|
2021-03-25 04:05:46 +01:00
|
|
|
} else if path.starts_with("rooms") {
|
|
|
|
let components: Vec<&str> = path.split("/").collect(); // Split on subsequent slashes
|
|
|
|
if components.len() == 1 {
|
|
|
|
return handlers::get_all_rooms().await;
|
|
|
|
} else if components.len() == 2 {
|
|
|
|
let room_id = components[1];
|
|
|
|
return handlers::get_room(&room_id).await;
|
|
|
|
} else {
|
|
|
|
println!("Invalid endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
} else if path == "rooms" {
|
|
|
|
return handlers::get_all_rooms().await;
|
2021-03-25 00:17:47 +01:00
|
|
|
}
|
|
|
|
// Check that the auth token is present
|
|
|
|
let auth_token = auth_token.ok_or(warp::reject::custom(Error::Unauthorized))?;
|
|
|
|
// Switch on the path
|
2021-03-23 05:50:03 +01:00
|
|
|
if path.starts_with("files") {
|
|
|
|
let components: Vec<&str> = path.split("/").collect(); // Split on subsequent slashes
|
2021-03-19 00:09:13 +01:00
|
|
|
if components.len() != 2 {
|
|
|
|
println!("Invalid endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
let file_id = components[1];
|
2021-03-25 00:56:16 +01:00
|
|
|
return handlers::get_file(file_id, &auth_token, &pool)
|
|
|
|
.await
|
|
|
|
.map(|json| warp::reply::json(&json).into_response());
|
2021-03-19 00:09:13 +01:00
|
|
|
}
|
2021-03-23 05:50:03 +01:00
|
|
|
match path {
|
2021-03-25 00:17:47 +01:00
|
|
|
"messages" => return handlers::get_messages(query_params, &auth_token, pool).await,
|
2021-03-25 00:56:16 +01:00
|
|
|
"deleted_messages" => {
|
|
|
|
return handlers::get_deleted_messages(query_params, &auth_token, pool).await
|
|
|
|
}
|
2021-03-25 00:17:47 +01:00
|
|
|
"moderators" => return handlers::get_moderators(&auth_token, pool).await,
|
|
|
|
"block_list" => return handlers::get_banned_public_keys(&auth_token, pool).await,
|
|
|
|
"member_count" => return handlers::get_member_count(&auth_token, pool).await,
|
2021-03-23 05:50:03 +01:00
|
|
|
"auth_token_challenge" => {
|
2021-03-24 00:02:53 +01:00
|
|
|
let challenge = handlers::get_auth_token_challenge(query_params, pool).await?;
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
struct Response {
|
|
|
|
status_code: u16,
|
2021-03-25 01:38:06 +01:00
|
|
|
challenge: models::Challenge,
|
2021-03-24 00:02:53 +01:00
|
|
|
}
|
2021-03-25 00:56:16 +01:00
|
|
|
let response = Response { status_code: StatusCode::OK.as_u16(), challenge };
|
2021-03-24 00:02:53 +01:00
|
|
|
return Ok(warp::reply::json(&response).into_response());
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-12 05:11:12 +01:00
|
|
|
_ => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Ignoring RPC call with invalid or unused endpoint: {}.", rpc_call.endpoint);
|
2021-03-25 00:56:16 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 00:56:16 +01:00
|
|
|
async fn handle_post_request(
|
|
|
|
rpc_call: RpcCall, path: &str, auth_token: Option<String>,
|
2021-03-25 01:38:06 +01:00
|
|
|
pool: &storage::DatabaseConnectionPool,
|
2021-03-25 00:56:16 +01:00
|
|
|
) -> Result<Response, Rejection> {
|
2021-03-25 00:17:47 +01:00
|
|
|
// Check that the auth token is present
|
|
|
|
let auth_token = auth_token.ok_or(warp::reject::custom(Error::Unauthorized))?;
|
|
|
|
// Switch on the path
|
2021-03-23 05:50:03 +01:00
|
|
|
match path {
|
|
|
|
"messages" => {
|
2021-03-12 05:11:12 +01:00
|
|
|
let message = match serde_json::from_str(&rpc_call.body) {
|
2021-03-17 01:51:11 +01:00
|
|
|
Ok(message) => message,
|
2021-03-12 05:11:12 +01:00
|
|
|
Err(e) => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Couldn't parse message from: {} due to error: {}.", rpc_call.body, e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
};
|
2021-03-25 00:56:16 +01:00
|
|
|
return handlers::insert_message(message, &auth_token, pool).await;
|
|
|
|
}
|
2021-03-23 05:50:03 +01:00
|
|
|
"block_list" => {
|
2021-03-19 06:44:07 +01:00
|
|
|
#[derive(Debug, Deserialize)]
|
2021-03-25 00:56:16 +01:00
|
|
|
struct JSON {
|
2021-03-25 01:38:06 +01:00
|
|
|
public_key: String,
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-19 06:44:07 +01:00
|
|
|
let json: JSON = match serde_json::from_str(&rpc_call.body) {
|
|
|
|
Ok(message) => message,
|
|
|
|
Err(e) => {
|
|
|
|
println!("Couldn't parse JSON from: {} due to error: {}.", rpc_call.body, e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
};
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::ban(&json.public_key, &auth_token, pool).await;
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-23 05:50:03 +01:00
|
|
|
"claim_auth_token" => {
|
2021-03-19 06:44:07 +01:00
|
|
|
#[derive(Debug, Deserialize)]
|
2021-03-25 00:56:16 +01:00
|
|
|
struct JSON {
|
2021-03-25 01:38:06 +01:00
|
|
|
public_key: String,
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-19 06:44:07 +01:00
|
|
|
let json: JSON = match serde_json::from_str(&rpc_call.body) {
|
|
|
|
Ok(message) => message,
|
|
|
|
Err(e) => {
|
|
|
|
println!("Couldn't parse JSON from: {} due to error: {}.", rpc_call.body, e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
};
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::claim_auth_token(&json.public_key, &auth_token, pool).await;
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-23 05:50:03 +01:00
|
|
|
"files" => {
|
2021-03-19 06:44:07 +01:00
|
|
|
#[derive(Debug, Deserialize)]
|
2021-03-25 00:56:16 +01:00
|
|
|
struct JSON {
|
2021-03-25 01:38:06 +01:00
|
|
|
file: String,
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-19 06:44:07 +01:00
|
|
|
let json: JSON = match serde_json::from_str(&rpc_call.body) {
|
|
|
|
Ok(message) => message,
|
|
|
|
Err(e) => {
|
|
|
|
println!("Couldn't parse JSON from: {} due to error: {}.", rpc_call.body, e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
|
|
|
}
|
|
|
|
};
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::store_file(&json.file, &auth_token, pool).await;
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|
2021-03-12 05:11:12 +01:00
|
|
|
_ => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Ignoring RPC call with invalid or unused endpoint: {}.", rpc_call.endpoint);
|
2021-03-25 00:56:16 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 00:56:16 +01:00
|
|
|
async fn handle_delete_request(
|
|
|
|
rpc_call: RpcCall, path: &str, auth_token: Option<String>,
|
2021-03-25 01:38:06 +01:00
|
|
|
pool: &storage::DatabaseConnectionPool,
|
2021-03-25 00:56:16 +01:00
|
|
|
) -> Result<Response, Rejection> {
|
2021-03-25 00:17:47 +01:00
|
|
|
// Check that the auth token is present
|
|
|
|
let auth_token = auth_token.ok_or(warp::reject::custom(Error::Unauthorized))?;
|
2021-03-12 05:11:12 +01:00
|
|
|
// DELETE /messages/:server_id
|
2021-03-23 05:50:03 +01:00
|
|
|
if path.starts_with("messages") {
|
|
|
|
let components: Vec<&str> = path.split("/").collect(); // Split on subsequent slashes
|
2021-03-12 05:11:12 +01:00
|
|
|
if components.len() != 2 {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Invalid endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
let server_id: i64 = match components[1].parse() {
|
|
|
|
Ok(server_id) => server_id,
|
2021-03-12 05:46:06 +01:00
|
|
|
Err(_) => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Invalid endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
};
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::delete_message(server_id, &auth_token, pool).await;
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
// DELETE /block_list/:public_key
|
2021-03-23 05:50:03 +01:00
|
|
|
if path.starts_with("block_list") {
|
|
|
|
let components: Vec<&str> = path.split("/").collect(); // Split on subsequent slashes
|
2021-03-12 05:11:12 +01:00
|
|
|
if components.len() != 2 {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Invalid endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
|
|
|
let public_key = components[1].to_string();
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::unban(&public_key, &auth_token, pool).await;
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
2021-03-18 00:29:59 +01:00
|
|
|
// DELETE /auth_token
|
2021-03-23 05:50:03 +01:00
|
|
|
if path == "auth_token" {
|
2021-03-25 00:17:47 +01:00
|
|
|
return handlers::delete_auth_token(&auth_token, pool).await;
|
2021-03-18 00:29:59 +01:00
|
|
|
}
|
2021-03-12 05:11:12 +01:00
|
|
|
// Unrecognized endpoint
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Ignoring RPC call with invalid or unused endpoint: {}.", rpc_call.endpoint);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidRpcCall));
|
2021-03-12 05:11:12 +01:00
|
|
|
}
|
2021-03-17 05:55:04 +01:00
|
|
|
|
|
|
|
// Utilities
|
2021-03-18 03:21:10 +01:00
|
|
|
|
2021-03-17 23:35:51 +01:00
|
|
|
fn get_auth_token(rpc_call: &RpcCall) -> Option<String> {
|
2021-03-25 00:56:16 +01:00
|
|
|
if rpc_call.headers.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2021-03-23 01:13:32 +01:00
|
|
|
return rpc_call.headers.get("Authorization").map(|s| s.to_string());
|
2021-03-18 05:53:24 +01:00
|
|
|
}
|
|
|
|
|
2021-03-23 03:21:28 +01:00
|
|
|
fn get_room_id(rpc_call: &RpcCall) -> Option<String> {
|
2021-03-25 00:56:16 +01:00
|
|
|
if rpc_call.headers.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2021-03-23 03:21:28 +01:00
|
|
|
return rpc_call.headers.get("Room").map(|s| s.to_string());
|
2021-03-25 00:56:16 +01:00
|
|
|
}
|