Authentication, more styles
This commit is contained in:
parent
ab42817c2b
commit
8d3b3eb23c
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/router-db-0-34-3
|
||||||
|
|
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -32,7 +32,7 @@ checksum = "f7001367fde4c768a19d1029f0a8be5abd9308e1119846d5bd9ad26297b8faf5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-soft",
|
"aes-soft",
|
||||||
"aesni",
|
"aesni",
|
||||||
"block-cipher",
|
"block-cipher 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -43,7 +43,7 @@ checksum = "86f5007801316299f922a6198d1d09a0bae95786815d066d5880d13f7c45ead1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aead",
|
"aead",
|
||||||
"aes",
|
"aes",
|
||||||
"block-cipher",
|
"block-cipher 0.7.1",
|
||||||
"ghash",
|
"ghash",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
@ -54,7 +54,7 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7"
|
checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-cipher",
|
"block-cipher 0.7.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"opaque-debug 0.2.3",
|
"opaque-debug 0.2.3",
|
||||||
]
|
]
|
||||||
|
@ -65,7 +65,7 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264"
|
checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-cipher",
|
"block-cipher 0.7.1",
|
||||||
"opaque-debug 0.2.3",
|
"opaque-debug 0.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -327,6 +327,18 @@ version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2cab630912253fb9dc92c0e2fabd0a7b51f5a5a4007177cfa31e517015b7204"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"blowfish",
|
||||||
|
"byteorder",
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -376,6 +388,15 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-cipher"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -403,6 +424,17 @@ dependencies = [
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blowfish"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f06850ba969bc59388b2cc0a4f186fc6d9d37208863b15b84ae3866ac90ac06"
|
||||||
|
dependencies = [
|
||||||
|
"block-cipher 0.8.0",
|
||||||
|
"byteorder",
|
||||||
|
"opaque-debug 0.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
|
@ -1360,11 +1392,15 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-process",
|
"async-process",
|
||||||
|
"async-trait",
|
||||||
|
"base64",
|
||||||
|
"bcrypt",
|
||||||
"blocking 1.0.0",
|
"blocking 1.0.0",
|
||||||
"config",
|
"config",
|
||||||
"futures-lite 1.3.0",
|
"futures-lite 1.3.0",
|
||||||
"mime",
|
"mime",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"ructe",
|
"ructe",
|
||||||
"serde 1.0.115",
|
"serde 1.0.115",
|
||||||
|
|
|
@ -10,11 +10,15 @@ build = "src/build.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-process = "1.0.0"
|
async-process = "1.0.0"
|
||||||
|
async-trait = "0.1.40"
|
||||||
|
base64 = "0.12.3"
|
||||||
|
bcrypt = "0.8.2"
|
||||||
blocking = "1.0.0"
|
blocking = "1.0.0"
|
||||||
config = { version = "0.10.1", features = ["toml"] }
|
config = { version = "0.10.1", features = ["toml"] }
|
||||||
futures-lite = "1.1.0"
|
futures-lite = "1.1.0"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
once_cell = "1.4.1"
|
once_cell = "1.4.1"
|
||||||
|
rand = "0.7.3"
|
||||||
regex = "1.3.9"
|
regex = "1.3.9"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
@ -6,3 +6,7 @@ internal = [
|
||||||
|
|
||||||
[network]
|
[network]
|
||||||
shared-internal = true
|
shared-internal = true
|
||||||
|
|
||||||
|
[server]
|
||||||
|
debug = true
|
||||||
|
secret = "8tr5C1kCmWpce9Exk2he+g0C72juZdxvxF+xKrRyeBFhVzYbJmxIxcE0ND4nP3il"
|
||||||
|
|
108
scss/index.scss
108
scss/index.scss
|
@ -1,3 +1,7 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
@ -27,6 +31,13 @@ section {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -81,6 +92,97 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&, &:focus, &:hover, &:visited {
|
||||||
|
color: #dd08dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #dd08dd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 150px;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: '▾';
|
||||||
|
top: 7px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 8px 24px 8px 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid #eae;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid #eae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: #fff3fc;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background-color: #fff3fc;
|
||||||
|
box-shadow: 0 1px 3px #9d0f9d1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
padding: 8px 24px;
|
||||||
|
background: #992d99;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #b034b0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="password"] {
|
||||||
|
width: 150px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 8px;
|
||||||
|
box-shadow: inset 0 0 3px #9d0f9d1f;
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid #eae;
|
||||||
|
box-shadow: inset 0 0 3px #9d0f9d1f, 0 1px 3px #9d0f9d1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: 650px) {
|
@media(max-width: 650px) {
|
||||||
body {
|
body {
|
||||||
padding: 8px 0 48px;
|
padding: 8px 0 48px;
|
||||||
|
@ -100,9 +202,13 @@ form {
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
table, form {
|
.content, .table-wrapper, form {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
160
src/main.rs
160
src/main.rs
|
@ -2,19 +2,36 @@ use blocking::unblock;
|
||||||
use futures_lite::*;
|
use futures_lite::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use sled::Db;
|
use sled::Db;
|
||||||
use tide::log::{self, LogMiddleware};
|
use std::time::Duration;
|
||||||
|
use tide::{
|
||||||
|
log::{self, LogMiddleware},
|
||||||
|
sessions::{MemoryStore, Session, SessionMiddleware},
|
||||||
|
};
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
||||||
|
|
||||||
mod iptables;
|
mod iptables;
|
||||||
|
mod middleware;
|
||||||
mod rules;
|
mod rules;
|
||||||
|
mod session;
|
||||||
mod startup;
|
mod startup;
|
||||||
|
|
||||||
use self::{rules::Rule, startup::Interfaces, templates::statics::StaticFile};
|
use self::{
|
||||||
|
middleware::AuthMiddleware,
|
||||||
|
rules::Rule,
|
||||||
|
startup::{Config, Interfaces, ServerState},
|
||||||
|
templates::statics::StaticFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
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(|| {
|
static INTERFACES: Lazy<Interfaces> = Lazy::new(|| {
|
||||||
let interfaces = Interfaces::init_blocking().unwrap();
|
let interfaces = Interfaces::init_blocking(&CONFIG).unwrap();
|
||||||
interfaces.reset_blocking().unwrap();
|
if !SERVER.debug {
|
||||||
|
interfaces.reset_blocking().unwrap();
|
||||||
|
}
|
||||||
interfaces
|
interfaces
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +63,9 @@ async fn save_rule(mut req: tide::Request<()>) -> tide::Result {
|
||||||
|
|
||||||
rules::save(&DB, &rule).await?;
|
rules::save(&DB, &rule).await?;
|
||||||
|
|
||||||
rules::apply(&INTERFACES, rule).await?;
|
if !SERVER.debug {
|
||||||
|
rules::apply(&INTERFACES, rule).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(to_rules_page())
|
Ok(to_rules_page())
|
||||||
}
|
}
|
||||||
|
@ -54,17 +73,101 @@ async fn save_rule(mut req: tide::Request<()>) -> tide::Result {
|
||||||
async fn delete_rule(req: tide::Request<()>) -> tide::Result {
|
async fn delete_rule(req: tide::Request<()>) -> tide::Result {
|
||||||
let id = req.param("id")?;
|
let id = req.param("id")?;
|
||||||
let rule = rules::delete(&DB, id).await?;
|
let rule = rules::delete(&DB, id).await?;
|
||||||
rules::unset(&INTERFACES, rule).await?;
|
|
||||||
|
if !SERVER.debug {
|
||||||
|
rules::unset(&INTERFACES, rule).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(to_rules_page())
|
Ok(to_rules_page())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_rules_page() -> tide::Response {
|
#[derive(serde::Deserialize)]
|
||||||
tide::Response::builder(301)
|
struct LoginForm {
|
||||||
.header("Location", "/rules")
|
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, login_form.password)?;
|
||||||
|
|
||||||
|
let session = req.session_mut();
|
||||||
|
|
||||||
|
session.insert("logged_in", true)?;
|
||||||
|
|
||||||
|
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");
|
||||||
|
Ok(to_custom(&path))
|
||||||
|
} else {
|
||||||
|
Ok(to_home())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login_page(req: tide::Request<()>) -> tide::Result {
|
||||||
|
tide::log::debug!("LOGIN PAGE");
|
||||||
|
let session = req.session();
|
||||||
|
let logged_in: bool = session.get("logged_in").unwrap_or(false);
|
||||||
|
|
||||||
|
if logged_in {
|
||||||
|
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.external.ip.to_string())?;
|
||||||
|
|
||||||
|
Ok(tide::Response::builder(200)
|
||||||
|
.body(html)
|
||||||
|
.content_type(
|
||||||
|
"text/html;charset=utf-8"
|
||||||
|
.parse::<tide::http::Mime>()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_custom(location: &str) -> tide::Response {
|
||||||
|
tide::Response::builder(302)
|
||||||
|
.header("Location", location)
|
||||||
|
.header("Cache-Control", "no-store")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
async fn statics(req: tide::Request<()>) -> tide::Result {
|
async fn statics(req: tide::Request<()>) -> tide::Result {
|
||||||
let file: String = req.param("file")?;
|
let file: String = req.param("file")?;
|
||||||
|
|
||||||
|
@ -84,21 +187,42 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
log::with_level(log::LevelFilter::Debug);
|
log::with_level(log::LevelFilter::Debug);
|
||||||
|
|
||||||
rules::apply_all(&DB, &INTERFACES).await?;
|
session::create_admin(&DB).await?;
|
||||||
|
if !SERVER.debug {
|
||||||
|
rules::apply_all(&DB, &INTERFACES).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut app = tide::new();
|
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.with(LogMiddleware::new());
|
||||||
app.at("/static/:file").get(statics);
|
app.at("/static/:file").get(statics);
|
||||||
app.at("/rules").get(rules_page).post(save_rule);
|
app.at("/login").get(login_page).post(login);
|
||||||
app.at("/rules/:id").get(delete_rule);
|
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);
|
||||||
|
|
||||||
let listeners: Vec<String> = INTERFACES
|
if SERVER.debug {
|
||||||
.internal
|
app.listen("127.0.0.1:8080").await?;
|
||||||
.iter()
|
} else {
|
||||||
.map(|info| format!("{}:8080", info.ip))
|
let listeners: Vec<String> = INTERFACES
|
||||||
.collect();
|
.internal
|
||||||
|
.iter()
|
||||||
|
.map(|info| format!("{}:80", info.ip))
|
||||||
|
.collect();
|
||||||
|
|
||||||
app.listen(listeners).await?;
|
app.listen(listeners).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(()) as Result<(), anyhow::Error>
|
Ok(()) as Result<(), anyhow::Error>
|
||||||
})?;
|
})?;
|
||||||
|
|
22
src/middleware.rs
Normal file
22
src/middleware.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::to_login;
|
||||||
|
use tide::{Middleware, Next, Request};
|
||||||
|
|
||||||
|
pub(crate) struct AuthMiddleware;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<State> Middleware<State> for AuthMiddleware
|
||||||
|
where
|
||||||
|
State: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn handle(&self, mut request: Request<State>, next: Next<'_, State>) -> tide::Result {
|
||||||
|
let path = request.url().path().to_string();
|
||||||
|
let session = request.session_mut();
|
||||||
|
let logged_in: bool = session.get("logged_in").unwrap_or(false);
|
||||||
|
|
||||||
|
if logged_in {
|
||||||
|
return Ok(next.run(request).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
to_login(session, &path)
|
||||||
|
}
|
||||||
|
}
|
57
src/session.rs
Normal file
57
src/session.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use sled::{Db, Tree};
|
||||||
|
|
||||||
|
static USER_TREE: OnceCell<Tree> = OnceCell::new();
|
||||||
|
|
||||||
|
fn user_tree(db: &Db) -> &'static Tree {
|
||||||
|
USER_TREE.get_or_init(|| db.open_tree("users").unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn login(db: &Db, username: String, password: String) -> Result<(), anyhow::Error> {
|
||||||
|
let tree = user_tree(db);
|
||||||
|
|
||||||
|
let hash = tree
|
||||||
|
.get(username.as_bytes())?
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Missing user {}", username))?;
|
||||||
|
|
||||||
|
let password_hash = String::from_utf8_lossy(&hash);
|
||||||
|
|
||||||
|
if bcrypt::verify(password, &password_hash)? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow::anyhow!("Invalid password"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_user(db: &Db, username: String, password: String) -> Result<(), anyhow::Error> {
|
||||||
|
let tree = user_tree(db);
|
||||||
|
|
||||||
|
let hash = bcrypt::hash(&password, bcrypt::DEFAULT_COST)?;
|
||||||
|
|
||||||
|
if tree
|
||||||
|
.compare_and_swap(username, None as Option<&[u8]>, Some(hash.as_bytes()))?
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(anyhow::anyhow!("User already exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.flush_async().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn create_admin(db: &Db) -> Result<(), anyhow::Error> {
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
let password = rand::thread_rng()
|
||||||
|
.sample_iter(rand::distributions::Alphanumeric)
|
||||||
|
.take(16)
|
||||||
|
.collect::<String>();
|
||||||
|
if add_user(db, String::from("admin"), password.clone())
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
println!("Admin password is '{}'", password);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -9,9 +9,10 @@ use std::{
|
||||||
mod preload;
|
mod preload;
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Config {
|
pub(crate) struct Config {
|
||||||
interface: InterfaceConfig,
|
interface: InterfaceConfig,
|
||||||
network: NetworkConfig,
|
network: NetworkConfig,
|
||||||
|
server: ServerConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
@ -26,6 +27,12 @@ struct NetworkConfig {
|
||||||
shared_internal: bool,
|
shared_internal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct ServerConfig {
|
||||||
|
debug: bool,
|
||||||
|
secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct Interfaces {
|
pub(crate) struct Interfaces {
|
||||||
pub(crate) external: InterfaceInfo,
|
pub(crate) external: InterfaceInfo,
|
||||||
pub(crate) internal: Vec<InterfaceInfo>,
|
pub(crate) internal: Vec<InterfaceInfo>,
|
||||||
|
@ -38,8 +45,24 @@ pub(crate) struct InterfaceInfo {
|
||||||
pub(crate) mask: u8,
|
pub(crate) mask: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ServerState {
|
||||||
|
pub(crate) debug: bool,
|
||||||
|
pub(crate) secret: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerState {
|
||||||
|
pub(crate) fn init(config: &Config) -> Result<Self, anyhow::Error> {
|
||||||
|
let secret = base64::decode(&config.server.secret)?;
|
||||||
|
|
||||||
|
Ok(ServerState {
|
||||||
|
debug: config.server.debug,
|
||||||
|
secret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
fn read() -> Result<Self, anyhow::Error> {
|
pub(crate) fn read() -> Result<Self, anyhow::Error> {
|
||||||
let mut config = config::Config::new();
|
let mut config = config::Config::new();
|
||||||
config.merge(config::File::with_name("config.toml"))?;
|
config.merge(config::File::with_name("config.toml"))?;
|
||||||
|
|
||||||
|
@ -48,8 +71,23 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interfaces {
|
impl Interfaces {
|
||||||
pub(crate) fn init_blocking() -> Result<Self, anyhow::Error> {
|
pub(crate) fn init_blocking(config: &Config) -> Result<Self, anyhow::Error> {
|
||||||
let config = Config::read()?;
|
if config.server.debug {
|
||||||
|
// Fake Data from Fake Things for Faking :tm:
|
||||||
|
return Ok(Interfaces {
|
||||||
|
external: InterfaceInfo {
|
||||||
|
interface: String::from("eth0"),
|
||||||
|
ip: "123.123.123.123".parse()?,
|
||||||
|
mask: 20,
|
||||||
|
},
|
||||||
|
internal: vec![InterfaceInfo {
|
||||||
|
interface: String::from("enp1s0"),
|
||||||
|
ip: "192.168.6.1".parse()?,
|
||||||
|
mask: 24,
|
||||||
|
}],
|
||||||
|
shared_internal: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let output = Command::new("ip").arg("addr").output()?;
|
let output = Command::new("ip").arg("addr").output()?;
|
||||||
let output = String::from_utf8_lossy(&output.stdout);
|
let output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
24
templates/home.rs.html
Normal file
24
templates/home.rs.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
@use crate::templates::layout_html;
|
||||||
|
|
||||||
|
@(ip: String)
|
||||||
|
|
||||||
|
@:layout_html("Router", {
|
||||||
|
<section>
|
||||||
|
<h1>Home</h1>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h4>Info</h4>
|
||||||
|
<div class="content">
|
||||||
|
<p>IP: @ip</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h4>Admin</h4>
|
||||||
|
<nav class="content">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/rules">Add traffic rules</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
})
|
||||||
|
|
17
templates/layout.rs.html
Normal file
17
templates/layout.rs.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@use crate::templates::statics::index_css;
|
||||||
|
|
||||||
|
@(title: &str, content: Content)
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>@title</title>
|
||||||
|
<link rel="stylesheet" href="/static/@index_css.name" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@:content()
|
||||||
|
</body>
|
||||||
|
</html>
|
27
templates/login.rs.html
Normal file
27
templates/login.rs.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@use crate::templates::{layout_html, return_home_html};
|
||||||
|
|
||||||
|
@()
|
||||||
|
|
||||||
|
@:layout_html("Login", {
|
||||||
|
<section>
|
||||||
|
<h1>Login</h1>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
<div class="form-body">
|
||||||
|
<label for="username">
|
||||||
|
<h4>Username</h4>
|
||||||
|
<input name="username" type="text" />
|
||||||
|
</label>
|
||||||
|
<label for="password">
|
||||||
|
<h4>Password</h4>
|
||||||
|
<input name="password" type="password" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="submit">
|
||||||
|
<button type="submit">Login!</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
@:return_home_html()
|
||||||
|
})
|
7
templates/return_home.rs.html
Normal file
7
templates/return_home.rs.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@()
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="content">
|
||||||
|
<a href="/">Return Home</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -1,102 +1,103 @@
|
||||||
@use crate::{
|
@use crate::{
|
||||||
rules::Rule,
|
rules::Rule,
|
||||||
templates::statics::index_css,
|
templates::{layout_html, return_home_html},
|
||||||
};
|
};
|
||||||
|
|
||||||
@(rules: &[(String, Rule)])
|
@(rules: &[(String, Rule)])
|
||||||
|
|
||||||
<!DOCTYPE html>
|
@:layout_html("Rules", {
|
||||||
|
<section>
|
||||||
<html lang="en">
|
<h1>Rules</h1>
|
||||||
<head>
|
</section>
|
||||||
<meta charset="utf-8" />
|
<section>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<div class="table-wrapper">
|
||||||
<title>Rules</title>
|
<table>
|
||||||
<link rel="stylesheet" href="/static/@index_css.name" type="text/css" />
|
<thead>
|
||||||
</head>
|
<th>Kind</th>
|
||||||
<body>
|
<th>Protocol</th>
|
||||||
<section>
|
<th class="port">Port</th>
|
||||||
<h1>Rules</h1>
|
<th>Destination IP</th>
|
||||||
</section>
|
<th class="port">Destination Port</th>
|
||||||
<section>
|
<th></th>
|
||||||
<div class="table-wrapper">
|
</thead>
|
||||||
<table>
|
</tbody>
|
||||||
<thead>
|
@for (id, rule) in rules {
|
||||||
<th>Kind</th>
|
<tr>
|
||||||
<th>Protocol</th>
|
@if let Some((dest_ip, dest_port)) = rule.as_forward() {
|
||||||
<th class="port">Port</th>
|
<td>Forward</td>
|
||||||
<th>Destination IP</th>
|
<td>@rule.proto</td>
|
||||||
<th class="port">Destination Port</th>
|
<td class="port">@rule.port</td>
|
||||||
<th></th>
|
<td>@dest_ip</td>
|
||||||
</thead>
|
<td class="port">@dest_port</td>
|
||||||
</tbody>
|
} else {
|
||||||
@for (id, rule) in rules {
|
<td>Accept</td>
|
||||||
<tr>
|
<td>@rule.proto</td>
|
||||||
@if let Some((dest_ip, dest_port)) = rule.as_forward() {
|
<td class="port">@rule.port</td>
|
||||||
<td>Forward</td>
|
<td></td>
|
||||||
<td>@rule.proto</td>
|
<td></td>
|
||||||
<td class="port">@rule.port</td>
|
}
|
||||||
<td>@dest_ip</td>
|
<td class="delete"><a href="/rules/@id">Delete</a></td>
|
||||||
<td class="port">@dest_port</td>
|
</tr>
|
||||||
} else {
|
}
|
||||||
<td>Accept</td>
|
</tbody>
|
||||||
<td>@rule.proto</td>
|
</table>
|
||||||
<td class="port">@rule.port</td>
|
</div>
|
||||||
<td></td>
|
</section>
|
||||||
<td></td>
|
<section>
|
||||||
}
|
<h4>Forward Port</h4>
|
||||||
<td class="delete"><a href="/rules/@id">Delete</a></td>
|
<form method="POST" action="/rules">
|
||||||
</tr>
|
<div class="form-body">
|
||||||
}
|
<label for="proto">
|
||||||
</tbody>
|
<h4>Protocol</h4>
|
||||||
</table>
|
<div class="select-wrapper">
|
||||||
|
<select name="proto">
|
||||||
|
<option value="Tcp">TCP</option>
|
||||||
|
<option value="Udp">UDP</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label for="port">
|
||||||
|
<h4>External Port</h4>
|
||||||
|
<input name="port" type="number" />
|
||||||
|
</label>
|
||||||
|
<input name="kind[type]" value="Forward" type="hidden" />
|
||||||
|
<label for="kind[dest_port]">
|
||||||
|
<h4>Internal Port</h4>
|
||||||
|
<input name="kind[dest_port]" type="number" />
|
||||||
|
</label>
|
||||||
|
<label for="kind[dest_ip]">
|
||||||
|
<h4>IP Address</h4>
|
||||||
|
<input name="kind[dest_ip]" type="text" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="submit">
|
||||||
<section>
|
<button type="submit">Forward!</button>
|
||||||
<h4>Forward Port</h4>
|
</div>
|
||||||
<form method="POST" action="/rules">
|
</form>
|
||||||
<div class="form-body">
|
</section>
|
||||||
<select name="proto">
|
<section>
|
||||||
<option value="Tcp">TCP</option>
|
<h4>Accept Port</h4>
|
||||||
<option value="Udp">UDP</option>
|
<form method="POST" action="/rules">
|
||||||
</select>
|
<div class="form-body">
|
||||||
<label for="port">
|
<label for="proto">
|
||||||
<h4>External Port</h4>
|
<h4>Protocol</h4>
|
||||||
<input name="port" type="number" />
|
<div class="select-wrapper">
|
||||||
</label>
|
<select name="proto">
|
||||||
<input name="kind[type]" value="Forward" type="hidden" />
|
<option value="Tcp">TCP</option>
|
||||||
<label for="kind[dest_port]">
|
<option value="Udp">UDP</option>
|
||||||
<h4>Internal Port</h4>
|
</select>
|
||||||
<input name="kind[dest_port]" type="number" />
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label for="kind[dest_ip]">
|
<label for="port">
|
||||||
<h4>IP Address</h4>
|
<h4>External Port</h4>
|
||||||
<input name="kind[dest_ip]" type="text" />
|
<input name="port" type="number" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
<input name="kind[type]" value="Accept" type="hidden" />
|
||||||
<div class="submit">
|
</div>
|
||||||
<button type="submit">Forward!</button>
|
<div class="submit">
|
||||||
</div>
|
<button type="submit">Accept!</button>
|
||||||
</form>
|
</div>
|
||||||
</section>
|
</form>
|
||||||
<section>
|
</section>
|
||||||
<h4>Accept Port</h4>
|
@:return_home_html()
|
||||||
<form method="POST" action="/rules">
|
})
|
||||||
<div class="form-body">
|
|
||||||
<select name="proto">
|
|
||||||
<option value="Tcp">TCP</option>
|
|
||||||
<option value="Udp">UDP</option>
|
|
||||||
</select>
|
|
||||||
<label for="port">
|
|
||||||
<h4>External Port</h4>
|
|
||||||
<input name="port" type="number" />
|
|
||||||
</label>
|
|
||||||
<input name="kind[type]" value="Accept" type="hidden" />
|
|
||||||
</div>
|
|
||||||
<div class="submit">
|
|
||||||
<button type="submit">Accept!</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
Loading…
Reference in a new issue