diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 13 | ||||
-rw-r--r-- | src/errors.rs | 10 | ||||
-rw-r--r-- | src/handlers/auth.rs | 12 | ||||
-rw-r--r-- | src/handlers/mod.rs | 10 | ||||
-rw-r--r-- | src/handlers/nets.rs | 50 | ||||
-rw-r--r-- | src/main.rs | 15 | ||||
-rw-r--r-- | src/models.rs | 20 | ||||
-rw-r--r-- | src/utils.rs | 51 |
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(()) +} |