aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build.rs10
-rw-r--r--src/config.rs54
-rw-r--r--src/errors.rs49
-rw-r--r--src/handlers/auth.rs117
-rw-r--r--src/handlers/mod.rs24
-rw-r--r--src/handlers/nets.rs50
-rw-r--r--src/main.rs185
-rw-r--r--src/models.rs24
-rw-r--r--src/utils.rs51
9 files changed, 0 insertions, 564 deletions
diff --git a/src/build.rs b/src/build.rs
deleted file mode 100644
index d7d6882..0000000
--- a/src/build.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use ructe::{Ructe, RucteError};
-
-fn main() -> Result<(), RucteError> {
- let mut ructe = Ructe::from_env()?;
- let mut statics = ructe.statics()?;
- statics.add_files("statics")?;
- statics.add_sass_file("scss/style.scss")?;
-
- ructe.compile_templates("templates")
-}
diff --git a/src/config.rs b/src/config.rs
deleted file mode 100644
index c153085..0000000
--- a/src/config.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use serde::Deserialize;
-use tracing::error;
-
-use crate::errors::ServiceError;
-use std::fs;
-use std::str;
-
-#[derive(Deserialize)]
-pub struct Config {
- pub server: ServerConfig,
- pub database: DbConfig,
- pub email: EmailConfig,
-}
-
-#[derive(Deserialize)]
-pub struct ServerConfig {
- pub bind_addr: String,
- pub admin_email: String
-}
-
-#[derive(Deserialize, Clone)]
-pub struct DbConfig {
- pub postgres_url: String,
- 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) {
- if c.len() == 0 {
- error!("Config file empty.");
- return Err(ServiceError::MissingConfig);
- } else {
- let config: Config = toml::from_str(str::from_utf8(&c).unwrap())?;
- return Ok(config);
- }
- } else {
- error!("Unable to read from config file.");
- return Err(ServiceError::MissingConfig);
- }
- }
-}
diff --git a/src/errors.rs b/src/errors.rs
deleted file mode 100644
index dcc0ae8..0000000
--- a/src/errors.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use axum::body;
-use axum::response::{IntoResponse, Response, Html};
-use hyper::StatusCode;
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum ServiceError {
- #[error("No config file")]
- MissingConfig,
- #[error("Invalid config file: {0}")]
- InvalidConfig(#[from] toml::de::Error),
- #[error("Not Authorized")]
- NotAuthorized,
- #[error("Not Found")]
- NotFound,
- #[error("Axum: {0}")]
- Axum(#[from] axum::Error),
- #[error("SQL: {0}")]
- 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>;
-pub type HtmlResult<T = Html<String>> = Result<T, ServiceError>;
-pub type JsonResult<T> = Result<T, ServiceError>;
-
-impl IntoResponse for ServiceError {
- fn into_response(self) -> axum::response::Response {
- let body = body::boxed(body::Full::from(self.to_string()));
-
- let status = match self {
- ServiceError::NotFound => StatusCode::NOT_FOUND,
- ServiceError::NotAuthorized => StatusCode::UNAUTHORIZED,
- _ => StatusCode::INTERNAL_SERVER_ERROR,
- };
- 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
deleted file mode 100644
index ab72bc8..0000000
--- a/src/handlers/auth.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use std::sync::Arc;
-
-use axum::{response::{IntoResponse, Html, Redirect}, Form, Extension};
-use axum_extra::extract::{PrivateCookieJar, cookie::Cookie};
-use serde::Deserialize;
-use sqlx::{query, query_as, pool::PoolConnection, Postgres};
-use tracing::{debug, instrument};
-use crate::{errors::ServiceError, State, models::DbUser};
-use chrono::prelude::*;
-
-#[derive(Deserialize, Debug)]
-pub struct RegisterForm {
- pub email: String,
- pub prefname: String,
- pub password: String,
- #[serde(rename="password-confirm")]
- pub password_confirm: String
-}
-
-#[derive(Deserialize)]
-pub struct LoginForm {
- pub email: String,
- pub password: String,
-}
-
-pub async fn login() -> impl IntoResponse {
- let mut buf = Vec::new();
- crate::templates::login_html(&mut buf).unwrap();
-
- Html(buf)
-}
-
-pub async fn login_post(Form(login): Form<LoginForm>, state: Extension<Arc<State>>, jar: PrivateCookieJar) -> Result<(PrivateCookieJar, Redirect), ServiceError> {
- let mut conn = state.conn.acquire().await?;
-
- let user: DbUser = query_as("SELECT * FROM users WHERE email=$1").bind(login.email)
- .fetch_one(&mut conn)
- .await?;
-
- if bcrypt::verify(login.password, &user.pw_hash)? {
- debug!("Logged in ID {} (email {})", user.id, user.email);
- query("UPDATE users SET last_login=$1 WHERE id=$2").bind(Utc::now()).bind(user.id.clone())
- .execute(&mut conn)
- .await?;
-
- let updated_jar = jar.add(Cookie::build("user-id", user.id.clone())
- .path("/")
- .finish());
-
- Ok((updated_jar, Redirect::to("/")))
- } else {
- let updated_jar = jar;
- Ok((updated_jar, Redirect::to("/dash/auth/login")))
- }
-}
-
-pub async fn register() -> impl IntoResponse {
- let mut buf = Vec::new();
- crate::templates::register_html(&mut buf).unwrap();
-
- Html(buf)
-}
-
-pub async fn register_post(Form(reg): Form<RegisterForm>, state: Extension<Arc<State>>) -> impl IntoResponse {
- if reg.password_confirm == reg.password {
- let hash = match bcrypt::hash(reg.password, bcrypt::DEFAULT_COST) {
- Ok(h) => h,
- Err(e) => {
- return Err(ServiceError::NotAuthorized);
- }
- };
-
- let mut conn = state.conn.acquire().await?;
- let ulid = ulid::Ulid::new();
-
- query("INSERT INTO users (id, email, pref_name, pw_hash, last_login) VALUES ($1, $2, $3, $4, $5)").bind(ulid.to_string()).bind(reg.email.clone()).bind(reg.prefname).bind(hash).bind(Utc::now())
- .execute(&mut conn)
- .await?;
-
- }
-
- 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");
- debug!("Displaying all cookies");
- for c in jar.iter() {
- debug!("{}={}", c.name(), c.value());
- }
- if let Some(id) = jar.get("user-id") {
- debug!("Found user {}", id);
-
- let user: DbUser = query_as("SELECT * FROM users WHERE id=$1").bind(id.value())
- .fetch_one(conn)
- .await?;
-
- Ok(user)
-
- } else {
- debug!("No user found");
- Err(ServiceError::NotAuthorized)
- }
-}
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
deleted file mode 100644
index 24db540..0000000
--- a/src/handlers/mod.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-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 {
-
- 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
deleted file mode 100644
index 6010787..0000000
--- a/src/handlers/nets.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-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
deleted file mode 100644
index 6ebe145..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,185 +0,0 @@
-mod config;
-mod errors;
-mod handlers;
-mod models;
-mod utils;
-
-use std::{net::SocketAddr, str::FromStr, sync::Arc, time::Duration};
-
-use axum::body;
-use axum::extract::Path;
-use axum::{error_handling::HandleErrorLayer, routing::get, BoxError, Extension, Router};
-use axum::response::{Html, IntoResponse, Response};
-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;
-use tracing::{error, info, debug, trace};
-use crate::errors::ServiceError;
-use tracing_subscriber::prelude::*;
-use crate::models::DbUser;
-use crate::handlers::auth::get_user_or_403;
-
-pub struct State {
- pub config: config::Config,
- pub conn: PgPool
-}
-
-#[tokio::main]
-async fn main() {
- color_eyre::install().unwrap();
- tracing_subscriber::registry()
- .with(tracing_subscriber::EnvFilter::new("debug"))
- .with(tracing_subscriber::fmt::layer())
- .init();
- let config = match config::Config::init("/etc/nccd/config.toml".to_owned()) {
- Ok(c) => c,
- Err(e) => {
- error!("Config Error: {:?}", e);
- std::process::exit(1);
- }
- };
-
- let bind_addr = config.server.bind_addr.clone();
-
- let db_config = config.database.clone();
-
- let conn = PgPoolOptions::new()
- .max_connections(db_config.max_connections)
- .connect(&db_config.postgres_url)
- .await.unwrap();
-
- let shared_state = Arc::new(State { config, conn });
-
- let key = load_or_gen_keypair().unwrap();
-
- let app = Router::new()
- .route("/health", get(health_check))
- .route("/", get(index))
- .nest("/dash", handlers::gen_routers().await)
- .route("/static/:name", get(statics))
- .layer(
- ServiceBuilder::new()
- .layer(HandleErrorLayer::new(|error: BoxError| async move {
- if error.is::<tower::timeout::error::Elapsed>() {
- Ok(StatusCode::REQUEST_TIMEOUT)
- } else {
- Err((
- StatusCode::INTERNAL_SERVER_ERROR,
- format!("Unhandled internal error: {}", error),
- ))
- }
- }))
- .timeout(Duration::from_secs(10))
- .layer(TraceLayer::new_for_http())
- .into_inner(),
- )
- .layer(Extension(shared_state))
- .layer(Extension(key));
-
- let addr = match SocketAddr::from_str(&bind_addr) {
- Ok(a) => a,
- Err(e) => {
- error!("Invalid bind addr: {:?}", e);
- std::process::exit(1);
- }
- };
-
- info!("Listening on {}", addr);
-
- axum::Server::bind(&addr)
- .serve(app.into_make_service())
- .await
- .unwrap();
-}
-
-async fn health_check() -> &'static str {
- "OK"
-}
-
-#[axum_macros::debug_handler]
-async fn index(state: Extension<Arc<State>>, jar: PrivateCookieJar) -> HtmlResult {
- let mut conn = state.conn.acquire().await?;
- let user: Option<DbUser> = match get_user_or_403(jar, &mut conn).await {
- 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, peers, nets).unwrap();
-
- match String::from_utf8(buf) {
- Ok(s) => Ok(Html(s)),
- Err(_) => Err(ServiceError::NotFound),
- }
-}
-
-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);
- }
-
- match templates::statics::StaticFile::get(&name) {
- Some(s) => match String::from_utf8(s.content.to_vec()) {
- Ok(c) => {
- let body = body::boxed(body::Full::from(c));
-
- Ok(Response::builder()
- .header("Content-Type", "text/css")
- .status(StatusCode::OK)
- .body(body).unwrap())
- },
- Err(_) => Err(ServiceError::NotFound),
- },
- None => Err(ServiceError::NotFound),
- }
-}
-
-use std::fs::{self, File};
-fn load_or_gen_keypair() -> Result<Key, ServiceError> {
- let kp: Key;
- let mut file = match File::open(".keypair") {
- Ok(f) => f,
- Err(_) => {
- debug!("File does not exist, creating at .keypair");
- File::create(".keypair").unwrap()
- }
- };
- if let Ok(c) = fs::read(".keypair") {
- if c.len() == 0 {
- debug!("No keypair found. Generating...");
- let key = Key::generate();
- fs::write(".keypair", key.master().as_ref()).unwrap();
- debug!("Written keypair {:?} to .keypair", key.master().as_ref());
- kp = key;
- } else {
- debug!("Found keypair file, contents: {:?}", c);
- kp = Key::from(&c);
- debug!("Loaded keypair from file");
- }
- } else {
- debug!("Generating new keypair");
- let key = Key::generate();
- fs::write(".keypair", key.master().as_ref()).unwrap();
- debug!("Written keypair {:?} to .keypair", key.master().as_ref());
- kp = key;
- }
- Ok(kp)
-}
-
-
-include!(concat!(env!("OUT_DIR"), "/templates.rs"));
diff --git a/src/models.rs b/src/models.rs
deleted file mode 100644
index 2295154..0000000
--- a/src/models.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use sqlx::{FromRow, types::ipnetwork::IpNetwork};
-
-#[derive(Debug, FromRow, Clone)]
-pub struct DbUser {
- pub id: String,
- pub email: String,
- pub pref_name: String,
- 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
deleted file mode 100644
index e723db8..0000000
--- a/src/utils.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-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(())
-}