freechat/server-api/src/main.rs
shockrah 1c1bb5f3cd * -c now uses the name parameter given to it
* new branch for failure of secret generation
2020-11-21 13:21:44 -08:00

254 lines
8.5 KiB
Rust

extern crate db;
extern crate chrono;
extern crate clap;
extern crate dotenv;
extern crate getrandom;
extern crate bcrypt;
extern crate base64;
extern crate serde;
use std::net::SocketAddr;
use std::convert::Infallible; // our main dispatcher basically never fails hence why we use this
use std::env::{self, set_var};
use tokio;
use hyper::{
self,
Server,
Response, Request, Body,
Method, StatusCode,
service::{make_service_fn, service_fn}
};
use mysql_async::Pool;
use dotenv::dotenv;
use clap::{Arg, App};
use auth::AuthReason;
mod auth;
mod routes;
mod invites;
mod channels;
mod members;
mod perms;
mod messages;
mod admin;
mod http_params;
mod testing;
const NO_ERR: u16 = 0;
const CONFIG_ERR: u16 = 1;
const SHUTDOWN_ERR: u16 = 2;
async fn route_dispatcher(pool: &Pool, resp: &mut Response<Body>, meth: &Method, path: &str, params: serde_json::Value) {
// At some point we should have some way of hiding this obnoxious complexity
use routes::resolve_dynamic_route;
const GET: &Method = &Method::GET;
const POST: &Method = &Method::POST;
const DELETE: &Method = &Method::DELETE;
match (meth, path) {
/* INVITES */
(GET, routes::INVITE_CREATE) => invites::create(pool, resp, params).await,
/* CHANNELS */
(GET, routes::CHANNELS_LIST) => channels::list_channels(pool, resp).await,
(POST, routes::CHANNELS_CREATE) => channels::create_channel(pool, resp, params).await,
(DELETE, routes::CHANNELS_DELETE) => channels::delete_channel(pool, resp, params).await,
/* MESSAGING */
(POST, routes::MESSAGE_SEND) => messages::send_message(pool, resp, params).await,
/* ADMIN */
(POST, routes::SET_PERMS_BY_ADMIN) => admin::set_permissions(pool, resp, params).await,
/* MEMBERS */
(GET, routes::GET_ONLINE_MEMBERS) => members::get_online_members(pool, resp).await,
/* OWNER */
(POST, routes::SET_NEW_ADMIN) => admin::new_admin(pool, resp, params).await,
_ => {
// We attempt dynamic routes as fallback for a few reasons
// 1. theres less of these than there are the static routes
// 2. because of the above and that this is wholly more expensive than static routse
// we can justify putting in tons of branches since we're likely to:
// far jump here, lose cache, and still be be network bound
// Computatinoal bounds are really of no concern with this api since
// we're not doing any heavy calculations at any point
if let Some(route) = resolve_dynamic_route(path) {
match (meth, route.base.as_str()) {
(&Method::GET, routes::DYN_JOIN) => invites::join(pool, resp, params).await,
_ => {
println!("\tNOT FOUND: {}: {}", meth, path);
*resp.status_mut() = StatusCode::NOT_FOUND
}
}
}
else {
println!("\tNOT FOUND: {}: {}", meth, path);
*resp.status_mut() = StatusCode::NOT_FOUND
}
}
}
}
async fn main_responder(request: Request<Body>) -> Result<Response<Body>, hyper::Error>{
use AuthReason::*;
let mut response = Response::new(Body::empty());
let (parts, mut body) = request.into_parts();
let method = parts.method;
let path = parts.uri.path();
println!("{}: {}", method, path);
let params_res = http_params::parse_params(&mut body).await;
if let Ok(mut params) = params_res {
let pool = Pool::new(&env::var("DATABASE_URL").unwrap());
if let Ok(auth_result) = auth::wall_entry(path, &pool, &mut params).await {
// Deal with permissions errors at this point
match auth_result {
OpenAuth | Good => route_dispatcher(&pool, &mut response, &method, path, params).await,
NoKey | BadKey => {
println!("\tAUTH: NoKey/BadKey");
*response.status_mut() = StatusCode::UNAUTHORIZED
},
}
}
else {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
}
else {
println!("\tPARSER: Parameter parsing failed");
*response.status_mut() = StatusCode::BAD_REQUEST;
}
Ok(response)
}
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
.expect("Failed to capture ctrl-c signal");
}
async fn start_server(ecode: u16) -> u16 {
println!("Servering on localhost:8888");
let addr = SocketAddr::from(([127,0,0,1], 8888));
let service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(main_responder))
});
let server = Server::bind(&addr).serve(service);
let graceful_shutdown = server.with_graceful_shutdown(shutdown_signal());
if let Err(e) = graceful_shutdown.await {
eprintln!("Server shutdown error: {}", e);
return ecode | SHUTDOWN_ERR;
}
else {
return ecode
}
}
#[tokio::main]
async fn main() -> Result<(), u16>{
let mut main_ret: u16 = 0;
let d_result = dotenv();
// check for a database_url before the override we get from the cmd line
if let Err(_d) = d_result {
if let Err(_e) = env::var("DATABASE_URL") {
main_ret |= CONFIG_ERR;
}
}
let args = App::new("Freechat Server")
.version("0.1")
.author("shockrah")
.about("Decentralized chat system")
.arg(Arg::with_name("db-url")
.short("d")
.long("db-url")
.value_name("DATABASE URL")
.help("Sets the DATABASE URL via an environment variable")
.takes_value(true))
.arg(Arg::with_name("create-owner")
.short("c")
.long("create-owner")
.value_name("Owner")
.help("Creates an account with full permissions in the SQL database."))
.arg(Arg::with_name("server")
.short("s")
.long("server")
.help("Starts the API server"))
.get_matches();
if args.args.len() == 0 {
println!("Freechat Server 0.1
shockrah
Decentralized chat system
USAGE:
freechat-server [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
-s, --server Starts the API server
-V, --version Prints version information
OPTIONS:
-c, --create-owner <Owner> Creates an account with full permissions in the SQL database.
-d, --db-url <DATABASE URL> Sets the DATABASE URL via an environment variable");
}
if let Some(db_url) = args.value_of("db-url") {
set_var("DATABASE_URL", db_url);
}
if let Some(owner_name) = args.value_of("create-owner") {
let p = Pool::new(&env::var("DATABASE_URL").unwrap());
eprintln!("Creating owner {{ {} }}...", owner_name);
let owner_secret = auth::generate_secret();
// creation of owner should just dump straight to stdout since this fires
// from a commandline parameter anyway
if let Ok(enc_secret) = auth::encrypt_secret(&owner_secret) {
match db::member::Member::add(&p, owner_name, &enc_secret, perms::OWNER).await {
Ok(response) => {
match response {
db::Response::Row(owner) =>
println!("{}", serde_json::to_string(&owner).expect("SQL query passed but serde couldn't parse the data for some reason")),
db::Response::Empty => {
eprintln!("SQL server failed to return owner data, check configs and also the members table to make sure there's nothing there by accident");
},
_ => {}
};
},
Err(_) => {
eprintln!("Could not communicate with the SQL server, check your configs!");
}
}
}
else {
eprintln!("Could not generate a proper secret");
}
//println!("{}", serde_json::to_string(&owner).unwrap());
p.disconnect();
}
if args.is_present("server") {
if main_ret == NO_ERR {
main_ret = start_server(main_ret).await;
}
}
if main_ret != 0 {
// dumb as heck loggin method here
if main_ret & CONFIG_ERR != 0 {println!("ERROR: Config was not setup properly => Missing {{DATABASE_URL}}");}
if main_ret & SHUTDOWN_ERR != 0 {println!("ERROR: Couldn't shutdown gracefully");}
Err(main_ret)
}
else {
Ok(())
}
}