342 lines
8.5 KiB
Rust
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(())
|
|
}
|