From a10bdce38f9b5c94e1240c84096067b1d7a8aa92 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:15:09 -0700 Subject: [PATCH] JWT Authentication --- unshell-gui/Cargo.lock | 100 +++++ unshell-gui/Cargo.toml | 1 + unshell-gui/assets/sw.js | 41 +- unshell-gui/src/app/app.rs | 11 +- unshell-gui/src/app/mod.rs | 10 +- unshell-gui/src/auth/mod.rs | 120 ++++-- unshell-gui/src/config/mod.rs | 230 +--------- unshell-gui/src/config/mod~.rs | 230 ++++++++++ unshell-gui/src/flowchart/flowchart.rs | 12 +- unshell-gui/src/lib.rs | 6 +- unshell-server/Cargo.lock | 568 +++++++++++++++++++++---- unshell-server/Cargo.toml | 19 +- unshell-server/src/app.rs | 29 ++ unshell-server/src/auth.rs | 124 ++++++ unshell-server/src/lib.rs | 21 + unshell-server/src/main.rs | 208 +-------- unshell-server/src/structs.rs | 47 ++ unshell-server/src/userdata.rs | 4 + 18 files changed, 1198 insertions(+), 583 deletions(-) create mode 100644 unshell-gui/src/config/mod~.rs create mode 100644 unshell-server/src/app.rs create mode 100644 unshell-server/src/auth.rs create mode 100644 unshell-server/src/lib.rs create mode 100644 unshell-server/src/structs.rs create mode 100644 unshell-server/src/userdata.rs diff --git a/unshell-gui/Cargo.lock b/unshell-gui/Cargo.lock index 4068bac..75c87d7 100644 --- a/unshell-gui/Cargo.lock +++ b/unshell-gui/Cargo.lock @@ -75,6 +75,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arboard" version = "3.6.1" @@ -292,6 +301,19 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -952,6 +974,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -2353,6 +2399,7 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" name = "unshell-gui" version = "0.1.0" dependencies = [ + "chrono", "eframe", "egui", "egui_extras", @@ -2748,12 +2795,65 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/unshell-gui/Cargo.toml b/unshell-gui/Cargo.toml index e5fdf4d..be2cebf 100644 --- a/unshell-gui/Cargo.toml +++ b/unshell-gui/Cargo.toml @@ -40,6 +40,7 @@ wasm-bindgen-futures = "0.4.50" wasm-bindgen = "0.2.106" web-sys = "0.3.70" # to access the DOM (to hide the loading text) serde_json = "1.0.145" +chrono = "0.4.42" [profile.release] opt-level = 2 # fast and small wasm diff --git a/unshell-gui/assets/sw.js b/unshell-gui/assets/sw.js index be7fe28..eaa1025 100644 --- a/unshell-gui/assets/sw.js +++ b/unshell-gui/assets/sw.js @@ -31,34 +31,45 @@ self.addEventListener("fetch", function (e) { // return xmlHttp.responseText; // } -export function httpGet(theUrl, callback) { +function startHttpRequest(callback) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState !== 4) return; - if (xmlHttp.status == 200) callback(xmlHttp.responseText); - else alert("Error " + xmlHttp.status + ", " + xmlHttp.responseText); + if (xmlHttp.status == 200) callback(true, xmlHttp.responseText); + else callback(false, xmlHttp.responseText); }; + return xmlHttp; +} + +export function httpGet(theUrl, callback) { + var xmlHttp = startHttpRequest(callback); xmlHttp.open("GET", theUrl, true); // true for asynchronous xmlHttp.setRequestHeader("Content-Type", "application/json"); xmlHttp.send(null); } export function httpPost(url, body, callback) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.onreadystatechange = function () { - if (xmlHttp.readyState !== 4) return; - - if (xmlHttp.status === 200) { - // var json = JSON.parse(xhr.responseText); - callback(xmlHttp.responseText); - } else { - alert("Error " + xmlHttp.status + ", " + xmlHttp.responseText); - } - }; - + var xmlHttp = startHttpRequest(callback); xmlHttp.open("POST", url, true); xmlHttp.setRequestHeader("Content-Type", "application/json"); // var data = JSON.stringify({ email: "[email protected]", password: "101010" }); xmlHttp.send(body); } + +export function httpGetAuth(theUrl, auth, callback) { + var xmlHttp = startHttpRequest(callback); + xmlHttp.open("GET", theUrl, true); // true for asynchronous + xmlHttp.setRequestHeader("Content-Type", "application/json"); + xmlHttp.setRequestHeader("authorization", auth); + xmlHttp.send(null); +} + +export function httpPostAuth(url, auth, body, callback) { + var xmlHttp = startHttpRequest(callback); + xmlHttp.open("POST", url, true); + xmlHttp.setRequestHeader("Content-Type", "application/json"); + xmlHttp.setRequestHeader("authorization", auth); + // var data = JSON.stringify({ email: "[email protected]", password: "101010" }); + xmlHttp.send(body); +} diff --git a/unshell-gui/src/app/app.rs b/unshell-gui/src/app/app.rs index 72a8721..fe744de 100644 --- a/unshell-gui/src/app/app.rs +++ b/unshell-gui/src/app/app.rs @@ -1,16 +1,12 @@ use egui::Frame; use egui_tiles::Tree; -use crate::{ - app::{AppState, windows::WindowWrapper}, - auth::Auth, -}; +use crate::app::{AppState, windows::WindowWrapper}; /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] // if we add new fields, give them default values when deserializing old state pub struct TemplateApp { - auth: Auth, // tab: Tab, state: AppState, tree: Tree, @@ -19,7 +15,6 @@ pub struct TemplateApp { impl Default for TemplateApp { fn default() -> Self { Self { - auth: Auth::default(), state: AppState::default(), tree: egui_tiles::Tree::new_horizontal("tree_root", Vec::new()), } @@ -50,10 +45,10 @@ impl eframe::App for TemplateApp { /// Called each time the UI needs repainting, which may be many times per second. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - if !self.auth.logged_in() { + if !self.state.auth.logged_in() { egui::CentralPanel::default() .frame(Frame::central_panel(&ctx.style()).inner_margin(0)) - .show(ctx, |ui| self.auth.update(ui)); + .show(ctx, |ui| self.state.auth.update(ui)); } else { egui::TopBottomPanel::top("tab_panel").show(ctx, |ui| { // The top panel is often a good place for a menu bar: diff --git a/unshell-gui/src/app/mod.rs b/unshell-gui/src/app/mod.rs index 675480a..7ce0408 100644 --- a/unshell-gui/src/app/mod.rs +++ b/unshell-gui/src/app/mod.rs @@ -3,12 +3,14 @@ mod windows; use std::collections::HashMap; -use crate::{app::windows::WindowWrapper, config::Config, flowchart::FlowChart}; +use crate::{app::windows::WindowWrapper, auth::Auth, config::Config, flowchart::FlowChart}; pub use app::TemplateApp; use egui_tiles::{TileId, Tree}; #[derive(Default, serde::Deserialize, serde::Serialize)] -struct AppState { +pub struct AppState { + pub auth: Auth, + pub open_windows: HashMap, pub flowchart: FlowChart, @@ -64,7 +66,7 @@ impl AppState { } #[derive(Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq, Eq, Hash)] -enum AppWindow { +pub enum AppWindow { Flowchart, Config, } @@ -73,7 +75,7 @@ impl AppWindow { fn update(&self, state: &mut AppState, ui: &mut egui::Ui) { match self { AppWindow::Flowchart => state.flowchart.paint(ui), - AppWindow::Config => state.config.update(ui), + AppWindow::Config => state.config.update(&mut state.auth, ui), } } } diff --git a/unshell-gui/src/auth/mod.rs b/unshell-gui/src/auth/mod.rs index 2ed4387..244efa1 100644 --- a/unshell-gui/src/auth/mod.rs +++ b/unshell-gui/src/auth/mod.rs @@ -1,5 +1,7 @@ -use std::{collections::HashMap, sync::Arc}; +use serde_json::json; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use chrono::Utc; use egui::{Align2, Area, Frame, Order, Sense, UiKind, Vec2, mutex::Mutex}; use wasm_bindgen::prelude::Closure; @@ -12,18 +14,20 @@ pub struct Auth { // UI Stuff username: String, + #[serde(skip)] password: String, show_password: bool, } -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)] struct Token { - access_token: String, - token_type: String, + expiration: u128, + token: String, } #[derive(Debug, PartialEq, Eq)] enum AuthState { + Unset, NotLoggedIn, RequestSent, Authorised(Token), @@ -32,25 +36,40 @@ enum AuthState { impl Default for AuthState { fn default() -> Self { - Self::NotLoggedIn + Self::Unset } } impl Auth { + /// Refresh the authentication state pub fn logged_in(&mut self) -> bool { - if self.token.is_some() { - true - } else { - match *self.auth_state.lock() { - AuthState::Authorised(ref token) => { - self.token = Some(token.clone()); - true - } - _ => false, - } - } + match (self.token.is_some(), &*self.auth_state.lock()) { + // The client is actually authorized + (true, AuthState::Authorised(_)) => true, - // self.auth_state.lock().eq(&AuthState::Authorised) + // If the user has just reloaded the session, + // the AuthState is not automatically set by any other process + (true, AuthState::Unset) => true, + + // If the authentication state has been updated to unauthorized, delete the token + (true, _) => { + self.token = None; + false + } + + // If the authentication state has been updated to authorized, set the token + (false, AuthState::Authorised(token)) => { + self.token = Some(token.clone()); + + // Also clear the password because it is bad to store it + self.password.clear(); + + true + } + + // The client is actually unauthorized + (false, _) => false, + } } pub fn update(&mut self, ui: &mut egui::Ui) { @@ -82,38 +101,61 @@ impl Auth { // ui.toggle_value(&mut self.show_password, "Show"); }); - if ui.button("Login").clicked() { - let json = serde_json::to_string(&HashMap::from([ - ("client_id", self.username.clone()), - ("client_secret", self.password.clone()), - ])) - .unwrap(); + ui.horizontal(|ui| { + if ui.button("Login").clicked() { + // Try to + ui.ctx().request_repaint_after(Duration::from_millis(500)); - let state = self.auth_state.clone(); - *(state.lock()) = AuthState::RequestSent; + let state = self.auth_state.clone(); - crate::httpPost( - "/auth", - &json, - Closure::once_into_js(move |response: String| { - *(state.lock()) = - if let Ok(token) = serde_json::from_str::(&response) { - AuthState::Authorised(token) + crate::httpPost( + "/auth", + &json!({ + "username": self.username.clone(), + "password": self.password.clone() + }) + .to_string(), + Closure::once_into_js(move |ok: bool, response: String| { + *(state.lock()) = if ok { + if let Ok(token) = serde_json::from_str::(&response) + { + AuthState::Authorised(token) + } else { + AuthState::Error("Malformed Response".into()) + } } else { - AuthState::Error("Malformed Response".into()) + AuthState::Error(response) } + }), + ); - // self.logged_in - // .store(true, std::sync::atomic::Ordering::Relaxed); - // self.logged_in(); - }), - ); - } + *(self.auth_state.lock()) = AuthState::RequestSent; + } + + ui.label(format!("{:?}", self.auth_state.lock())); + }); }); }); // }); // }); } + + pub fn test(&self) { + if let Some(ref token) = self.token { + let state = self.auth_state.clone(); + crate::httpGetAuth( + "/api/test1234/kjhejwer/kwherjwer/iuwehrhiwer/wiuerhjwer", + format!("Bearer {}", token.token), + Closure::once_into_js(move |ok: bool, response: String| { + if ok { + crate::log(&response); + } else { + *(state.lock()) = AuthState::Error(response); + } + }), + ); + } + } } impl Default for Auth { diff --git a/unshell-gui/src/config/mod.rs b/unshell-gui/src/config/mod.rs index 096d5c5..fc72820 100644 --- a/unshell-gui/src/config/mod.rs +++ b/unshell-gui/src/config/mod.rs @@ -1,230 +1,12 @@ -use egui::{TextStyle, Ui}; -use egui_extras::{Column, TableBuilder}; +use crate::auth::Auth; -#[derive(serde::Deserialize, serde::Serialize)] -pub struct Config { - state: ConfigState, - current_payloads: Vec, -} - -#[derive(serde::Deserialize, serde::Serialize)] -enum ConfigState { - Base, - NewConfig(PayloadConfig), - EditConfig(usize, PayloadConfig), -} - -impl Default for Config { - fn default() -> Self { - Self { - state: ConfigState::Base, - current_payloads: vec![PayloadConfig { - name: "Test".to_string(), - components: vec!["server".to_string()], - // runtimes: Vec::new(), - }], - } - } -} +#[derive(Default, serde::Deserialize, serde::Serialize)] +pub struct Config {} impl Config { - // pub fn title(&self) -> &str { - // match self.state { - // ConfigState::Base => "Config", - // ConfigState::NewConfig(..) => "Config/New", - // ConfigState::EditConfig(..) => "Config/Edit", - // } - // } - - pub fn update(&mut self, ui: &mut Ui) { - match &mut self.state { - ConfigState::Base => self.table_ui(ui), - ConfigState::EditConfig(_, config) => { - ui.heading("Edit Payload"); - match Self::edit_ui(ui, config) { - Some(true) => { - if let ConfigState::EditConfig(n, config) = - std::mem::replace(&mut self.state, ConfigState::Base) - { - self.current_payloads[n] = config; - } - } - Some(false) => { - self.state = ConfigState::Base; - } - _ => {} - } - } - ConfigState::NewConfig(config) => { - ui.heading("Edit Payload"); - match Self::edit_ui(ui, config) { - Some(true) => { - if let ConfigState::NewConfig(config) = - std::mem::replace(&mut self.state, ConfigState::Base) - { - self.current_payloads.push(config); - } - } - Some(false) => { - self.state = ConfigState::Base; - } - _ => {} - } - } - } - } - - fn table_ui(&mut self, ui: &mut Ui) { - ui.horizontal(|ui| { - ui.heading("Payloads"); - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("New").clicked() { - self.state = ConfigState::NewConfig(PayloadConfig::new()); - } - }); - }); - - let body_text_size = TextStyle::Body.resolve(ui.style()).size; - use egui_extras::{Size, StripBuilder}; - StripBuilder::new(ui) - .size(Size::remainder().at_least(100.0)) // for the table - .size(Size::exact(body_text_size)) // for the source code link - .vertical(|mut strip| { - strip.cell(|ui| { - egui::ScrollArea::horizontal().show(ui, |ui| { - let table = TableBuilder::new(ui) - .striped(true) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::auto().resizable(false)) - .column(Column::remainder()) - .column(Column::remainder()) - .column(Column::remainder()) - .column(Column::auto().resizable(false)) - .min_scrolled_height(0.0) - .sense(egui::Sense::click()); - - table - .header(20., |mut header| { - header.col(|ui| { - ui.strong("#"); - }); - header.col(|ui| { - ui.strong("Name"); - }); - header.col(|ui| { - ui.strong("Components"); - }); - header.col(|ui| { - ui.strong("Runtimes"); - }); - header.col(|_| {}); - }) - .body(|mut body| { - for i in 0..self.current_payloads.len() { - // let runtime = self.current_runtimes - - body.row(18., |mut row| { - row.col(|ui| { - ui.label(i.to_string()); - }); - row.col(|ui| { - ui.label(self.current_payloads[i].name.clone()); - }); - row.col(|ui| { - ui.label(format!( - "{:?}", - self.current_payloads[i].components.clone() - )); - }); - row.col(|ui| { - ui.label("A"); - }); - row.col(|ui| { - if ui.button("Edit").clicked() { - self.state = ConfigState::EditConfig( - i, - self.current_payloads[i].clone(), - ); - } - // if ui.button("Delete").clicked() { - // self.state = ConfigState::EditConfig( - // i, - // self.current_payloads[i].clone(), - // ); - // } - }); - - if row.response().clicked() { - self.state = ConfigState::EditConfig( - i, - self.current_payloads[i].clone(), - ); - } - }); - } - }); - }); - }); - }); - } - - fn edit_ui(ui: &mut Ui, config: &mut PayloadConfig) -> Option { - ui.horizontal(|ui| { - ui.label("Name"); - ui.text_edit_singleline(&mut config.name); - }); - - ui.horizontal(|ui| { - ui.label("Components: "); - - for component in vec!["client", "server"] { - let enabled = config - .components - .iter() - .enumerate() - .find(|s| s.1.eq(component)); - if ui.selectable_label(enabled.is_some(), component).clicked() { - if let Some((i, _)) = enabled { - let _ = config.components.remove(i); - } else { - config.components.push(component.to_string()); - } - } - } - }); - - let mut ret = None; - ui.horizontal(|ui| { - ret = if ui.button("Back").clicked() { - Some(false) - } else if ui.button("Save").clicked() { - Some(true) - } else { - None - }; - }); - - ret - - // return None; - } -} - -#[derive(Clone, serde::Deserialize, serde::Serialize)] -struct PayloadConfig { - name: String, - components: Vec, - // runtimes: Vec, -} - -impl PayloadConfig { - pub fn new() -> Self { - Self { - name: "New Payload".to_string(), - components: Vec::new(), - // runtimes: Vec::new(), + pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) { + if ui.button("Test").clicked() { + auth.test(); } } } diff --git a/unshell-gui/src/config/mod~.rs b/unshell-gui/src/config/mod~.rs new file mode 100644 index 0000000..096d5c5 --- /dev/null +++ b/unshell-gui/src/config/mod~.rs @@ -0,0 +1,230 @@ +use egui::{TextStyle, Ui}; +use egui_extras::{Column, TableBuilder}; + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct Config { + state: ConfigState, + current_payloads: Vec, +} + +#[derive(serde::Deserialize, serde::Serialize)] +enum ConfigState { + Base, + NewConfig(PayloadConfig), + EditConfig(usize, PayloadConfig), +} + +impl Default for Config { + fn default() -> Self { + Self { + state: ConfigState::Base, + current_payloads: vec![PayloadConfig { + name: "Test".to_string(), + components: vec!["server".to_string()], + // runtimes: Vec::new(), + }], + } + } +} + +impl Config { + // pub fn title(&self) -> &str { + // match self.state { + // ConfigState::Base => "Config", + // ConfigState::NewConfig(..) => "Config/New", + // ConfigState::EditConfig(..) => "Config/Edit", + // } + // } + + pub fn update(&mut self, ui: &mut Ui) { + match &mut self.state { + ConfigState::Base => self.table_ui(ui), + ConfigState::EditConfig(_, config) => { + ui.heading("Edit Payload"); + match Self::edit_ui(ui, config) { + Some(true) => { + if let ConfigState::EditConfig(n, config) = + std::mem::replace(&mut self.state, ConfigState::Base) + { + self.current_payloads[n] = config; + } + } + Some(false) => { + self.state = ConfigState::Base; + } + _ => {} + } + } + ConfigState::NewConfig(config) => { + ui.heading("Edit Payload"); + match Self::edit_ui(ui, config) { + Some(true) => { + if let ConfigState::NewConfig(config) = + std::mem::replace(&mut self.state, ConfigState::Base) + { + self.current_payloads.push(config); + } + } + Some(false) => { + self.state = ConfigState::Base; + } + _ => {} + } + } + } + } + + fn table_ui(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.heading("Payloads"); + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("New").clicked() { + self.state = ConfigState::NewConfig(PayloadConfig::new()); + } + }); + }); + + let body_text_size = TextStyle::Body.resolve(ui.style()).size; + use egui_extras::{Size, StripBuilder}; + StripBuilder::new(ui) + .size(Size::remainder().at_least(100.0)) // for the table + .size(Size::exact(body_text_size)) // for the source code link + .vertical(|mut strip| { + strip.cell(|ui| { + egui::ScrollArea::horizontal().show(ui, |ui| { + let table = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto().resizable(false)) + .column(Column::remainder()) + .column(Column::remainder()) + .column(Column::remainder()) + .column(Column::auto().resizable(false)) + .min_scrolled_height(0.0) + .sense(egui::Sense::click()); + + table + .header(20., |mut header| { + header.col(|ui| { + ui.strong("#"); + }); + header.col(|ui| { + ui.strong("Name"); + }); + header.col(|ui| { + ui.strong("Components"); + }); + header.col(|ui| { + ui.strong("Runtimes"); + }); + header.col(|_| {}); + }) + .body(|mut body| { + for i in 0..self.current_payloads.len() { + // let runtime = self.current_runtimes + + body.row(18., |mut row| { + row.col(|ui| { + ui.label(i.to_string()); + }); + row.col(|ui| { + ui.label(self.current_payloads[i].name.clone()); + }); + row.col(|ui| { + ui.label(format!( + "{:?}", + self.current_payloads[i].components.clone() + )); + }); + row.col(|ui| { + ui.label("A"); + }); + row.col(|ui| { + if ui.button("Edit").clicked() { + self.state = ConfigState::EditConfig( + i, + self.current_payloads[i].clone(), + ); + } + // if ui.button("Delete").clicked() { + // self.state = ConfigState::EditConfig( + // i, + // self.current_payloads[i].clone(), + // ); + // } + }); + + if row.response().clicked() { + self.state = ConfigState::EditConfig( + i, + self.current_payloads[i].clone(), + ); + } + }); + } + }); + }); + }); + }); + } + + fn edit_ui(ui: &mut Ui, config: &mut PayloadConfig) -> Option { + ui.horizontal(|ui| { + ui.label("Name"); + ui.text_edit_singleline(&mut config.name); + }); + + ui.horizontal(|ui| { + ui.label("Components: "); + + for component in vec!["client", "server"] { + let enabled = config + .components + .iter() + .enumerate() + .find(|s| s.1.eq(component)); + if ui.selectable_label(enabled.is_some(), component).clicked() { + if let Some((i, _)) = enabled { + let _ = config.components.remove(i); + } else { + config.components.push(component.to_string()); + } + } + } + }); + + let mut ret = None; + ui.horizontal(|ui| { + ret = if ui.button("Back").clicked() { + Some(false) + } else if ui.button("Save").clicked() { + Some(true) + } else { + None + }; + }); + + ret + + // return None; + } +} + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +struct PayloadConfig { + name: String, + components: Vec, + // runtimes: Vec, +} + +impl PayloadConfig { + pub fn new() -> Self { + Self { + name: "New Payload".to_string(), + components: Vec::new(), + // runtimes: Vec::new(), + } + } +} diff --git a/unshell-gui/src/flowchart/flowchart.rs b/unshell-gui/src/flowchart/flowchart.rs index 5bc734f..2fb03f5 100644 --- a/unshell-gui/src/flowchart/flowchart.rs +++ b/unshell-gui/src/flowchart/flowchart.rs @@ -93,6 +93,12 @@ impl FlowChart { } pub fn paint(&mut self, ui: &mut Ui) { + if ui.button("Arrange").clicked() { + for _ in 0..1_000 { + self.force(0.1); + } + } + let scene = Scene::new() // .max_inner_size([350.0, 1000.0]) .zoom_range(0.1..=2.0); @@ -135,11 +141,5 @@ impl FlowChart { if response.double_clicked() { self.scene_rect = inner_rect; } - - if ui.button("Arrange").clicked() { - for _ in 0..1_000 { - self.force(0.1); - } - } } } diff --git a/unshell-gui/src/lib.rs b/unshell-gui/src/lib.rs index 61890cc..eb3cd98 100644 --- a/unshell-gui/src/lib.rs +++ b/unshell-gui/src/lib.rs @@ -21,8 +21,10 @@ extern "C" { #[wasm_bindgen(module = "/assets/sw.js")] extern "C" { - pub fn httpGet(url: &str, callback: JsValue); - pub fn httpPost(url: &str, data: &str, callback: JsValue); + pub fn httpGet(url: &str, ok_callback: JsValue); + pub fn httpPost(url: &str, data: &str, ok_callback: JsValue); + pub fn httpGetAuth(url: &str, auth: String, ok_callback: JsValue); + pub fn httpPostAuth(url: &str, auth: String, data: &str, ok_callback: JsValue); } // } diff --git a/unshell-server/Cargo.lock b/unshell-server/Cargo.lock index efd7ec7..80cf506 100644 --- a/unshell-server/Cargo.lock +++ b/unshell-server/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +22,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -126,6 +146,45 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bcrypt" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +dependencies = [ + "base64", + "blowfish", + "getrandom 0.3.4", + "subtle", + "zeroize", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -141,18 +200,61 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.48" @@ -171,6 +273,41 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cmake" version = "0.1.54" @@ -180,6 +317,12 @@ dependencies = [ "cc", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -189,6 +332,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.7" @@ -335,6 +493,18 @@ dependencies = [ "http", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + [[package]] name = "http" version = "1.4.0" @@ -380,6 +550,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -417,6 +596,40 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding 0.3.3", + "generic-array", +] + [[package]] name = "itoa" version = "1.0.15" @@ -460,18 +673,22 @@ dependencies = [ "simple_asn1", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -487,15 +704,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - [[package]] name = "matchit" version = "0.8.4" @@ -525,15 +733,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -631,6 +830,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -655,6 +863,26 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -664,13 +892,34 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -735,7 +984,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -786,12 +1035,14 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "lazy_static", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -815,7 +1066,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -846,6 +1097,51 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "static_init" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bae1df58c5fea7502e8e352ec26b5579f6178e1fdb311e088580c980dee25ed" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases 0.2.1", + "libc", + "parking_lot", + "parking_lot_core", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f" +dependencies = [ + "cfg_aliases 0.1.1", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.111" @@ -880,16 +1176,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", + "syn 2.0.111", ] [[package]] @@ -948,7 +1235,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -987,21 +1274,9 @@ checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.35" @@ -1009,36 +1284,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -1053,18 +1298,60 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unshell-crypt" +version = "0.1.0" +dependencies = [ + "aes", + "block-padding 0.4.2", + "cbc", + "getrandom 0.3.4", + "hex", + "hex-literal", + "regex", + "sha2", +] + +[[package]] +name = "unshell-lib" +version = "0.0.0" +dependencies = [ + "bincode", + "chrono", + "crossbeam-channel", + "libc", + "libloading", + "rand", + "serde", + "serde_json", + "unshell-obfuscate", +] + +[[package]] +name = "unshell-obfuscate" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "unshell-crypt", +] + [[package]] name = "unshell-server" version = "0.1.0" dependencies = [ "axum", "axum-extra", + "bcrypt", + "chrono", "jsonwebtoken", "serde", "serde_json", + "static_init", "tokio", - "tracing", - "tracing-subscriber", + "unshell-lib", + "unshell-obfuscate", ] [[package]] @@ -1074,10 +1361,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "valuable" -version = "0.1.1" +name = "unty" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "version_check" @@ -1085,6 +1372,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1132,7 +1425,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -1145,12 +1438,87 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -1240,6 +1608,26 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "zerocopy" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "zeroize" version = "1.8.2" diff --git a/unshell-server/Cargo.toml b/unshell-server/Cargo.toml index 54dcea3..842f99c 100644 --- a/unshell-server/Cargo.toml +++ b/unshell-server/Cargo.toml @@ -3,15 +3,30 @@ name = "unshell-server" version = "0.1.0" edition = "2024" +[features] +log_debug = [] + + + [dependencies] + +unshell-lib = {path = "../unshell-lib"} +unshell-obfuscate = {path = "../unshell-obfuscate"} + axum = "0.8.7" axum-extra = {version="0.12.2", features = ["typed-header"]} jsonwebtoken = {version = "10.2.0", features = ["aws_lc_rs"]} serde = {version = "1.0.228", features = ["derive"]} serde_json = "1.0.145" tokio = {version="1.48.0", features = ["full"] } -tracing = "0.1.43" -tracing-subscriber = {version="0.3.22", features = ["env-filter"] } +bcrypt = "0.17.1" +chrono = "0.4.42" +static_init = "1.0.4" + +# tracing = "0.1.43" +# tracing-subscriber = {version="0.3.22", features = ["env-filter"] } + + # async-trait = "0.1.89" # axum = "0.8.7" # axum-extra = "0.12.2" diff --git a/unshell-server/src/app.rs b/unshell-server/src/app.rs new file mode 100644 index 0000000..d623dc7 --- /dev/null +++ b/unshell-server/src/app.rs @@ -0,0 +1,29 @@ +use crate::{auth, structs::CurrentUser}; +use axum::{ + Extension, Router, + extract::Path, + middleware, + response::IntoResponse, + routing::{get, post}, +}; +use unshell_lib::info; + +pub async fn app() -> Router { + Router::new().route("/auth", post(auth::sign_in)).route( + "/api/{*path}", + get(protected).layer(middleware::from_fn(auth::authorize)), + ) +} + +pub async fn protected( + Path(path): Path, + Extension(currentUser): Extension, +) -> impl IntoResponse { + info!("{}", path); + // Json(UserResponse { + // email: currentUser.email, + // first_name: currentUser.first_name, + // last_name: currentUser.last_name, + // }) + "Test" +} diff --git a/unshell-server/src/auth.rs b/unshell-server/src/auth.rs new file mode 100644 index 0000000..00f0dbf --- /dev/null +++ b/unshell-server/src/auth.rs @@ -0,0 +1,124 @@ +use axum::{ + body::Body, + extract::{Json, Request}, + http::{self, Response, StatusCode}, + middleware::Next, +}; +use bcrypt::{DEFAULT_COST, hash, verify}; +use chrono::Utc; +use jsonwebtoken::{Header, TokenData, Validation, decode, encode}; +use serde_json::{Value, json}; +use unshell_lib::info; + +use crate::{ + EXPIRE_DURATION, JWT_DECODING_KEY, JWT_ENCODING_KEY, + structs::{AuthError, Cliams, CurrentUser, SignInData}, +}; + +pub fn hash_password(password: &str) -> Result { + let hash = hash(password, DEFAULT_COST)?; + Ok(hash) +} + +pub fn encode_jwt(email: String) -> Result<(String, usize), StatusCode> { + let now = Utc::now(); + let exp = (now + EXPIRE_DURATION).timestamp() as usize; + let iat = now.timestamp() as usize; + + let claim = Cliams { iat, exp, email }; + + let token = encode(&Header::default(), &claim, &JWT_ENCODING_KEY) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok((token, exp)) +} + +pub fn decode_jwt(jwt: String) -> Result, StatusCode> { + decode(&jwt, &JWT_DECODING_KEY, &Validation::default()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +pub async fn authorize(mut req: Request, next: Next) -> Result, AuthError> { + let auth_header = req.headers_mut().get(http::header::AUTHORIZATION); + + let auth_header = match auth_header { + Some(header) => header.to_str().map_err(|_| AuthError { + message: "Empty header is not allowed".to_string(), + status_code: StatusCode::FORBIDDEN, + })?, + None => { + return Err(AuthError { + message: "Please add the JWT token to the header".to_string(), + status_code: StatusCode::FORBIDDEN, + }); + } + }; + let mut header = auth_header.split_whitespace(); + + let (_, token) = (header.next(), header.next()); + + let token_data = match decode_jwt(token.unwrap().to_string()) { + Ok(data) => data, + Err(_) => { + return Err(AuthError { + message: "Invalid Session".to_string(), + status_code: StatusCode::UNAUTHORIZED, + }); + } + }; + + // Fetch the user details from the database + let current_user = match retrieve_user_by_email(&token_data.claims.email) { + Some(user) => user, + None => { + return Err(AuthError { + message: "Unauthorized".to_string(), + status_code: StatusCode::UNAUTHORIZED, + }); + } + }; + + req.extensions_mut().insert(current_user); + Ok(next.run(req).await) +} + +pub async fn sign_in(Json(user_data): Json) -> Result, StatusCode> { + // 1. Retrieve user from the database + let user = match retrieve_user_by_email(&user_data.username) { + Some(user) => user, + None => return Err(StatusCode::UNAUTHORIZED), // User not found + }; + + // 2. Compare the password + if !verify(&user_data.password, &user.password_hash) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + // Handle bcrypt errors + { + return Err(StatusCode::UNAUTHORIZED); // Wrong password + } + + info!( + "Authenticated user {} for {}", + user_data.username, EXPIRE_DURATION + ); + + // 3. Generate JWT + let (token, experation) = + encode_jwt(user.email).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + // 4. Return the token + Ok(Json(json!({ + "token": token, + "expiration": experation, + }))) +} + +fn retrieve_user_by_email(_email: &str) -> Option { + let current_user: CurrentUser = CurrentUser { + email: "foo".to_string(), + first_name: "Eze".to_string(), + last_name: "Sunday".to_string(), + password_hash: hash_password("bar").unwrap(), + }; + Some(current_user) +} diff --git a/unshell-server/src/lib.rs b/unshell-server/src/lib.rs new file mode 100644 index 0000000..2c11b43 --- /dev/null +++ b/unshell-server/src/lib.rs @@ -0,0 +1,21 @@ +#![macro_use] + +use chrono::Duration; +use jsonwebtoken::{DecodingKey, EncodingKey}; +use static_init::dynamic; +extern crate unshell_lib; + +pub mod app; +mod auth; +mod structs; +mod userdata; + +static EXPIRE_DURATION: Duration = Duration::seconds(10); + +#[dynamic] +static JWT_SECRET: String = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); + +#[dynamic] +static JWT_ENCODING_KEY: EncodingKey = EncodingKey::from_secret(JWT_SECRET.as_bytes()); +#[dynamic] +static JWT_DECODING_KEY: DecodingKey = DecodingKey::from_secret(JWT_SECRET.as_bytes()); diff --git a/unshell-server/src/main.rs b/unshell-server/src/main.rs index 20cb3ee..dcdf226 100644 --- a/unshell-server/src/main.rs +++ b/unshell-server/src/main.rs @@ -1,200 +1,22 @@ -//! Example JWT authorization/authentication. -//! -//! Run with -//! -//! ```not_rust -//! JWT_SECRET=secret cargo run -p example-jwt -//! ``` +use axum; +use tokio::net::TcpListener; +use unshell_lib::info; -use axum::{ - Json, RequestPartsExt, Router, - extract::FromRequestParts, - http::{StatusCode, request::Parts}, - response::{IntoResponse, Response}, - routing::{get, post}, -}; -use axum_extra::{ - TypedHeader, - headers::{Authorization, authorization::Bearer}, -}; -use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use std::fmt::Display; -use std::sync::LazyLock; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - -// Quick instructions -// -// - get an authorization token: -// -// curl -s \ -// -w '\n' \ -// -H 'Content-Type: application/json' \ -// -d '{"client_id":"foo","client_secret":"bar"}' \ -// http://localhost:3000/authorize -// -// - visit the protected area using the authorized token -// -// curl -s \ -// -w '\n' \ -// -H 'Content-Type: application/json' \ -// -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjEwMDAwMDAwMDAwfQ.M3LAZmrzUkXDC1q5mSzFAs_kJrwuKz3jOoDmjJ0G4gM' \ -// http://localhost:3000/protected -// -// - try to visit the protected area using an invalid token -// -// curl -s \ -// -w '\n' \ -// -H 'Content-Type: application/json' \ -// -H 'Authorization: Bearer blahblahblah' \ -// http://localhost:3000/protected - -static KEYS: LazyLock = LazyLock::new(|| { - let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); - Keys::new(secret.as_bytes()) -}); +use unshell_server::app; #[tokio::main] async fn main() { - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); + unshell_lib::logger::PrettyLogger::init(); - let app = Router::new() - .route("/auth", post(authorize)) - .route("/protected", get(protected)); - - let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") + let listener = TcpListener::bind("127.0.0.1:3000") .await - .unwrap(); - tracing::debug!("listening on {}", listener.local_addr().unwrap()); - axum::serve(listener, app).await.unwrap(); -} - -async fn protected(claims: Claims) -> Result { - // Send the protected data to the user - Ok(format!( - "Welcome to the protected area :)\nYour data:\n{claims}", - )) -} - -async fn authorize(Json(payload): Json) -> Result, AuthError> { - println!("EEE"); - - // Check if the user sent the credentials - if payload.client_id.is_empty() || payload.client_secret.is_empty() { - return Err(AuthError::MissingCredentials); - } - // Here you can check the user credentials from a database - if payload.client_id != "foo" || payload.client_secret != "bar" { - return Err(AuthError::WrongCredentials); - } - let claims = Claims { - sub: "b@b.com".to_owned(), - company: "ACME".to_owned(), - // Mandatory expiry time as UTC timestamp - exp: 2000000000, // May 2033 - }; - // Create the authorization token - let token = encode(&Header::default(), &claims, &KEYS.encoding) - .map_err(|_| AuthError::TokenCreation)?; - - // Send the authorized token - Ok(Json(AuthBody::new(token))) -} - -impl Display for Claims { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Email: {}\nCompany: {}", self.sub, self.company) - } -} - -impl AuthBody { - fn new(access_token: String) -> Self { - Self { - access_token, - token_type: "Bearer".to_string(), - } - } -} - -impl FromRequestParts for Claims -where - S: Send + Sync, -{ - type Rejection = AuthError; - - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - // Extract the token from the authorization header - let TypedHeader(Authorization(bearer)) = parts - .extract::>>() - .await - .map_err(|_| AuthError::InvalidToken)?; - // Decode the user data - let token_data = decode::(bearer.token(), &KEYS.decoding, &Validation::default()) - .map_err(|_| AuthError::InvalidToken)?; - - Ok(token_data.claims) - } -} - -impl IntoResponse for AuthError { - fn into_response(self) -> Response { - let (status, error_message) = match self { - AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"), - AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"), - AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"), - AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"), - }; - let body = Json(json!({ - "error": error_message, - })); - (status, body).into_response() - } -} - -struct Keys { - encoding: EncodingKey, - decoding: DecodingKey, -} - -impl Keys { - fn new(secret: &[u8]) -> Self { - Self { - encoding: EncodingKey::from_secret(secret), - decoding: DecodingKey::from_secret(secret), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct Claims { - sub: String, - company: String, - exp: usize, -} - -#[derive(Debug, Serialize)] -struct AuthBody { - access_token: String, - token_type: String, -} - -#[derive(Debug, Deserialize)] -struct AuthPayload { - client_id: String, - client_secret: String, -} - -#[derive(Debug)] -enum AuthError { - WrongCredentials, - MissingCredentials, - TokenCreation, - InvalidToken, + .expect("Unable to start listener"); + + info!("Listening on {}", listener.local_addr().unwrap()); + + let app = app::app().await; + + axum::serve(listener, app) + .await + .expect("Error serving application"); } diff --git a/unshell-server/src/structs.rs b/unshell-server/src/structs.rs new file mode 100644 index 0000000..22dc14c --- /dev/null +++ b/unshell-server/src/structs.rs @@ -0,0 +1,47 @@ +use axum::{ + Json, + body::Body, + http::{Response, StatusCode}, + response::IntoResponse, +}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[derive(Deserialize)] +pub struct SignInData { + pub username: String, + pub password: String, +} + +#[derive(Debug, Clone)] +pub struct CurrentUser { + pub email: String, + pub first_name: String, + pub last_name: String, + pub password_hash: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Cliams { + // pub exp: u128, + // pub iat: u128, + // + pub exp: usize, + pub iat: usize, + pub email: String, +} + +pub struct AuthError { + pub message: String, + pub status_code: StatusCode, +} + +impl IntoResponse for AuthError { + fn into_response(self) -> Response { + let body = Json(json!({ + "error": self.message, + })); + + (self.status_code, body).into_response() + } +} diff --git a/unshell-server/src/userdata.rs b/unshell-server/src/userdata.rs new file mode 100644 index 0000000..5bd98f9 --- /dev/null +++ b/unshell-server/src/userdata.rs @@ -0,0 +1,4 @@ +pub struct UserData { + username: String, + hash: Vec, +}