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 = Lazy::new(|| Config::read().unwrap()); static SERVER: Lazy = Lazy::new(|| ServerState::init(&CONFIG).unwrap()); static INTERFACES: Lazy = Lazy::new(|| { let interfaces = Interfaces::init_blocking(&CONFIG).unwrap(); if !SERVER.debug { interfaces.reset_blocking().unwrap(); } interfaces }); static DB: Lazy = Lazy::new(|| sled::open("router-db-0-34-3").unwrap()); static QS: Lazy = 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::() .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::("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::() .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::() .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::("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::() .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 = INTERFACES .internal .iter() .map(|info| format!("{}:80", info.ip)) .collect(); app.listen(listeners).await?; } Ok(()) as Result<(), anyhow::Error> })?; Ok(()) }