router/src/main.rs
2021-02-07 17:10:36 -06:00

342 lines
8.5 KiB
Rust

use blocking::unblock;
use futures_lite::*;
use once_cell::sync::Lazy;
use sled::Db;
use std::time::Duration;
use tide::{
log::{self, LogMiddleware},
sessions::{MemoryStore, Session, SessionMiddleware},
};
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
mod iptables;
mod middleware;
mod rules;
mod session;
mod startup;
mod tunnels;
use self::{
middleware::{AuthMiddleware, UserInfo},
rules::Rule,
startup::{Config, Interfaces, ServerState},
templates::statics::StaticFile,
tunnels::{NewPeer, NewTunnel},
};
static CONFIG: Lazy<Config> = Lazy::new(|| Config::read().unwrap());
static SERVER: Lazy<ServerState> = Lazy::new(|| ServerState::init(&CONFIG).unwrap());
static INTERFACES: Lazy<Interfaces> = Lazy::new(|| {
let interfaces = Interfaces::init_blocking(&CONFIG).unwrap();
if !SERVER.debug {
interfaces.reset_blocking().unwrap();
}
interfaces
});
static DB: Lazy<Db> = Lazy::new(|| sled::open("router-db-0-34-3").unwrap());
static QS: Lazy<serde_qs::Config> = Lazy::new(|| serde_qs::Config::new(3, false));
async fn rules_page(_: tide::Request<()>) -> tide::Result {
let mut html = Vec::new();
let rules = unblock(move || rules::read(&DB)).await?;
templates::rules(&mut html, &rules)?;
Ok(tide::Response::builder(200)
.body(html)
.content_type(
"text/html;charset=utf-8"
.parse::<tide::http::Mime>()
.unwrap(),
)
.build())
}
async fn save_rule(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let rule: Rule = QS.deserialize_str(&body_string)?;
rules::save(&DB, &rule).await?;
if !SERVER.debug {
rules::apply(&INTERFACES, rule).await?;
}
Ok(to_rules_page())
}
async fn delete_rule(req: tide::Request<()>) -> tide::Result {
let id = req.param("id")?;
let rule = rules::delete(&DB, id).await?;
if !SERVER.debug {
rules::unset(&INTERFACES, rule).await?;
}
Ok(to_rules_page())
}
async fn tunnels_page(_: tide::Request<()>) -> tide::Result {
Ok(tide::Response::builder(200).build())
}
async fn save_tunnel(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let new_tunnel: NewTunnel = QS.deserialize_str(&body_string)?;
let tunnel = tunnels::create(new_tunnel).await?;
// TODO: add firewall rule for tunnel
tunnels::save(&DB, &tunnel).await?;
Ok(to_tunnels_page())
}
async fn peers_page(_: tide::Request<()>) -> tide::Result {
Ok(tide::Response::builder(200).build())
}
async fn save_peer(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let new_peer: NewPeer = QS.deserialize_str(&body_string)?;
let peer = tunnels::add_peer(&DB, new_peer).await?;
let _ = peer;
// TODO: add firewall rule for peer
Ok(to_tunnels_page())
}
#[derive(serde::Deserialize)]
struct LoginForm {
username: String,
password: String,
}
async fn login(mut req: tide::Request<()>) -> tide::Result {
tide::log::debug!("LOGIN");
let body_string = req.body_string().await?;
let login_form: LoginForm = QS.deserialize_str(&body_string)?;
session::login(&DB, login_form.username.clone(), login_form.password)?;
let session = req.session_mut();
session.insert(
"user",
UserInfo {
username: login_form.username,
},
)?;
Ok(return_to(session))
}
async fn login_page(req: tide::Request<()>) -> tide::Result {
tide::log::debug!("LOGIN PAGE");
let session = req.session();
if session.get::<UserInfo>("user").is_some() {
tide::log::debug!("TO HOME");
return Ok(to_home());
}
let mut html = Vec::new();
templates::login(&mut html)?;
Ok(tide::Response::builder(200)
.body(html)
.content_type(
"text/html;charset=utf-8"
.parse::<tide::http::Mime>()
.unwrap(),
)
.build())
}
async fn home_page(_: tide::Request<()>) -> tide::Result {
let mut html = Vec::new();
templates::home_html(&mut html, &INTERFACES)?;
Ok(tide::Response::builder(200)
.body(html)
.content_type(
"text/html;charset=utf-8"
.parse::<tide::http::Mime>()
.unwrap(),
)
.build())
}
async fn account_page(mut req: tide::Request<()>) -> tide::Result {
let mut html = Vec::new();
let path = req.url().path().to_string();
let session = req.session_mut();
let user_info = if let Some(user_info) = session.get::<UserInfo>("user") {
user_info
} else {
return to_login(session, &path);
};
templates::account_html(&mut html, &user_info)?;
Ok(tide::Response::builder(200)
.body(html)
.content_type(
"text/html;charset=utf-8"
.parse::<tide::http::Mime>()
.unwrap(),
)
.build())
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
struct UpdateAccountForm {
username: String,
new_password: String,
confirm_new_password: String,
password: String,
}
async fn update_account(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let form: UpdateAccountForm = QS.deserialize_str(&body_string)?;
if form.new_password != form.confirm_new_password {
return Err(anyhow::anyhow!("Passwords do not match").into());
}
session::verify(&DB, &form.username, form.password).await?;
session::update_password(&DB, form.username, form.new_password).await?;
Ok(to_account())
}
fn return_to(session: &mut Session) -> tide::Response {
if let Some(path) = session.get("return_to") {
let path: String = path; // tell the compiler what type i want it to be
session.remove("return_to");
to_custom(&path)
} else {
to_home()
}
}
fn to_custom(location: &str) -> tide::Response {
tide::Response::builder(302)
.header("Location", location)
.header("Cache-Control", "no-store")
.build()
}
fn to_account() -> tide::Response {
to_custom("/account")
}
fn to_home() -> tide::Response {
to_custom("/")
}
fn to_login(session: &mut Session, from: &str) -> tide::Result {
session.insert("return_to", from)?;
Ok(to_custom("/login"))
}
fn to_rules_page() -> tide::Response {
to_custom("/rules")
}
fn to_tunnels_page() -> tide::Response {
to_custom("/tunnnels")
}
async fn statics(req: tide::Request<()>) -> tide::Result {
let file: &str = req.param("file")?;
if let Some(data) = StaticFile::get(file) {
Ok(tide::Response::builder(200)
.header("Content-Type", data.mime.to_string())
.body(data.content)
.build())
} else {
Ok(tide::Response::builder(404).build())
}
}
fn main() -> Result<(), anyhow::Error> {
future::block_on(async {
println!("Hello, world!");
log::with_level(log::LevelFilter::Debug);
session::create_admin(&DB).await?;
if !SERVER.debug {
rules::apply_all(&DB, &INTERFACES).await?;
}
let mut app = tide::new();
app.with(
SessionMiddleware::new(MemoryStore::new(), &SERVER.secret)
.with_cookie_name("router.cookie")
.with_session_ttl(Some(Duration::from_secs(60 * 15))),
);
app.with(LogMiddleware::new());
app.at("/static/:file").get(statics);
app.at("/login").get(login_page).post(login);
app.at("/account").get(account_page).post(update_account);
app.at("/tunnels")
.with(AuthMiddleware)
.get(tunnels_page)
.post(save_tunnel)
.nest({
let mut app = tide::new();
app.at("/:id/peers").get(peers_page).post(save_peer);
app
});
app.at("/rules")
.with(AuthMiddleware)
.get(rules_page)
.post(save_rule)
.nest({
let mut app = tide::new();
app.at("/:id").get(delete_rule);
app
});
app.at("/").get(home_page);
if SERVER.debug {
app.listen("127.0.0.1:8080").await?;
} else {
let listeners: Vec<String> = INTERFACES
.internal
.iter()
.map(|info| format!("{}:80", info.ip))
.collect();
app.listen(listeners).await?;
}
Ok(()) as Result<(), anyhow::Error>
})?;
Ok(())
}