feat: added tcp
sorry Judah
This commit is contained in:
2
sparse-server/migrations/20250211235645_interface.sql
Normal file
2
sparse-server/migrations/20250211235645_interface.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE beacon_template ADD COLUMN source_interface blob DEFAULT '';
|
||||
@@ -1,9 +1,9 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
||||
use leptos_router::{
|
||||
components::{A, ParentRoute, Route, Router, Routes},
|
||||
components::{ParentRoute, Route, Router, Routes, A},
|
||||
hooks::use_query_map,
|
||||
path
|
||||
path,
|
||||
};
|
||||
|
||||
use crate::users::User;
|
||||
@@ -29,7 +29,7 @@ pub async fn me() -> Result<Option<User>, ServerFnError> {
|
||||
|
||||
Ok(user.map(|user| User {
|
||||
user_id: user.user_id,
|
||||
user_name: user.user_name
|
||||
user_name: user.user_name,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -73,10 +73,7 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
let (user_res, set_user_res) = signal(None::<User>);
|
||||
|
||||
let user = Resource::new(
|
||||
move || login.version().get(),
|
||||
|_| async { me().await }
|
||||
);
|
||||
let user = Resource::new(move || login.version().get(), |_| async { me().await });
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
Effect::new(move || {
|
||||
@@ -143,7 +140,12 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn LoginPage(login: ServerAction<Login>) -> impl IntoView {
|
||||
let next = move || use_query_map().read().get("next").unwrap_or("/".to_string());
|
||||
let next = move || {
|
||||
use_query_map()
|
||||
.read()
|
||||
.get("next")
|
||||
.unwrap_or("/".to_string())
|
||||
};
|
||||
|
||||
view! {
|
||||
<main class="login">
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct BeaconResources {
|
||||
listeners: Resource<Result<Vec<listeners::PubListener>, ServerFnError>>,
|
||||
categories: Resource<Result<Vec<categories::Category>, ServerFnError>>,
|
||||
configs: Resource<Result<Vec<configs::BeaconConfig>, ServerFnError>>,
|
||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>
|
||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>,
|
||||
}
|
||||
|
||||
pub fn provide_beacon_resources() {
|
||||
@@ -56,40 +56,48 @@ pub fn provide_beacon_resources() {
|
||||
let remove_template = ServerAction::<templates::RemoveTemplate>::new();
|
||||
|
||||
let listeners = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
add_listener.version().get(),
|
||||
remove_listener.version().get(),
|
||||
),
|
||||
|_| async { listeners::get_listeners().await }
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_listener.version().get(),
|
||||
remove_listener.version().get(),
|
||||
)
|
||||
},
|
||||
|_| async { listeners::get_listeners().await },
|
||||
);
|
||||
|
||||
let categories = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
add_category.version().get(),
|
||||
remove_category.version().get(),
|
||||
rename_category.version().get(),
|
||||
),
|
||||
|_| async { categories::get_categories().await }
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_category.version().get(),
|
||||
remove_category.version().get(),
|
||||
rename_category.version().get(),
|
||||
)
|
||||
},
|
||||
|_| async { categories::get_categories().await },
|
||||
);
|
||||
|
||||
let configs = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
add_beacon_config.version().get(),
|
||||
remove_beacon_config.version().get(),
|
||||
),
|
||||
|_| async { configs::get_beacon_configs().await }
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_beacon_config.version().get(),
|
||||
remove_beacon_config.version().get(),
|
||||
)
|
||||
},
|
||||
|_| async { configs::get_beacon_configs().await },
|
||||
);
|
||||
|
||||
let templates = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
add_template.version().get(),
|
||||
remove_template.version().get()
|
||||
),
|
||||
|_| async { templates::get_templates().await }
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_template.version().get(),
|
||||
remove_template.version().get(),
|
||||
)
|
||||
},
|
||||
|_| async { templates::get_templates().await },
|
||||
);
|
||||
|
||||
provide_context(BeaconResources {
|
||||
@@ -106,7 +114,7 @@ pub fn provide_beacon_resources() {
|
||||
listeners,
|
||||
categories,
|
||||
configs,
|
||||
templates
|
||||
templates,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,7 +150,7 @@ enum SortMethod {
|
||||
Listener,
|
||||
Config,
|
||||
Category,
|
||||
Template
|
||||
Template,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for SortMethod {
|
||||
@@ -154,7 +162,7 @@ impl std::str::FromStr for SortMethod {
|
||||
"Config" => Ok(Self::Config),
|
||||
"Category" => Ok(Self::Category),
|
||||
"Template" => Ok(Self::Template),
|
||||
&_ => Err(())
|
||||
&_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +175,8 @@ impl std::string::ToString for SortMethod {
|
||||
SM::Config => "Config",
|
||||
SM::Category => "Category",
|
||||
SM::Template => "Template",
|
||||
}.to_string()
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
crate::db::user,
|
||||
};
|
||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
||||
|
||||
use super::BeaconResources;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub category_id: i64,
|
||||
pub category_name: String
|
||||
pub category_name: String,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@@ -20,28 +16,26 @@ pub async fn get_categories() -> Result<Vec<Category>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
Ok(
|
||||
sqlx::query_as!(
|
||||
Category,
|
||||
"SELECT * FROM beacon_category"
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
)
|
||||
Ok(sqlx::query_as!(Category, "SELECT * FROM beacon_category")
|
||||
.fetch_all(&db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
#[server]
|
||||
pub async fn add_category(name: String) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -50,8 +44,8 @@ pub async fn add_category(name: String) -> Result<(), ServerFnError> {
|
||||
"INSERT INTO beacon_category (category_name) VALUES (?)",
|
||||
name
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -61,15 +55,14 @@ pub async fn remove_category(id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_category WHERE category_id = ?",
|
||||
id
|
||||
)
|
||||
sqlx::query!("DELETE FROM beacon_category WHERE category_id = ?", id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -81,7 +74,9 @@ pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError>
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -91,15 +86,19 @@ pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError>
|
||||
name,
|
||||
id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CategoriesView() -> impl IntoView {
|
||||
let BeaconResources { add_category, categories, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
add_category,
|
||||
categories,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="categories">
|
||||
@@ -148,7 +147,11 @@ pub fn CategoriesView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
||||
let BeaconResources { remove_category, rename_category, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_category,
|
||||
rename_category,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
let (target_rename_id, set_target_rename_id) = signal(0);
|
||||
let target_rename_name = RwSignal::new("".to_owned());
|
||||
@@ -157,55 +160,59 @@ fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
||||
|
||||
let categories_view = categories
|
||||
.iter()
|
||||
.map(|category| view! {
|
||||
<li>
|
||||
{category.category_id}
|
||||
": "
|
||||
{category.category_name.clone()}
|
||||
<button
|
||||
on:click={
|
||||
let id = category.category_id;
|
||||
let name = category.category_name.clone();
|
||||
let set_target_rename_id = set_target_rename_id.clone();
|
||||
let target_rename_name = target_rename_name.clone();
|
||||
move |_| {
|
||||
set_target_rename_id(id);
|
||||
target_rename_name.set(name.clone());
|
||||
.map(|category| {
|
||||
view! {
|
||||
<li>
|
||||
{category.category_id}
|
||||
": "
|
||||
{category.category_name.clone()}
|
||||
<button
|
||||
on:click={
|
||||
let id = category.category_id;
|
||||
let name = category.category_name.clone();
|
||||
let set_target_rename_id = set_target_rename_id.clone();
|
||||
let target_rename_name = target_rename_name.clone();
|
||||
move |_| {
|
||||
set_target_rename_id(id);
|
||||
target_rename_name.set(name.clone());
|
||||
|
||||
if let Some(dialog) = dialog_ref.get() {
|
||||
let _ = dialog.show_modal();
|
||||
if let Some(dialog) = dialog_ref.get() {
|
||||
let _ = dialog.show_modal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
"rename"
|
||||
</button>
|
||||
<button
|
||||
on:click={
|
||||
let id = category.category_id;
|
||||
move |_| {
|
||||
remove_category.dispatch(RemoveCategory { id });
|
||||
>
|
||||
"rename"
|
||||
</button>
|
||||
<button
|
||||
on:click={
|
||||
let id = category.category_id;
|
||||
move |_| {
|
||||
remove_category.dispatch(RemoveCategory { id });
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
Effect::watch(
|
||||
move || (
|
||||
rename_category.version().get(),
|
||||
rename_category.value().get(),
|
||||
dialog_ref.get()
|
||||
),
|
||||
move |(_, res,dialog_ref),_,_| {
|
||||
move || {
|
||||
(
|
||||
rename_category.version().get(),
|
||||
rename_category.value().get(),
|
||||
dialog_ref.get(),
|
||||
)
|
||||
},
|
||||
move |(_, res, dialog_ref), _, _| {
|
||||
if let (Some(Ok(())), Some(dialog)) = (res, dialog_ref) {
|
||||
let _ = dialog.close();
|
||||
}
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
view! {
|
||||
|
||||
@@ -2,12 +2,10 @@ use leptos::{either::Either, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
std::str::FromStr,
|
||||
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
|
||||
crate::db::user
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
||||
use super::BeaconResources;
|
||||
@@ -32,7 +30,7 @@ impl FromRow<'_, SqliteRow> for BeaconConfigTypes {
|
||||
)),
|
||||
"cron" => Ok(Self::CronSchedule(
|
||||
row.try_get("cron_schedule")?,
|
||||
row.try_get("cron_mode")?
|
||||
row.try_get("cron_mode")?,
|
||||
)),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string(),
|
||||
@@ -55,7 +53,9 @@ pub async fn get_beacon_configs() -> Result<Vec<BeaconConfig>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -78,7 +78,9 @@ pub async fn add_beacon_config(
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -89,14 +91,16 @@ pub async fn add_beacon_config(
|
||||
"INSERT INTO beacon_config (config_name, mode) VALUES (?, 'single')",
|
||||
name
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"regular" => {
|
||||
if regular_interval < 1 {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid interval provided".to_owned()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid interval provided".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@@ -108,10 +112,12 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"random" => {
|
||||
if random_min_time < 1 || random_max_time < random_min_time {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid random interval provided".to_owned()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid random interval provided".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@@ -124,19 +130,21 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"cron" => {
|
||||
if let Err(e) = cron::Schedule::from_str(&cron_schedule) {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(format!(
|
||||
"Could not parse cron expression: {}",
|
||||
e
|
||||
)))
|
||||
)));
|
||||
}
|
||||
|
||||
match &*cron_mode {
|
||||
"local" | "utc" => {},
|
||||
"local" | "utc" => {}
|
||||
_ => {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Unrecognized timezone specifier for cron".to_string()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Unrecognized timezone specifier for cron".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +158,10 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
_ => {
|
||||
Err(ServerFnError::<NoCustomError>::ServerError("Invalid mode supplied".to_owned()))
|
||||
}
|
||||
_ => Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid mode supplied".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,15 +170,14 @@ pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_config WHERE config_id = ?",
|
||||
id
|
||||
)
|
||||
sqlx::query!("DELETE FROM beacon_config WHERE config_id = ?", id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -179,7 +186,11 @@ pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn ConfigsView() -> impl IntoView {
|
||||
let BeaconResources { add_beacon_config, configs, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
add_beacon_config,
|
||||
configs,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="config">
|
||||
@@ -260,42 +271,47 @@ pub fn ConfigsView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayConfigs(configs: Vec<BeaconConfig>) -> impl IntoView {
|
||||
let BeaconResources { remove_beacon_config, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_beacon_config,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
let configs_view = configs
|
||||
.iter()
|
||||
.map(|config| view! {
|
||||
<li>
|
||||
{config.config_id}
|
||||
": "
|
||||
{config.config_name.clone()}
|
||||
" ("
|
||||
{match &config.config_type {
|
||||
BeaconConfigTypes::Single => {
|
||||
format!("Single")
|
||||
},
|
||||
BeaconConfigTypes::Regular(int) => {
|
||||
format!("Regular; every {int} seconds")
|
||||
},
|
||||
BeaconConfigTypes::Random(min, max) => {
|
||||
format!("Random; regularly between {min} and {max} seconds")
|
||||
},
|
||||
BeaconConfigTypes::CronSchedule(sch, mode) => {
|
||||
format!("{mode} cron; schedule: {sch}")
|
||||
}
|
||||
}}
|
||||
") "
|
||||
<button
|
||||
on:click={
|
||||
let id = config.config_id;
|
||||
move |_| {
|
||||
remove_beacon_config.dispatch(RemoveBeaconConfig { id });
|
||||
.map(|config| {
|
||||
view! {
|
||||
<li>
|
||||
{config.config_id}
|
||||
": "
|
||||
{config.config_name.clone()}
|
||||
" ("
|
||||
{match &config.config_type {
|
||||
BeaconConfigTypes::Single => {
|
||||
format!("Single")
|
||||
},
|
||||
BeaconConfigTypes::Regular(int) => {
|
||||
format!("Regular; every {int} seconds")
|
||||
},
|
||||
BeaconConfigTypes::Random(min, max) => {
|
||||
format!("Random; regularly between {min} and {max} seconds")
|
||||
},
|
||||
BeaconConfigTypes::CronSchedule(sch, mode) => {
|
||||
format!("{mode} cron; schedule: {sch}")
|
||||
}
|
||||
}}
|
||||
") "
|
||||
<button
|
||||
on:click={
|
||||
let id = config.config_id;
|
||||
move |_| {
|
||||
remove_beacon_config.dispatch(RemoveBeaconConfig { id });
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
rcgen::{generate_simple_self_signed, CertifiedKey},
|
||||
|
||||
sparse_handler::BeaconListenerMap,
|
||||
|
||||
crate::db::user,
|
||||
sqlx::SqlitePool,
|
||||
};
|
||||
|
||||
use super::BeaconResources;
|
||||
@@ -22,7 +19,7 @@ struct DbListener {
|
||||
listener_id: i64,
|
||||
port: i64,
|
||||
public_ip: String,
|
||||
domain_name: String
|
||||
domain_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
@@ -31,7 +28,7 @@ pub struct PubListener {
|
||||
pub port: i64,
|
||||
pub public_ip: String,
|
||||
pub domain_name: String,
|
||||
pub active: bool
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@@ -39,7 +36,9 @@ pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -49,8 +48,8 @@ pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||
DbListener,
|
||||
"SELECT listener_id, port, public_ip, domain_name FROM beacon_listener"
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
let Ok(beacon_handles_handle) = beacon_handles.read() else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("".to_string()));
|
||||
@@ -66,13 +65,16 @@ pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||
active: beacon_handles_handle
|
||||
.get(&b.listener_id)
|
||||
.map(|h| !h.is_finished())
|
||||
.unwrap_or(false)
|
||||
.unwrap_or(false),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn generate_cert_from_keypair(kp: &rcgen::KeyPair, names: Vec<String>) -> Result<rcgen::Certificate, rcgen::Error> {
|
||||
pub fn generate_cert_from_keypair(
|
||||
kp: &rcgen::KeyPair,
|
||||
names: Vec<String>,
|
||||
) -> Result<rcgen::Certificate, rcgen::Error> {
|
||||
use rcgen::CertificateParams;
|
||||
|
||||
let mut params = CertificateParams::new(names)?;
|
||||
@@ -83,25 +85,33 @@ pub fn generate_cert_from_keypair(kp: &rcgen::KeyPair, names: Vec<String>) -> Re
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn add_listener(public_ip: String, port: i16, domain_name: String) -> Result<(), ServerFnError> {
|
||||
pub async fn add_listener(
|
||||
public_ip: String,
|
||||
port: i16,
|
||||
domain_name: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
if public_ip.parse::<Ipv4Addr>().is_err() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Unable to parse public IP address".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Unable to parse public IP address".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let subject_alt_names = vec![public_ip.to_string(), domain_name.clone()];
|
||||
|
||||
|
||||
let (key_pair, cert) = tokio::task::spawn_blocking(|| {
|
||||
rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
|
||||
.and_then(|keypair|
|
||||
generate_cert_from_keypair(&keypair, subject_alt_names).map(|cert| (keypair, cert)))
|
||||
}).await??;
|
||||
rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).and_then(|keypair| {
|
||||
generate_cert_from_keypair(&keypair, subject_alt_names).map(|cert| (keypair, cert))
|
||||
})
|
||||
})
|
||||
.await??;
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
@@ -128,20 +138,26 @@ pub async fn remove_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
{
|
||||
let blm = expect_context::<BeaconListenerMap>();
|
||||
|
||||
let Ok(mut blm_handle) = blm.write() else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Failed to get write handle for beacon listener map".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Failed to get write handle for beacon listener map".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
if let Some(bl) = blm_handle.get_mut(&listener_id) {
|
||||
bl.abort();
|
||||
} else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Failed to get write handle for beacon listener map".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Failed to get write handle for beacon listener map".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
blm_handle.remove(&listener_id);
|
||||
@@ -153,8 +169,8 @@ pub async fn remove_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||
"DELETE FROM beacon_listener WHERE listener_id = ?",
|
||||
listener_id
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -164,21 +180,23 @@ pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sparse_handler::start_listener(
|
||||
expect_context(),
|
||||
listener_id,
|
||||
expect_context()
|
||||
).await?;
|
||||
sparse_handler::start_listener(expect_context(), listener_id, expect_context()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ListenersView() -> impl IntoView {
|
||||
let super::BeaconResources { add_listener, listeners, .. } = expect_context::<super::BeaconResources>();
|
||||
let super::BeaconResources {
|
||||
add_listener,
|
||||
listeners,
|
||||
..
|
||||
} = expect_context::<super::BeaconResources>();
|
||||
|
||||
view! {
|
||||
<div class="listeners">
|
||||
@@ -230,7 +248,11 @@ pub fn ListenersView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
||||
let BeaconResources { listeners: listener_resource, remove_listener, .. } = expect_context::<BeaconResources>();
|
||||
let BeaconResources {
|
||||
listeners: listener_resource,
|
||||
remove_listener,
|
||||
..
|
||||
} = expect_context::<BeaconResources>();
|
||||
|
||||
let (error_msg, set_error_msg) = signal(None);
|
||||
let start_listener_action = Action::new(move |&id: &i64| async move {
|
||||
@@ -246,46 +268,48 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
||||
|
||||
let listeners_view = listeners
|
||||
.iter()
|
||||
.map(|listener| view! {
|
||||
<li>
|
||||
{listener.listener_id}
|
||||
": "
|
||||
{listener.domain_name.clone()}
|
||||
" ("
|
||||
{listener.public_ip.clone()}
|
||||
":"
|
||||
{listener.port}
|
||||
") "
|
||||
{match listener.active {
|
||||
true => Either::Left(view! {
|
||||
<span>"active!"</span>
|
||||
}),
|
||||
false => Either::Right(view! {
|
||||
<button
|
||||
on:click={
|
||||
let id = listener.listener_id;
|
||||
move |e| {
|
||||
let _ = e.prevent_default();
|
||||
start_listener_action.dispatch(id);
|
||||
.map(|listener| {
|
||||
view! {
|
||||
<li>
|
||||
{listener.listener_id}
|
||||
": "
|
||||
{listener.domain_name.clone()}
|
||||
" ("
|
||||
{listener.public_ip.clone()}
|
||||
":"
|
||||
{listener.port}
|
||||
") "
|
||||
{match listener.active {
|
||||
true => Either::Left(view! {
|
||||
<span>"active!"</span>
|
||||
}),
|
||||
false => Either::Right(view! {
|
||||
<button
|
||||
on:click={
|
||||
let id = listener.listener_id;
|
||||
move |e| {
|
||||
let _ = e.prevent_default();
|
||||
start_listener_action.dispatch(id);
|
||||
}
|
||||
}
|
||||
>
|
||||
"activate"
|
||||
</button>
|
||||
})
|
||||
}}
|
||||
<button
|
||||
on:click={
|
||||
let id = listener.listener_id;
|
||||
move |e| {
|
||||
let _ = e.prevent_default();
|
||||
remove_listener.dispatch(RemoveListener { listener_id: id });
|
||||
}
|
||||
>
|
||||
"activate"
|
||||
</button>
|
||||
})
|
||||
}}
|
||||
<button
|
||||
on:click={
|
||||
let id = listener.listener_id;
|
||||
move |e| {
|
||||
let _ = e.prevent_default();
|
||||
remove_listener.dispatch(RemoveListener { listener_id: id });
|
||||
}
|
||||
}
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
>
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
std::net::Ipv4Addr,
|
||||
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
|
||||
crate::db::user
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
std::net::Ipv4Addr,
|
||||
};
|
||||
|
||||
use crate::beacons::BeaconResources;
|
||||
@@ -15,7 +13,7 @@ use crate::beacons::BeaconResources;
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum BeaconSourceMode {
|
||||
Host,
|
||||
Custom(i64, String)
|
||||
Custom(i64, String),
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -25,7 +23,7 @@ impl FromRow<'_, SqliteRow> for BeaconSourceMode {
|
||||
"host" => Ok(Self::Host),
|
||||
"custom" => Ok(Self::Custom(
|
||||
row.try_get("source_netmask")?,
|
||||
row.try_get("source_gateway")?
|
||||
row.try_get("source_gateway")?,
|
||||
)),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string(),
|
||||
@@ -48,7 +46,7 @@ pub struct BeaconTemplate {
|
||||
|
||||
config_id: i64,
|
||||
listener_id: i64,
|
||||
default_category: Option<i64>
|
||||
default_category: Option<i64>,
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@@ -72,7 +70,8 @@ pub async fn add_template(
|
||||
source_mac: String,
|
||||
source_mode: String,
|
||||
source_netmask: i64,
|
||||
source_gateway: String
|
||||
source_gateway: String,
|
||||
source_interface: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
@@ -97,7 +96,11 @@ pub async fn add_template(
|
||||
}
|
||||
|
||||
let mac_parts = source_mac.split(":").collect::<Vec<_>>();
|
||||
if mac_parts.len() != 6 || mac_parts.iter().any(|p| p.len() != 2 || u8::from_str_radix(p, 16).is_err()) {
|
||||
if mac_parts.len() != 6
|
||||
|| mac_parts
|
||||
.iter()
|
||||
.any(|p| p.len() != 2 || u8::from_str_radix(p, 16).is_err())
|
||||
{
|
||||
srverr!("Source MAC address is formatted incorrectly");
|
||||
}
|
||||
|
||||
@@ -107,8 +110,8 @@ pub async fn add_template(
|
||||
"SELECT certificate, privkey FROM beacon_listener WHERE listener_id = ?",
|
||||
listener_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
use rcgen::{Certificate, CertificateParams, KeyPair};
|
||||
|
||||
@@ -119,7 +122,7 @@ pub async fn add_template(
|
||||
srverr!("Could not parse private key: {e}");
|
||||
}
|
||||
},
|
||||
&rcgen::PKCS_ECDSA_P256_SHA256
|
||||
&rcgen::PKCS_ECDSA_P256_SHA256,
|
||||
)?;
|
||||
let ca_params = CertificateParams::from_ca_cert_der(&(*listener.certificate).into())?;
|
||||
let ca_cert = ca_params.self_signed(&keypair)?;
|
||||
@@ -131,6 +134,8 @@ pub async fn add_template(
|
||||
let client_key_der = client_key.serialize_der();
|
||||
let client_cert_der = client_cert.der().to_vec();
|
||||
|
||||
let interface = Some(source_interface).filter(|s| !s.is_empty());
|
||||
|
||||
match &*source_mode {
|
||||
"host" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
@@ -138,9 +143,11 @@ pub async fn add_template(
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category, client_key, client_cert)
|
||||
(template_name, operating_system, config_id, listener_id, source_ip,
|
||||
source_mac, source_mode, default_category, client_key, client_cert,
|
||||
source_interface)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)",
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
@@ -149,22 +156,25 @@ pub async fn add_template(
|
||||
source_mac,
|
||||
default_category,
|
||||
client_key_der,
|
||||
client_cert_der
|
||||
client_cert_der,
|
||||
interface
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"custom" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
let default_category = Some(default_category).filter(|dc| *dc != 0);
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category, client_key, client_cert)
|
||||
(template_name, operating_system, config_id, listener_id, source_ip,
|
||||
source_mac, source_mode, source_netmask, source_gateway, default_category,
|
||||
client_key, client_cert, source_interface)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?)",
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?, ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
@@ -175,13 +185,14 @@ pub async fn add_template(
|
||||
source_gateway,
|
||||
default_category,
|
||||
client_key_der,
|
||||
client_cert_der
|
||||
client_cert_der,
|
||||
interface
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
_other => {
|
||||
srverr!("Invalid type of source mode provided");
|
||||
}
|
||||
@@ -198,9 +209,12 @@ pub async fn remove_template(template_id: i64) -> Result<(), ServerFnError> {
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!("DELETE FROM beacon_template WHERE template_id = ?", template_id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_template WHERE template_id = ?",
|
||||
template_id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -210,7 +224,9 @@ pub async fn get_templates() -> Result<Vec<BeaconTemplate>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@@ -222,7 +238,13 @@ pub async fn get_templates() -> Result<Vec<BeaconTemplate>, ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TemplatesView() -> impl IntoView {
|
||||
let BeaconResources { configs, listeners, categories, templates, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
configs,
|
||||
listeners,
|
||||
categories,
|
||||
templates,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="templates">
|
||||
@@ -379,6 +401,8 @@ pub fn AddTemplateForm(
|
||||
<input class="mode-custom" type="number" name="source_netmask" value="24"/>
|
||||
<label class="mode-custom">"Network gateway"</label>
|
||||
<input class="mode-custom" name="source_gateway"/>
|
||||
<label class="mode-custom">"Network interface name"</label>
|
||||
<input class="mode-custom" name="source_interface"/>
|
||||
<div></div>
|
||||
<input type="submit" value="Submit" />
|
||||
</fieldset>
|
||||
@@ -391,9 +415,11 @@ pub fn DisplayTemplates(
|
||||
configs: Vec<super::configs::BeaconConfig>,
|
||||
listeners: Vec<super::listeners::PubListener>,
|
||||
categories: Vec<super::categories::Category>,
|
||||
templates: Vec<BeaconTemplate>
|
||||
templates: Vec<BeaconTemplate>,
|
||||
) -> impl IntoView {
|
||||
let BeaconResources { remove_template, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_template, ..
|
||||
} = expect_context();
|
||||
|
||||
let templates_view = templates
|
||||
.iter()
|
||||
|
||||
@@ -9,7 +9,7 @@ pub async fn handle_user_command(user_command: UC, db: SqlitePool) -> anyhow::Re
|
||||
match user_command {
|
||||
UC::List {} => list_users(db).await,
|
||||
UC::Create { user_name } => create_user(db, user_name).await,
|
||||
UC::ResetPassword { user_id } => reset_password(&db, user_id).await
|
||||
UC::ResetPassword { user_id } => reset_password(&db, user_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ async fn create_user(db: SqlitePool, name: String) -> anyhow::Result<ExitCode> {
|
||||
|
||||
async fn reset_password<'a, E>(db: E, id: i16) -> anyhow::Result<ExitCode>
|
||||
where
|
||||
E: sqlx::SqliteExecutor<'a>
|
||||
E: sqlx::SqliteExecutor<'a>,
|
||||
{
|
||||
let password = get_password()?;
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use leptos::prelude::ServerFnError;
|
||||
use leptos::{prelude::expect_context, server_fn::error::NoCustomError};
|
||||
use leptos_axum::{extract, ResponseOptions};
|
||||
use leptos::prelude::ServerFnError;
|
||||
use pbkdf2::{Pbkdf2, password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, rand_core::{OsRng, RngCore}, SaltString}};
|
||||
use pbkdf2::{
|
||||
password_hash::{
|
||||
rand_core::{OsRng, RngCore},
|
||||
PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
||||
},
|
||||
Pbkdf2,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::error::Error;
|
||||
@@ -11,7 +17,7 @@ pub struct User {
|
||||
pub user_id: i64,
|
||||
pub user_name: String,
|
||||
password_hash: String,
|
||||
pub last_active: Option<i64>
|
||||
pub last_active: Option<i64>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for User {
|
||||
@@ -29,12 +35,13 @@ async fn hash_password(pass: &[u8]) -> Result<String, Error> {
|
||||
let pass = pass.to_owned();
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
move ||
|
||||
Pbkdf2.hash_password(
|
||||
&*pass,
|
||||
&salt,
|
||||
).map(|hash| hash.serialize().as_str().to_string())
|
||||
}).await??)
|
||||
move || {
|
||||
Pbkdf2
|
||||
.hash_password(&*pass, &salt)
|
||||
.map(|hash| hash.serialize().as_str().to_string())
|
||||
}
|
||||
})
|
||||
.await??)
|
||||
}
|
||||
|
||||
async fn verify_password(pass: &str, hash: &str) -> Result<bool, Error> {
|
||||
@@ -42,46 +49,49 @@ async fn verify_password(pass: &str, hash: &str) -> Result<bool, Error> {
|
||||
let pass = pass.to_owned();
|
||||
let hash = hash.to_owned();
|
||||
|
||||
move ||
|
||||
move || {
|
||||
PasswordHash::new(&*hash)
|
||||
.map(|parsed| Pbkdf2.verify_password(
|
||||
&pass.as_bytes(),
|
||||
&parsed
|
||||
).is_ok())
|
||||
}).await??)
|
||||
.map(|parsed| Pbkdf2.verify_password(&pass.as_bytes(), &parsed).is_ok())
|
||||
}
|
||||
})
|
||||
.await??)
|
||||
}
|
||||
|
||||
pub async fn reset_password<'a, E>(pool: E, id: i16, password: String) -> Result<(), crate::error::Error>
|
||||
pub async fn reset_password<'a, E>(
|
||||
pool: E,
|
||||
id: i16,
|
||||
password: String,
|
||||
) -> Result<(), crate::error::Error>
|
||||
where
|
||||
E: sqlx::SqliteExecutor<'a>
|
||||
E: sqlx::SqliteExecutor<'a>,
|
||||
{
|
||||
let password_string = hash_password(
|
||||
password.as_bytes()
|
||||
).await?;
|
||||
let password_string = hash_password(password.as_bytes()).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||
password_string,
|
||||
id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_user<'a, E>(acq: E, name: String, password: String) -> Result<(), crate::error::Error>
|
||||
pub async fn create_user<'a, E>(
|
||||
acq: E,
|
||||
name: String,
|
||||
password: String,
|
||||
) -> Result<(), crate::error::Error>
|
||||
where
|
||||
E: sqlx::Acquire<'a, Database = sqlx::Sqlite>
|
||||
E: sqlx::Acquire<'a, Database = sqlx::Sqlite>,
|
||||
{
|
||||
let mut tx = acq.begin().await?;
|
||||
|
||||
let previous_user_check = sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM users WHERE user_name = ?",
|
||||
name
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
let previous_user_check =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE user_name = ?", name)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if previous_user_check > 0 {
|
||||
return Err(Error::UserCreate("User already exists".to_string()));
|
||||
@@ -93,9 +103,9 @@ where
|
||||
r#"INSERT INTO users (user_name, password_hash) VALUES (?, "")"#,
|
||||
name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
|
||||
reset_password(&mut *tx, new_id as i16, password).await?;
|
||||
|
||||
@@ -108,37 +118,30 @@ const SESSION_ID_KEY: &'static str = "session_id";
|
||||
const SESSION_AGE: i64 = 30 * 60;
|
||||
|
||||
pub async fn create_auth_session(username: String, password: String) -> Result<(), ServerFnError> {
|
||||
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
let resp = expect_context::<ResponseOptions>();
|
||||
|
||||
let user: Option<User> = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT * FROM users WHERE user_name = ?",
|
||||
username
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
let user: Option<User> =
|
||||
sqlx::query_as!(User, "SELECT * FROM users WHERE user_name = ?", username)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let Some(user) = user else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid credentials".to_string()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid credentials".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let good_hash = verify_password(
|
||||
&password,
|
||||
&user.password_hash
|
||||
).await?;
|
||||
let good_hash = verify_password(&password, &user.password_hash).await?;
|
||||
|
||||
if good_hash {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let expires = now + SESSION_AGE;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE users SET last_active = ?",
|
||||
now
|
||||
)
|
||||
sqlx::query!("UPDATE users SET last_active = ?", now)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -146,7 +149,8 @@ pub async fn create_auth_session(username: String, password: String) -> Result<(
|
||||
let mut key = [0u8; 32];
|
||||
OsRng.fill_bytes(&mut key);
|
||||
hex::encode(&key[..])
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions (session_id, user_id, expires) VALUES (?, ?, ?)",
|
||||
@@ -154,8 +158,8 @@ pub async fn create_auth_session(username: String, password: String) -> Result<(
|
||||
user.user_id,
|
||||
expires
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let cookie = Cookie::build((SESSION_ID_KEY, &session_id))
|
||||
.http_only(true)
|
||||
@@ -168,7 +172,9 @@ pub async fn create_auth_session(username: String, password: String) -> Result<(
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServerFnError::<NoCustomError>::ServerError("Invalid credentials".to_string()))
|
||||
Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid credentials".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +190,7 @@ pub async fn destroy_auth_session() -> Result<(), ServerFnError> {
|
||||
|
||||
let session_id = cookie.value();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions WHERE session_id = ?",
|
||||
session_id
|
||||
)
|
||||
sqlx::query!("DELETE FROM sessions WHERE session_id = ?", session_id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -217,8 +220,8 @@ pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
||||
session_id,
|
||||
now
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
if let Some(u) = &user {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
@@ -229,22 +232,19 @@ pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
||||
now,
|
||||
u.user_id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE sessions SET expires = ? WHERE session_id = ?",
|
||||
expires,
|
||||
session_id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
.execute(&db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions WHERE expires < ?",
|
||||
now
|
||||
)
|
||||
sqlx::query!("DELETE FROM sessions WHERE expires < ?", now)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod cli;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod webserver;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod beacons;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod users;
|
||||
pub mod error;
|
||||
mod cli;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod users;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod webserver;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
|
||||
use std::{path::PathBuf, process::ExitCode, str::FromStr};
|
||||
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
|
||||
use structopt::StructOpt;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use sqlx::sqlite::{SqlitePool, SqliteConnectOptions};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()),
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let options = cli::Options::from_args();
|
||||
|
||||
let db_location = options.db_location.clone()
|
||||
let db_location = options
|
||||
.db_location
|
||||
.clone()
|
||||
.or(std::env::var("DATABASE_URL")
|
||||
.map(|p| p.replace("sqlite://", ""))
|
||||
.map(PathBuf::from)
|
||||
@@ -45,7 +46,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
if !options.init_ok {
|
||||
tracing::error!("Database doesn't exist, and initialization not allowed!");
|
||||
tracing::error!("{:?}", e);
|
||||
return Ok(ExitCode::FAILURE)
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
|
||||
tracing::info!("Database doesn't exist, readying initialization");
|
||||
@@ -53,14 +54,13 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
|
||||
let pool = SqlitePool::connect_with(
|
||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", db_location.to_string_lossy()))?
|
||||
.create_if_missing(options.init_ok)
|
||||
).await?;
|
||||
.create_if_missing(options.init_ok),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Running database migrations...");
|
||||
|
||||
sqlx::migrate!()
|
||||
.run(&pool)
|
||||
.await?;
|
||||
sqlx::migrate!().run(&pool).await?;
|
||||
|
||||
tracing::info!("Done running database migrations!");
|
||||
|
||||
@@ -69,12 +69,8 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
tracing::info!("Performing requested action, acting as web server");
|
||||
webserver::serve_web(management_address, pool).await
|
||||
}
|
||||
Some(cli::Command::ExtractPubKey { }) => {
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
Some(cli::Command::User { command }) => {
|
||||
cli::user::handle_user_command(command, pool).await
|
||||
}
|
||||
Some(cli::Command::ExtractPubKey {}) => Ok(ExitCode::SUCCESS),
|
||||
Some(cli::Command::User { command }) => cli::user::handle_user_command(command, pool).await,
|
||||
None => {
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
use chrono::{DateTime, offset::Utc};
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
use leptos::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
crate::db::user
|
||||
};
|
||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
||||
|
||||
pub fn format_delta(time: chrono::TimeDelta) -> String {
|
||||
let seconds = time.num_seconds();
|
||||
|
||||
match seconds {
|
||||
0..=59 => format!("{} second{} ago", seconds, if seconds == 1 {""} else {"s"}),
|
||||
0..=59 => format!(
|
||||
"{} second{} ago",
|
||||
seconds,
|
||||
if seconds == 1 { "" } else { "s" }
|
||||
),
|
||||
60..=3599 => {
|
||||
let minutes = seconds / 60;
|
||||
format!("{} minute{} ago", minutes, if minutes == 1 {""} else {"s"})
|
||||
format!(
|
||||
"{} minute{} ago",
|
||||
minutes,
|
||||
if minutes == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
3600..=86399 => {
|
||||
let hours = seconds / 3600;
|
||||
format!("{} hours{} ago", hours, if hours == 1 {""} else {"s"})
|
||||
format!("{} hours{} ago", hours, if hours == 1 { "" } else { "s" })
|
||||
}
|
||||
_ => {
|
||||
let days = seconds / 86400;
|
||||
format!("{} day{} ago", days, if days == 1 {""} else {"s"})
|
||||
format!("{} day{} ago", days, if days == 1 { "" } else { "s" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,14 +48,14 @@ impl std::cmp::PartialEq for User {
|
||||
pub struct DbUser {
|
||||
user_id: i64,
|
||||
user_name: String,
|
||||
last_active: Option<i64>
|
||||
last_active: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct PubUser {
|
||||
user_id: i64,
|
||||
user_name: String,
|
||||
last_active: Option<DateTime<Utc>>
|
||||
last_active: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@@ -59,15 +63,14 @@ async fn delete_user(user_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM users WHERE user_id = ?",
|
||||
user_id
|
||||
)
|
||||
sqlx::query!("DELETE FROM users WHERE user_id = ?", user_id)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
@@ -79,7 +82,9 @@ async fn reset_password(user_id: i64, password: String) -> Result<(), ServerFnEr
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
@@ -96,15 +101,21 @@ pub fn RenderUser(refresh_user_list: Action<(), ()>, user: PubUser) -> impl Into
|
||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||
let (time_ago, set_time_ago) = signal(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||
let (time_ago, set_time_ago) = signal(
|
||||
user.last_active
|
||||
.map(|active| format_delta(Utc::now() - active)),
|
||||
);
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
Effect::watch(
|
||||
move || counter.get(),
|
||||
move |_, _, _| {
|
||||
set_time_ago(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||
set_time_ago(
|
||||
user.last_active
|
||||
.map(|active| format_delta(Utc::now() - active)),
|
||||
);
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
let dialog_ref = NodeRef::<leptos::html::Dialog>::new();
|
||||
@@ -220,23 +231,27 @@ async fn list_users() -> Result<Vec<PubUser>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
let users = sqlx::query_as!(
|
||||
DbUser,
|
||||
"SELECT user_id, user_name, last_active FROM users"
|
||||
)
|
||||
let users = sqlx::query_as!(DbUser, "SELECT user_id, user_name, last_active FROM users")
|
||||
.fetch(&pool)
|
||||
.map(|user| user.map(|u| PubUser {
|
||||
user_id: u.user_id,
|
||||
user_name: u.user_name,
|
||||
last_active: u.last_active.map(|ts| DateTime::from_timestamp(ts, 0)).flatten()
|
||||
}))
|
||||
.map(|user| {
|
||||
user.map(|u| PubUser {
|
||||
user_id: u.user_id,
|
||||
user_name: u.user_name,
|
||||
last_active: u
|
||||
.last_active
|
||||
.map(|ts| DateTime::from_timestamp(ts, 0))
|
||||
.flatten(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await;
|
||||
|
||||
@@ -250,7 +265,9 @@ async fn add_user(name: String, password: String) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use std::{net::SocketAddrV4, process::ExitCode};
|
||||
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use axum::{extract::{FromRef, Path, State}, response::IntoResponse, Router, routing::get};
|
||||
use axum::{
|
||||
extract::{FromRef, Path, State},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use tokio::signal;
|
||||
|
||||
use sparse_server::app::*;
|
||||
@@ -11,8 +16,10 @@ use sparse_server::app::*;
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub(crate) mod beacon_binaries {
|
||||
pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX"));
|
||||
pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
||||
pub const WINDOWS_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS"));
|
||||
pub const FREEBSD_INSTALLER: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
||||
pub const WINDOWS_INSTALLER: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS"));
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -21,7 +28,11 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
|
||||
"linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-installer",
|
||||
"freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-installer",
|
||||
"windows" => "target/x86_64-pc-windows-gnu/debug/sparse-unix-installer",
|
||||
other => return Err(crate::error::Error::Generic(format!("unknown beacon type: {other}"))),
|
||||
other => {
|
||||
return Err(crate::error::Error::Generic(format!(
|
||||
"unknown beacon type: {other}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
@@ -32,20 +43,22 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
|
||||
"linux" => Ok(beacon_binaries::LINUX_INSTALLER.to_owned()),
|
||||
"windows" => Ok(beacon_binaries::WINDOWS_INSTALLER.to_owned()),
|
||||
"freebsd" => Ok(beacon_binaries::FREEBSD_INSTALLER.to_owned()),
|
||||
other => Err(crate::error::Error::Generic(format!("unknown beacon type: {other}")))
|
||||
other => Err(crate::error::Error::Generic(format!(
|
||||
"unknown beacon type: {other}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRef, Clone, Debug)]
|
||||
pub struct AppState {
|
||||
db: SqlitePool,
|
||||
leptos_options: leptos::config::LeptosOptions
|
||||
leptos_options: leptos::config::LeptosOptions,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn download_beacon_installer(
|
||||
Path(template_id): Path<i64>,
|
||||
State(db): State<AppState>
|
||||
State(db): State<AppState>,
|
||||
) -> Result<impl IntoResponse, crate::error::Error> {
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use sparse_actions::payload_types::{Parameters_t, XOR_KEY};
|
||||
@@ -53,15 +66,17 @@ pub async fn download_beacon_installer(
|
||||
let mut parameters_buffer = vec![0u8; std::mem::size_of::<Parameters_t>()];
|
||||
let _ = OsRng.try_fill_bytes(&mut parameters_buffer);
|
||||
|
||||
let parameters: &mut Parameters_t = unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) };
|
||||
let parameters: &mut Parameters_t =
|
||||
unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) };
|
||||
|
||||
let template = sqlx::query!(
|
||||
r"SELECT operating_system, source_ip, source_mac, source_mode, source_netmask,
|
||||
source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key
|
||||
source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key,
|
||||
source_interface
|
||||
FROM beacon_template JOIN beacon_listener"
|
||||
)
|
||||
.fetch_one(&db.db)
|
||||
.await?;
|
||||
.fetch_one(&db.db)
|
||||
.await?;
|
||||
|
||||
let dest_ip = template.public_ip.parse::<std::net::Ipv4Addr>()?;
|
||||
let src_ip = template.source_ip.parse::<std::net::Ipv4Addr>()?;
|
||||
@@ -79,19 +94,29 @@ pub async fn download_beacon_installer(
|
||||
.map(|by| u8::from_str_radix(by, 16))
|
||||
.collect::<Result<Vec<u8>, _>>()
|
||||
.map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string()))
|
||||
.and_then(
|
||||
|bytes| bytes.try_into().map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string()))
|
||||
)?;
|
||||
.and_then(|bytes| {
|
||||
bytes.try_into().map_err(|_| {
|
||||
crate::error::Error::Generic("Could not parse source MAC address".to_string())
|
||||
})
|
||||
})?;
|
||||
|
||||
let src_octets = src_ip.octets();
|
||||
match (template.source_mode.as_deref(), template.source_netmask, template.source_gateway) {
|
||||
match (
|
||||
template.source_mode.as_deref(),
|
||||
template.source_netmask,
|
||||
template.source_gateway,
|
||||
) {
|
||||
(Some("custom"), Some(nm), Some(ip)) => unsafe {
|
||||
let gateway = ip.parse::<std::net::Ipv4Addr>()?;
|
||||
let gw_octets = gateway.octets();
|
||||
|
||||
parameters.source_ip.custom_networking.mode = 0;
|
||||
parameters.source_ip.custom_networking.source_mac.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.custom_networking.netmask = nm as u16;
|
||||
parameters
|
||||
.source_ip
|
||||
.custom_networking
|
||||
.source_mac
|
||||
.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.custom_networking.source_ip.a = src_octets[0];
|
||||
parameters.source_ip.custom_networking.source_ip.b = src_octets[1];
|
||||
parameters.source_ip.custom_networking.source_ip.c = src_octets[2];
|
||||
@@ -100,17 +125,31 @@ pub async fn download_beacon_installer(
|
||||
parameters.source_ip.custom_networking.gateway.b = gw_octets[1];
|
||||
parameters.source_ip.custom_networking.gateway.c = gw_octets[2];
|
||||
parameters.source_ip.custom_networking.gateway.d = gw_octets[3];
|
||||
}
|
||||
|
||||
if let Some(intf) = &template.source_interface {
|
||||
parameters.source_ip.custom_networking.interface[..intf.len()]
|
||||
.copy_from_slice(&intf[..]);
|
||||
parameters.source_ip.custom_networking.interface_len = intf.len() as u8;
|
||||
} else {
|
||||
parameters.source_ip.custom_networking.interface_len = 0;
|
||||
}
|
||||
},
|
||||
(Some("host"), _, _) => unsafe {
|
||||
parameters.source_ip.use_host_networking.mode = 1;
|
||||
parameters.source_ip.use_host_networking.source_mac.copy_from_slice(&src_mac[..]);
|
||||
parameters
|
||||
.source_ip
|
||||
.use_host_networking
|
||||
.source_mac
|
||||
.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.use_host_networking.source_ip.a = src_octets[0];
|
||||
parameters.source_ip.use_host_networking.source_ip.b = src_octets[1];
|
||||
parameters.source_ip.use_host_networking.source_ip.c = src_octets[2];
|
||||
parameters.source_ip.use_host_networking.source_ip.d = src_octets[3];
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(crate::error::Error::Generic("Could not parse host networking configuration".to_string()));
|
||||
return Err(crate::error::Error::Generic(
|
||||
"Could not parse host networking configuration".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +183,7 @@ pub async fn download_beacon_installer(
|
||||
|
||||
Ok((
|
||||
[
|
||||
(
|
||||
header::CONTENT_TYPE,
|
||||
"application/octet-stream".to_string()
|
||||
),
|
||||
(header::CONTENT_TYPE, "application/octet-stream".to_string()),
|
||||
(
|
||||
header::CONTENT_DISPOSITION,
|
||||
format!(
|
||||
@@ -157,17 +193,17 @@ pub async fn download_beacon_installer(
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
[
|
||||
&installer_bytes[..],
|
||||
¶meters_bytes[..]
|
||||
].concat()
|
||||
[&installer_bytes[..], ¶meters_bytes[..]].concat(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyhow::Result<ExitCode> {
|
||||
pub async fn serve_web(
|
||||
management_address: SocketAddrV4,
|
||||
db: SqlitePool,
|
||||
) -> anyhow::Result<ExitCode> {
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let routes = generate_route_list(App);
|
||||
@@ -183,7 +219,7 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh
|
||||
|
||||
let state = AppState {
|
||||
leptos_options: leptos_options.clone(),
|
||||
db: db.clone()
|
||||
db: db.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
@@ -198,20 +234,26 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh
|
||||
{
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
}
|
||||
},
|
||||
)
|
||||
.fallback(leptos_axum::file_and_error_handler::<leptos::config::LeptosOptions, _>(shell))
|
||||
.fallback(leptos_axum::file_and_error_handler::<
|
||||
leptos::config::LeptosOptions,
|
||||
_,
|
||||
>(shell))
|
||||
.with_state(state)
|
||||
.layer(
|
||||
tower::ServiceBuilder::new()
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.layer(compression_layer)
|
||||
.layer(compression_layer),
|
||||
);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let management_listener = tokio::net::TcpListener::bind(&management_address).await?;
|
||||
tracing::info!("management interface listening on http://{}", &management_address);
|
||||
tracing::info!(
|
||||
"management interface listening on http://{}",
|
||||
&management_address
|
||||
);
|
||||
|
||||
axum::serve(management_listener, app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
|
||||
Reference in New Issue
Block a user