Serve certain endpoints on localhost

This commit is contained in:
nielsandriesse 2021-03-31 11:00:02 +11:00
parent e9bacc93b2
commit d99a5e3df6
7 changed files with 120 additions and 66 deletions

View File

@ -20,13 +20,6 @@ enum AuthorizationLevel {
Moderator,
}
#[derive(Debug, Deserialize, Serialize)]
struct RoomInfo {
id: String,
name: String,
image_id: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct GenericStringResponse {
pub status_code: u16,
@ -35,14 +28,14 @@ pub struct GenericStringResponse {
// Rooms
// Currently not exposed
pub async fn create_room(id: &str, name: &str) -> Result<Response, Rejection> {
// Not publicly exposed.
pub async fn create_room(room: models::Room) -> Result<Response, Rejection> {
// Get a connection
let pool = &storage::MAIN_POOL;
let conn = pool.get().map_err(|_| Error::DatabaseFailedInternally)?;
// Insert the room
let stmt = format!("REPLACE INTO {} (id, name) VALUES (?1, ?2)", storage::MAIN_TABLE);
match conn.execute(&stmt, params![id, name]) {
match conn.execute(&stmt, params![&room.id, &room.name]) {
Ok(_) => (),
Err(e) => {
println!("Couldn't create room due to error: {}.", e);
@ -50,20 +43,20 @@ pub async fn create_room(id: &str, name: &str) -> Result<Response, Rejection> {
}
}
// Set up the database
storage::create_database_if_needed(id);
storage::create_database_if_needed(&room.id);
// Return
let json = models::StatusCode { status_code: StatusCode::OK.as_u16() };
return Ok(warp::reply::json(&json).into_response());
}
// Currently not exposed
pub async fn delete_room(id: &str) -> Result<Response, Rejection> {
// Not publicly exposed.
pub async fn delete_room(id: String) -> Result<Response, Rejection> {
// Get a connection
let pool = &storage::MAIN_POOL;
let conn = pool.get().map_err(|_| Error::DatabaseFailedInternally)?;
// Insert the room
let stmt = format!("DELETE FROM {} WHERE id = (?1)", storage::MAIN_TABLE);
match conn.execute(&stmt, params![id]) {
match conn.execute(&stmt, params![&id]) {
Ok(_) => (),
Err(e) => {
println!("Couldn't delete room due to error: {}.", e);
@ -83,7 +76,7 @@ pub async fn get_room(room_id: &str) -> Result<Response, Rejection> {
let raw_query =
format!("SELECT id, name, image_id FROM {} where id = (?1)", storage::MAIN_TABLE);
let room = match conn.query_row(&raw_query, params![room_id], |row| {
Ok(RoomInfo { id: row.get(0)?, name: row.get(1)?, image_id: row.get(2).ok() })
Ok(models::Room { id: row.get(0)?, name: row.get(1)?, image_id: row.get(2).ok() })
}) {
Ok(info) => info,
Err(_) => return Err(warp::reject::custom(Error::NoSuchRoom)),
@ -92,7 +85,7 @@ pub async fn get_room(room_id: &str) -> Result<Response, Rejection> {
#[derive(Debug, Deserialize, Serialize)]
struct Response {
status_code: u16,
room: RoomInfo,
room: models::Room,
}
let response = Response { status_code: StatusCode::OK.as_u16(), room };
return Ok(warp::reply::json(&response).into_response());
@ -106,7 +99,7 @@ pub async fn get_all_rooms() -> Result<Response, Rejection> {
let raw_query = format!("SELECT id, name, image_id FROM {}", storage::MAIN_TABLE);
let mut query = conn.prepare(&raw_query).map_err(|_| Error::DatabaseFailedInternally)?;
let rows = match query.query_map(params![], |row| {
Ok(RoomInfo { id: row.get(0)?, name: row.get(1)?, image_id: row.get(2).ok() })
Ok(models::Room { id: row.get(0)?, name: row.get(1)?, image_id: row.get(2).ok() })
}) {
Ok(rows) => rows,
Err(e) => {
@ -114,12 +107,12 @@ pub async fn get_all_rooms() -> Result<Response, Rejection> {
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
let rooms: Vec<RoomInfo> = rows.filter_map(|result| result.ok()).collect();
let rooms: Vec<models::Room> = rows.filter_map(|result| result.ok()).collect();
// Return
#[derive(Debug, Deserialize, Serialize)]
struct Response {
status_code: u16,
rooms: Vec<RoomInfo>,
rooms: Vec<models::Room>,
}
let response = Response { status_code: StatusCode::OK.as_u16(), rooms };
return Ok(warp::reply::json(&response).into_response());
@ -603,13 +596,14 @@ pub async fn get_deleted_messages(
// Currently not exposed
pub async fn make_public_key_moderator(
public_key: &str, pool: &storage::DatabaseConnectionPool,
body: models::AddModeratorRequestBody,
) -> Result<Response, Rejection> {
// Get a database connection
let pool = storage::pool_by_room_id(&body.room_id);
let conn = pool.get().map_err(|_| Error::DatabaseFailedInternally)?;
// Insert the moderator
let stmt = format!("INSERT INTO {} (public_key) VALUES (?1)", storage::MODERATORS_TABLE);
match conn.execute(&stmt, params![public_key]) {
match conn.execute(&stmt, params![&body.public_key]) {
Ok(_) => (),
Err(e) => {
println!("Couldn't make public key moderator due to error: {}.", e);

View File

@ -11,6 +11,7 @@ mod errors;
mod handlers;
mod models;
mod onion_requests;
mod options;
mod routes;
mod rpc;
mod storage;
@ -18,46 +19,12 @@ mod storage;
#[cfg(test)]
mod tests;
// The default is * not * to run in TLS mode. This is because normally the server communicates through
// onion requests, eliminating the need for TLS.
#[derive(StructOpt)]
#[structopt(name = "Session Open Group Server")]
struct Opt {
/// Run in TLS mode.
#[structopt(long)]
tls: bool,
/// Path to TLS certificate.
#[structopt(long = "tls-certificate", default_value = "tls_certificate.pem")]
tls_certificate: String,
/// Path to TLS private key.
#[structopt(long = "tls-private-key", default_value = "tls_private_key.pem")]
tls_private_key: String,
/// Path to X25519 public key.
#[structopt(long = "x25519-public-key", default_value = "x25519_public_key.pem")]
x25519_public_key: String,
/// Path to X25519 private key.
#[structopt(long = "x25519-private-key", default_value = "x25519_private_key.pem")]
x25519_private_key: String,
/// Port to bind to.
#[structopt(short = "P", long = "port", default_value = "80")]
port: u16,
/// IP to bind to.
#[structopt(short = "H", long = "host", default_value = "0.0.0.0")]
host: Ipv4Addr,
}
#[tokio::main]
async fn main() {
// Parse arguments
let opt = Opt::from_args();
let opt = options::Opt::from_args();
let addr = SocketAddr::new(IpAddr::V4(opt.host), opt.port);
let localhost = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 80);
*crypto::PRIVATE_KEY_PATH.lock().unwrap() = opt.x25519_private_key;
*crypto::PUBLIC_KEY_PATH.lock().unwrap() = opt.x25519_public_key;
// Print the server public key
@ -75,30 +42,36 @@ async fn main() {
let prune_tokens_future = storage::prune_tokens_periodically();
let prune_files_future = storage::prune_files_periodically();
// Serve routes
let routes = routes::root().or(routes::lsrpc());
let public_routes = routes::root().or(routes::lsrpc());
let private_routes =
routes::create_room().or(routes::delete_room()).or(routes::add_moderator());
if opt.tls {
println!("Running on {} with TLS.", addr);
let serve_routes_future = warp::serve(routes)
let serve_public_routes_future = warp::serve(public_routes)
.tls()
.cert_path(opt.tls_certificate)
.key_path(opt.tls_private_key)
.run(addr);
let serve_private_routes_future = warp::serve(private_routes).run(localhost);
// Keep futures alive
join!(
prune_pending_tokens_future,
prune_tokens_future,
prune_files_future,
serve_routes_future
serve_public_routes_future,
serve_private_routes_future
);
} else {
println!("Running on {}.", addr);
let serve_routes_future = warp::serve(routes).run(addr);
let serve_public_routes_future = warp::serve(public_routes).run(addr);
let serve_private_routes_future = warp::serve(private_routes).run(localhost);
// Keep futures alive
join!(
prune_pending_tokens_future,
prune_tokens_future,
prune_files_future,
serve_routes_future
serve_public_routes_future,
serve_private_routes_future
);
}
}
@ -106,8 +79,9 @@ async fn main() {
async fn create_default_rooms() {
let info: Vec<(&str, &str)> = vec![("main", "Main")];
for info in info {
let id = info.0;
let name = info.1;
handlers::create_room(id, name).await.unwrap();
let id = info.0.to_string();
let name = info.1.to_string();
let room = models::Room { id, name, image_id: None };
handlers::create_room(room).await.unwrap();
}
}

View File

@ -15,6 +15,19 @@ impl Message {
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Room {
pub id: String,
pub name: String,
pub image_id: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct AddModeratorRequestBody {
pub public_key: String,
pub room_id: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Challenge {
pub ciphertext: String,

38
src/options.rs Normal file
View File

@ -0,0 +1,38 @@
use std::net::Ipv4Addr;
use structopt::StructOpt;
// The default is * not * to run in TLS mode. This is because normally the server communicates through
// onion requests, eliminating the need for TLS.
#[derive(StructOpt)]
#[structopt(name = "Session Open Group Server")]
pub struct Opt {
/// Path to X25519 public key.
#[structopt(long = "x25519-public-key", default_value = "x25519_public_key.pem")]
pub x25519_public_key: String,
/// Path to X25519 private key.
#[structopt(long = "x25519-private-key", default_value = "x25519_private_key.pem")]
pub x25519_private_key: String,
/// Port to bind to.
#[structopt(short = "P", long = "port", default_value = "80")]
pub port: u16,
/// IP to bind to.
#[structopt(short = "H", long = "host", default_value = "0.0.0.0")]
pub host: Ipv4Addr,
/// Run in TLS mode.
#[structopt(long)]
pub tls: bool,
/// Path to TLS certificate.
#[structopt(long = "tls-certificate", default_value = "tls_certificate.pem")]
pub tls_certificate: String,
/// Path to TLS private key.
#[structopt(long = "tls-private-key", default_value = "tls_private_key.pem")]
pub tls_private_key: String,
}

View File

@ -1,6 +1,7 @@
use warp::{reply::Reply, reply::Response, Filter, Rejection};
use super::errors;
use super::handlers;
use super::onion_requests;
/// GET /
@ -23,6 +24,33 @@ pub fn lsrpc() -> impl Filter<Extract = impl warp::Reply, Error = Rejection> + C
.recover(into_response);
}
/// POST /rooms
///
/// Not publicly exposed.
pub fn create_room() -> impl Filter<Extract = impl warp::Reply, Error = Rejection> + Clone {
return warp::post()
.and(warp::path("rooms"))
.and(warp::body::json())
.and_then(handlers::create_room);
}
/// DELETE /rooms/:id
///
/// Not publicly exposed.
pub fn delete_room() -> impl Filter<Extract = impl warp::Reply, Error = Rejection> + Clone {
return warp::delete().and(warp::path!("rooms" / String)).and_then(handlers::delete_room);
}
/// POST /moderators
///
/// Not publicly exposed.
pub fn add_moderator() -> impl Filter<Extract = impl warp::Reply, Error = Rejection> + Clone {
return warp::post()
.and(warp::path("moderators"))
.and(warp::body::json())
.and_then(handlers::make_public_key_moderator);
}
pub async fn root_html() -> Result<Response, Rejection> {
let body = r#"
<html>

View File

@ -8,6 +8,7 @@ use super::handlers;
use super::models;
use super::storage;
#[allow(dead_code)]
enum Mode {
FileServer,
OpenGroupServer,

View File

@ -8,6 +8,7 @@ use warp::http::StatusCode;
use super::crypto;
use super::handlers;
use super::models;
use super::storage;
macro_rules! aw {
@ -26,7 +27,12 @@ fn set_up_test_room() {
perform_main_setup();
let test_room_id = "test_room";
let test_room_name = "Test Room";
aw!(handlers::create_room(&test_room_id, &test_room_name)).unwrap();
let test_room = models::Room {
id: test_room_id.to_string(),
name: test_room_name.to_string(),
image_id: None,
};
aw!(handlers::create_room(test_room)).unwrap();
let raw_path = format!("rooms/{}.db", test_room_id);
let path = Path::new(&raw_path);
fs::read(path).unwrap(); // Fail if this doesn't exist