aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2022-07-20 10:31:01 -0400
committerCara Salter <cara@devcara.com>2022-07-20 10:31:01 -0400
commit4b616447715b8129ae322341959e1c2bfabbd10e (patch)
tree01c23c8a5e29d2821cdd1047d0ec27a69f8b1138
parentb98646d8501689072f5624483d258adcbf6fc5c5 (diff)
downloadnccd-4b616447715b8129ae322341959e1c2bfabbd10e.tar.gz
nccd-4b616447715b8129ae322341959e1c2bfabbd10e.zip
emails
also subnet storage
-rw-r--r--Cargo.lock260
-rw-r--r--Cargo.toml2
-rw-r--r--src/config.rs13
-rw-r--r--src/errors.rs10
-rw-r--r--src/handlers/auth.rs12
-rw-r--r--src/handlers/mod.rs10
-rw-r--r--src/handlers/nets.rs50
-rw-r--r--src/main.rs15
-rw-r--r--src/models.rs20
-rw-r--r--src/utils.rs51
-rw-r--r--templates/index.rs.html63
-rw-r--r--templates/new_net.rs.html21
12 files changed, 517 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6be56e3..9010c78 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -440,6 +440,22 @@ dependencies = [
]
[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
name = "cpufeatures"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -575,6 +591,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]]
+name = "email-encoding"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34dd14c63662e0206599796cd5e1ad0268ab2b9d19b868d6050d688eba2bbf98"
+dependencies = [
+ "base64",
+ "memchr",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8684b7c9cb4857dfa1e5b9629ef584ba618c9b93bae60f58cb23f4f271d0468e"
+
+[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -606,6 +638,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -643,6 +690,12 @@ dependencies = [
]
[[package]]
+name = "futures-io"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
+
+[[package]]
name = "futures-sink"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -661,10 +714,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
+ "futures-io",
"futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"pin-utils",
+ "slab",
]
[[package]]
@@ -794,6 +850,17 @@ dependencies = [
]
[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi",
+]
+
+[[package]]
name = "http"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -909,6 +976,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127"
[[package]]
+name = "ipnetwork"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -939,6 +1015,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
+name = "lettre"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eabca5e0b4d0e98e7f2243fb5b7520b6af2b65d8f87bcc86f2c75185a6ff243"
+dependencies = [
+ "async-trait",
+ "base64",
+ "email-encoding",
+ "email_address",
+ "fastrand",
+ "futures-io",
+ "futures-util",
+ "hostname",
+ "httpdate",
+ "idna",
+ "mime",
+ "native-tls",
+ "nom",
+ "once_cell",
+ "quoted_printable",
+ "socket2",
+ "tokio",
+ "tokio-native-tls",
+ "tracing",
+]
+
+[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -964,6 +1067,12 @@ dependencies = [
]
[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1049,6 +1158,24 @@ dependencies = [
]
[[package]]
+name = "native-tls"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
name = "nccd"
version = "0.1.0"
dependencies = [
@@ -1060,6 +1187,8 @@ dependencies = [
"chrono",
"color-eyre",
"hyper",
+ "ipnetwork 0.20.0",
+ "lettre",
"ructe",
"serde",
"sqlx",
@@ -1176,6 +1305,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
+name = "openssl"
+version = "0.10.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "owo-colors"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1274,6 +1448,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "pkg-config"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+
+[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1310,6 +1490,12 @@ dependencies = [
]
[[package]]
+name = "quoted_printable"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f"
+
+[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1384,6 +1570,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1462,6 +1657,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
+name = "schannel"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+dependencies = [
+ "lazy_static",
+ "windows-sys",
+]
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1478,6 +1683,29 @@ dependencies = [
]
[[package]]
+name = "security-framework"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "serde"
version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1649,7 +1877,7 @@ dependencies = [
"hkdf",
"hmac 0.12.1",
"indexmap",
- "ipnetwork",
+ "ipnetwork 0.19.0",
"itoa",
"libc",
"log",
@@ -1740,6 +1968,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1845,6 +2087,16 @@ dependencies = [
]
[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2115,6 +2367,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index b055fd1..60187f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,8 @@ chrono = "0.4.19"
ulid = "0.6.0"
async-session = "3.0.0"
bcrypt = "0.13.0"
+ipnetwork = "0.20.0"
+lettre = { version = "0.10.1", features = ["tokio1", "tracing", "tokio1-native-tls"] }
[dependencies.sqlx]
version = "0.6"
diff --git a/src/config.rs b/src/config.rs
index 4ceba75..c153085 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -9,6 +9,7 @@ use std::str;
pub struct Config {
pub server: ServerConfig,
pub database: DbConfig,
+ pub email: EmailConfig,
}
#[derive(Deserialize)]
@@ -23,6 +24,18 @@ pub struct DbConfig {
pub max_connections: u32,
}
+#[derive(Deserialize)]
+pub struct EmailConfig {
+ pub smtp_server: String,
+ pub smtp_port: u16,
+ pub smtp_tls: bool,
+ pub smtp_starttls: bool,
+ pub smtp_username: Option<String>,
+ pub smtp_password: Option<String>,
+ pub email_from: String,
+ pub email_helo: String,
+}
+
impl Config {
pub fn init(path: String) -> Result<Config, ServiceError> {
if let Ok(c) = fs::read(path) {
diff --git a/src/errors.rs b/src/errors.rs
index 23ad8fa..dcc0ae8 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -19,6 +19,10 @@ pub enum ServiceError {
Sql(#[from] sqlx::Error),
#[error("Bcrypt: {0}")]
Bcrypt(#[from] bcrypt::BcryptError),
+ #[error("Parse Error: {0}")]
+ Parse(String),
+ #[error("Email: {0}")]
+ Email(String),
}
pub type StringResult<T = String> = Result<T, ServiceError>;
@@ -37,3 +41,9 @@ impl IntoResponse for ServiceError {
Response::builder().status(status).body(body).unwrap()
}
}
+
+impl From<ipnetwork::IpNetworkError> for ServiceError {
+ fn from(e: ipnetwork::IpNetworkError) -> Self {
+ Self::Parse(e.to_string())
+ }
+}
diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs
index c00fb8d..ab72bc8 100644
--- a/src/handlers/auth.rs
+++ b/src/handlers/auth.rs
@@ -82,6 +82,18 @@ pub async fn register_post(Form(reg): Form<RegisterForm>, state: Extension<Arc<S
Ok(Redirect::to("/dash/auth/login"))
}
+pub async fn logout_post(jar: PrivateCookieJar) -> Result<(PrivateCookieJar, Redirect), ServiceError> {
+ if let Some(id) = jar.get("user-id") {
+ debug!("Found user {}", id);
+
+ let updated_jar = jar.remove(id);
+
+ Ok((updated_jar, Redirect::to("/dash/auth/login")))
+ } else {
+ Ok((jar, Redirect::to("/")))
+ }
+}
+
#[instrument]
pub async fn get_user_or_403(jar: PrivateCookieJar, conn: &mut PoolConnection<Postgres>) -> Result<DbUser, ServiceError> {
debug!("Starting middleware get_user_or_403");
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index b83d83c..24db540 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -1,11 +1,13 @@
-use axum::{Router, routing::get};
+use axum::{Router, routing::{get, post}};
pub mod auth;
+mod nets;
pub async fn gen_routers() -> Router {
Router::new()
.nest("/auth", auth_routes().await)
+ .nest("/nets", net_routes().await)
}
async fn auth_routes() -> Router {
@@ -13,4 +15,10 @@ async fn auth_routes() -> Router {
Router::new()
.route("/login", get(auth::login).post(auth::login_post))
.route("/register", get(auth::register).post(auth::register_post))
+ .route("/logout", post(auth::logout_post))
+}
+
+async fn net_routes() -> Router {
+ Router::new()
+ .route("/new", get(nets::new).post(nets::new_post))
}
diff --git a/src/handlers/nets.rs b/src/handlers/nets.rs
new file mode 100644
index 0000000..6010787
--- /dev/null
+++ b/src/handlers/nets.rs
@@ -0,0 +1,50 @@
+use std::sync::Arc;
+
+use axum::{Extension, Form};
+use axum::response::{Html, IntoResponse, Redirect};
+use axum_extra::extract::PrivateCookieJar;
+use sqlx::query;
+use sqlx::types::ipnetwork::IpNetwork;
+use serde::Deserialize;
+
+use crate::State;
+
+use crate::errors::{HtmlResult, ServiceError};
+
+use super::auth::get_user_or_403;
+
+#[derive(Deserialize)]
+pub struct NewNetForm {
+ pub subnet: String,
+ pub description: String,
+}
+
+pub async fn new(jar: PrivateCookieJar, state: Extension<Arc<State>>) -> Result<Html<Vec<u8>>, ServiceError> {
+ let mut conn = state.conn.acquire().await?;
+
+ let _ = get_user_or_403(jar, &mut conn).await?;
+
+ let mut buf = Vec::new();
+ crate::templates::new_net_html(&mut buf).unwrap();
+
+ Ok(Html(buf))
+}
+
+pub async fn new_post(Form(new): Form<NewNetForm>, jar: PrivateCookieJar, state: Extension<Arc<State>>) -> Result<Redirect, ServiceError> {
+ let mut conn = state.conn.acquire().await?;
+
+ let _ = get_user_or_403(jar, &mut conn).await?;
+
+ let id = ulid::Ulid::new();
+
+ let cidr: IpNetwork = match new.subnet.parse() {
+ Ok(c) => c,
+ Err(e) => {
+ return Err(ServiceError::Parse(e.to_string()));
+ }
+ };
+
+ query("INSERT INTO networks (subnet, description, id) VALUES ($1, $2, $3)").bind(cidr).bind(new.description).bind(id.to_string()).execute(&mut conn).await?;
+
+ Ok(Redirect::to("/"))
+}
diff --git a/src/main.rs b/src/main.rs
index ab03fb3..6ebe145 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@ mod config;
mod errors;
mod handlers;
mod models;
+mod utils;
use std::{net::SocketAddr, str::FromStr, sync::Arc, time::Duration};
@@ -13,6 +14,8 @@ use axum_extra::extract::{PrivateCookieJar, SignedCookieJar};
use axum_extra::extract::cookie::Key;
use errors::{StringResult, HtmlResult};
use hyper::StatusCode;
+use models::{Peer, Network};
+use sqlx::query_as;
use sqlx::{PgPool, postgres::PgPoolOptions};
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
@@ -106,8 +109,12 @@ async fn index(state: Extension<Arc<State>>, jar: PrivateCookieJar) -> HtmlResul
Ok(u) => Some(u),
Err(_) => None,
};
+
+ let peers: Vec<Peer> = query_as("SELECT * FROM peers").fetch_all(&mut conn).await?;
+ let nets: Vec<Network> = query_as("SELECT * FROM networks").fetch_all(&mut conn).await?;
+
let mut buf = Vec::new();
- crate::templates::index_html(&mut buf, user).unwrap();
+ crate::templates::index_html(&mut buf, user, peers, nets).unwrap();
match String::from_utf8(buf) {
Ok(s) => Ok(Html(s)),
@@ -115,6 +122,12 @@ async fn index(state: Extension<Arc<State>>, jar: PrivateCookieJar) -> HtmlResul
}
}
+async fn test_email() -> Result<(), ServiceError> {
+ utils::send_email("csalter@carathe.dev".to_string(), "Test Email".to_string(), "Hi, test".to_string()).await?;
+
+ Ok(())
+}
+
async fn statics(Path(name): Path<String>) -> Result<Response, ServiceError> {
for s in templates::statics::STATICS {
trace!("Name: {}\nContents:\n{:?}\n\n", s.name, s.content);
diff --git a/src/models.rs b/src/models.rs
index b367f4f..2295154 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -1,8 +1,6 @@
-use sqlx::FromRow;
+use sqlx::{FromRow, types::ipnetwork::IpNetwork};
-
-
-#[derive(Debug, FromRow)]
+#[derive(Debug, FromRow, Clone)]
pub struct DbUser {
pub id: String,
pub email: String,
@@ -10,3 +8,17 @@ pub struct DbUser {
pub pw_hash: String,
pub last_login: chrono::NaiveDateTime
}
+
+#[derive(Debug, FromRow, Clone)]
+pub struct Peer {
+ pub id: String,
+ pub addr: IpNetwork,
+ pub public_key: String,
+}
+
+#[derive(Debug, FromRow, Clone)]
+pub struct Network {
+ pub id: String,
+ pub subnet: IpNetwork,
+ pub description: Option<String>
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..e723db8
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,51 @@
+use lettre::{Message, transport::smtp::{authentication::Credentials, extension::ClientId}, transport::smtp::AsyncSmtpTransport, AsyncTransport, Tokio1Executor};
+
+use crate::{errors::ServiceError, config};
+
+pub async fn send_email(to: String, subject: String, content: String) -> Result<(), ServiceError> {
+ let config = config::Config::init("/etc/nccd/config.toml".to_string()).unwrap();
+ let email = if let Ok(e) = Message::builder()
+ .from(config.email.email_from.parse().unwrap())
+ .to(to.parse().unwrap())
+ .subject(subject)
+ .body(content) {
+ e
+ } else {
+ return Err(ServiceError::Email("Invalid email content".to_string()));
+ };
+ let mailer: AsyncSmtpTransport<Tokio1Executor>;
+ let helo = ClientId::Domain(config.email.email_helo);
+ if let Some(u) = config.email.smtp_username {
+ let creds = Credentials::new(u, config.email.smtp_password.unwrap());
+ if config.email.smtp_starttls {
+ mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&config.email.smtp_server)
+ .unwrap()
+ .credentials(creds)
+ .hello_name(helo)
+ .build();
+ } else {
+ mailer = AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&config.email.smtp_server)
+ .credentials(creds)
+ .hello_name(helo)
+ .build();
+ }
+ } else {
+ if config.email.smtp_tls && config.email.smtp_starttls {
+ mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&config.email.smtp_server).unwrap().hello_name(helo)
+ .build();
+ } else if config.email.smtp_tls {
+ mailer = AsyncSmtpTransport::<Tokio1Executor>::relay(&config.email.smtp_server).unwrap().hello_name(helo).build();
+ } else {
+ mailer = AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&config.email.smtp_server).hello_name(helo).build();
+ }
+ }
+
+ if let Err(e) = mailer.test_connection().await {
+ return Err(ServiceError::Email(e.to_string()));
+ } else {
+ if let Err(e) = mailer.send(email).await {
+ return Err(ServiceError::Email(e.to_string()));
+ }
+ }
+ Ok(())
+}
diff --git a/templates/index.rs.html b/templates/index.rs.html
index d0196df..e686b01 100644
--- a/templates/index.rs.html
+++ b/templates/index.rs.html
@@ -1,12 +1,69 @@
@use super::{header_html, footer_html};
-@use crate::models::DbUser;
+@use crate::models::{DbUser, Peer, Network};
-@(user: Option<DbUser>)
+@(user: Option<DbUser>, peers: Vec<Peer>, nets: Vec<Network>)
@:header_html()
<h1>NCCd (Network Communications Control Daemon)</h1>
@if user.is_some() {
- <h3>Welcome @user.unwrap().pref_name</h3>
+ <h3>Welcome @user.clone().unwrap().pref_name</h3>
+ <table>
+ <tr>
+ <th>Key</th>
+ <th>Value</th>
+ </tr>
+ <tr>
+ <td>ID</td>
+ <td>@user.clone().unwrap().id</td>
+ </tr>
+ <tr>
+ <td>Email</td>
+ <td>@user.clone().unwrap().email</td>
+ </tr>
+ <tr>
+ <td>Preferred Name</td>
+ <td>@user.clone().unwrap().pref_name</td>
+ </tr>
+ <tr>
+ <td>Last Login</td>
+ <td>@(user.clone().unwrap().last_login)Z</td>
+ </tr>
+ </table>
+
+ <hr>
+
+ <h3>Configured Peers</h3>
+ <table>
+ <tr>
+ <th>ID</th>
+ <th>Address</th>
+ <th>Public Key</th>
+ </tr>
+ @for p in peers {
+ <tr>
+ <td>@p.id</td>
+ <td>@p.addr</td>
+ <td>@p.public_key</td>
+ </tr>
+ }
+ </table>
+ <hr>
+ <h3>Configured subnets (<a href="/dash/nets/new">New</a>)</h3>
+ <table>
+ <tr>
+ <th>ID</th>
+ <th>Subnet CIDR</th>
+ <th>Description</th>
+ </tr>
+ @for n in nets {
+ <tr>
+ <td>@n.id</td>
+ <td>@n.subnet</td>
+ <td>@if n.description.is_some() { @n.description.unwrap()
+ } else { None }</td>
+ </tr>
+ }
+ </table>
} else {
<h3>Please <a href="/dash/auth/login">Log in</a></h3>
diff --git a/templates/new_net.rs.html b/templates/new_net.rs.html
new file mode 100644
index 0000000..4628432
--- /dev/null
+++ b/templates/new_net.rs.html
@@ -0,0 +1,21 @@
+@use super::{header_html, footer_html};
+
+@()
+
+@:header_html()
+
+<h1>New Subnet</h1>
+
+<form method="POST">
+ <div>
+ <label for="subnet">Subnet CIDR</label>
+ <input type="text" name="subnet" required>
+ </div>
+ <div>
+ <label for="description">Description</label>
+ <textarea name="description"></textarea>
+ </div>
+ <button type="submit" class="accent">Submit</button>
+</form>
+
+@:footer_html()