aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
8 files changed, 175 insertions, 6 deletions
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(())
+}