From 26d973a1a723fc4407376c6ecfbed3b37174c793 Mon Sep 17 00:00:00 2001 From: Danil Date: Tue, 25 Jun 2024 13:11:51 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=88=D0=B8=D1=84=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.html | 12 ++ src-tauri/Cargo.lock | 18 +- src-tauri/Cargo.toml | 8 +- src-tauri/src/api/aes_enc_dec.rs | 32 +++ src-tauri/src/api/config_tcp.rs | 121 +++++++++++ src-tauri/src/api/multithread_tcp_listener.rs | 146 +++++++++++++ src-tauri/src/enc_dec_file.rs | 49 +++++ src-tauri/src/main.rs | 51 ++++- src-tauri/src/tasks_functions.rs | 50 +---- src/ApiApp.vue | 193 ++++++++++++++++++ src/App.vue | 42 ++-- src/api_components/ConfirmModal.vue | 35 ++++ src/api_components/SettingsModal.vue | 81 ++++++++ src/api_window.js | 4 + 14 files changed, 776 insertions(+), 66 deletions(-) create mode 100644 api.html create mode 100644 src-tauri/src/api/aes_enc_dec.rs create mode 100644 src-tauri/src/api/config_tcp.rs create mode 100644 src-tauri/src/api/multithread_tcp_listener.rs create mode 100644 src-tauri/src/enc_dec_file.rs create mode 100644 src/ApiApp.vue create mode 100644 src/api_components/ConfirmModal.vue create mode 100644 src/api_components/SettingsModal.vue create mode 100644 src/api_window.js diff --git a/api.html b/api.html new file mode 100644 index 0000000..ad5ff6d --- /dev/null +++ b/api.html @@ -0,0 +1,12 @@ + + + + + + Api + + +
+ + + \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b57943f..4088abc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -161,7 +161,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", + "block-padding 0.2.1", "generic-array", ] @@ -180,7 +180,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding", + "block-padding 0.2.1", "cipher", ] @@ -190,6 +190,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[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 = "brotli" version = "3.5.0" @@ -3200,7 +3209,12 @@ dependencies = [ name = "to-do-app" version = "0.0.0" dependencies = [ + "aes", + "block-modes", + "block-padding 0.3.3", + "hex", "magic-crypt", + "rand 0.8.5", "rust-fuzzy-search", "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 88b9afc..5b45152 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,6 +16,12 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" magic-crypt = "3.1.13" rust-fuzzy-search = "0.1.1" +rand = "0.8.5" +hex = "0.4" + +aes = "0.7.5" +block-modes = "0.8.1" +block-padding = "0.3.3" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! @@ -23,6 +29,6 @@ custom-protocol = ["tauri/custom-protocol"] [profile.release] lto = true # Enables link to optimizations -opt-level = "z" # Optimize for binary size +opt-level = "s" # Optimize for binary size strip = true # Remove debug symbols diff --git a/src-tauri/src/api/aes_enc_dec.rs b/src-tauri/src/api/aes_enc_dec.rs new file mode 100644 index 0000000..a80f7fe --- /dev/null +++ b/src-tauri/src/api/aes_enc_dec.rs @@ -0,0 +1,32 @@ +use aes::Aes256; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::Pkcs7; +use rand::Rng; + +type Aes256Cbc = Cbc; + +pub fn encrypt(key: &[u8], data: &str) -> Vec { + let iv = rand::thread_rng().gen::<[u8; 16]>(); // Генерация соли + let cipher = Aes256Cbc::new_from_slices(key, &iv).unwrap(); // Создание шифратора + let ciphertext = cipher.encrypt_vec(data.as_bytes()); // Шифрование строки + let res: &[u8] = &ciphertext[..]; + [&iv, res].concat() // Вывод данных в виде 256-битного массива +} + +pub fn decrypt(key: &[u8], encrypted_data: &[u8]) -> String { + let iv = &encrypted_data[0..16]; // Получение соли из данных + let ciphertext = &encrypted_data[16..]; // Получение текста из данных + + let cipher = Aes256Cbc::new_from_slices(key, iv).unwrap(); // Создание дешифратора + let decrypted_ciphertext = cipher.decrypt_vec(ciphertext).unwrap(); // Дешифрование данных + String::from_utf8(decrypted_ciphertext).unwrap() // Вывод результата в виде строки +} + +/// Примеры +//println!("TEST ENC: {:?}", crate::aes_enc_dec::encrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), "{\"command\": \"status1\"}")); +//println!("TEST DEC: {:?}\n", crate::aes_enc_dec::decrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), &crate::aes_enc_dec::encrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), "{\"command\": \"status1\"}"))); + +//println!("REQ HEX: {:?}", req.trim()); +//println!("REQ u8: {:?}\n", hex::decode(req.trim()).unwrap()); + +//println!("DEC AES: {:?}", crate::aes_enc_dec::decrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), &hex::decode(req.trim()).unwrap())) \ No newline at end of file diff --git a/src-tauri/src/api/config_tcp.rs b/src-tauri/src/api/config_tcp.rs new file mode 100644 index 0000000..cfba92c --- /dev/null +++ b/src-tauri/src/api/config_tcp.rs @@ -0,0 +1,121 @@ +use std::fs::{File, read_to_string}; +use std::path::Path; +use serde_json::{json, Value}; +use crate::enc_dec_file::{encrypt_n_save_file, decrypt_file}; + +use rand::Rng; +use rand::distributions::Uniform; // 0.8 + +#[tauri::command] +pub fn check_or_create_config_file(app_handle: tauri::AppHandle) { + let mut path = app_handle.path_resolver().app_local_data_dir().unwrap(); + path.push("ToDo"); + path.push("config_api"); + + path.set_extension("enc"); + + let exist: bool = Path::new(&path).exists(); + if exist{ + let content = read_to_string(path); + return match content { + Ok(_) => {}, + Err(_) => { + println!("Не удалось открыть конфигурационный файл!"); + } + } + } else{ + let path = Path::new(&path); + let prefix = path.parent().unwrap(); + let file = std::fs::create_dir_all(prefix); + match file{ + Ok(_) => {}, + Err(err) => println!("Не удалось создать директории! \n{}", err) + } + let file = File::create(path); + match file{ + Ok(_) => { + let rand_s: String = rand::thread_rng() + .sample_iter(Uniform::new(char::from(32), char::from(126))) + .take(64) + .map(char::from) + .collect(); + let token: String = rand_s.replace("'", "").replace('"', "") + .replace("{", "").replace("}", "").replace("[", "") + .replace("}", "").replace("\\", "").replace("'", ""); + + + let data = String::from(format!("{}\ + \"port\": 49494,\ + \"autostart\": true,\ + \"token\": \"{}\"\ + {}", "{", token, "}")); + println!("{}", data); + encrypt_n_save_file(path.into(), data); + }, + Err(err) => println!("Не удалось создать файл! \n{}", err) + } + } +} + +#[tauri::command] +pub fn get_port(app_handle: tauri::AppHandle) -> i64 { + let mut path = app_handle.path_resolver().app_local_data_dir().unwrap(); + path.push("ToDo"); + path.push("config_api"); + + path.set_extension("enc"); + + let content = decrypt_file(path); + let data: Value = serde_json::from_str(content.as_str()).unwrap(); + + return data["port"].as_i64().unwrap(); +} + +#[tauri::command] +pub fn get_autostart(app_handle: tauri::AppHandle) -> bool { + let mut path = app_handle.path_resolver().app_local_data_dir().unwrap(); + path.push("ToDo"); + path.push("config_api"); + + path.set_extension("enc"); + + let content = decrypt_file(path); + let data: Value = serde_json::from_str(content.as_str()).unwrap(); + + return data["autostart"].as_bool().unwrap(); +} + +#[tauri::command] +pub fn edit_settings(app_handle: tauri::AppHandle, port: i32, autostart: bool) { + let mut path = app_handle.path_resolver().app_local_data_dir().unwrap(); + path.push("ToDo"); + path.push("config_api"); + + path.set_extension("enc"); + + let content = decrypt_file(path.clone()); + let mut data: Value = serde_json::from_str(content.as_str()).unwrap(); + data["port"] = json!(port); + data["autostart"] = json!(autostart); + + encrypt_n_save_file(path.into(), data.to_string()); +} + +#[tauri::command] +pub fn reset_token(app_handle: tauri::AppHandle) -> String { + let mut path = app_handle.path_resolver().app_local_data_dir().unwrap(); + path.push("ToDo"); + path.push("config_api"); + + path.set_extension("enc"); + + let key = rand::thread_rng().gen::<[u8; 32]>(); // 256-битный ключ + + let content = decrypt_file(path.clone()); + let mut data: Value = serde_json::from_str(content.as_str()).unwrap(); + data["token"] = json!(hex::encode(&key.clone())); + + encrypt_n_save_file(path.into(), data.to_string()); + + return hex::encode(&key.clone()); +} \ No newline at end of file diff --git a/src-tauri/src/api/multithread_tcp_listener.rs b/src-tauri/src/api/multithread_tcp_listener.rs new file mode 100644 index 0000000..d07e272 --- /dev/null +++ b/src-tauri/src/api/multithread_tcp_listener.rs @@ -0,0 +1,146 @@ +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpListener, TcpStream}; +use std::path::PathBuf; +use std::thread; +use std::string::String; +use serde_json::Value; +use tauri::{AppHandle, Manager}; + +use crate::config_tcp; +use crate::enc_dec_file::decrypt_file; +use crate::aes_enc_dec::{encrypt, decrypt}; + +static mut TCP_COMMAND: &str = ""; +static mut PATH_CONFIG: Option<&'static str> = None; + +fn get_token() -> String{ + unsafe { + let content = decrypt_file(PathBuf::from(PATH_CONFIG.unwrap())); + let data: Value = serde_json::from_str(content.as_str()).unwrap(); + + return data["token"].as_str().unwrap().to_string(); + } +} // Получение токена доступа + +fn data_to_enc_hex(data: &str) -> String{ + hex::encode(encrypt(&hex::decode(get_token()).unwrap(), data)) +} // Шифрование строки до данных обёрнутых в HEX + +fn enc_hex_to_data(data: &str) -> String{ + decrypt(&hex::decode(get_token()).unwrap(), &hex::decode(data).unwrap()) +} // Расшифровка обёрнутых в HEX данных до строки + + +pub fn create_tcp_listener(app_handle: AppHandle) -> TcpListener{ + let path: PathBuf = [ + app_handle.path_resolver().app_local_data_dir().unwrap(), + "ToDo".into(), + "config_api.enc".into(), + ].iter().collect(); // Путь к конфиг файлу + + let path_str = Box::leak(Box::new(path.to_string_lossy().into_owned())); + + unsafe { + PATH_CONFIG = Some(path_str); + } + + return match TcpListener::bind(format!("127.0.0.1:{}", config_tcp::get_port(app_handle.clone()))){ // Создание TCP слушателя на локальном порту + Ok(tcp) => { + for stream in tcp.incoming() { + unsafe { + TCP_COMMAND = ""; + } + + let stream = stream.unwrap(); // Получение запроса + + let thread = thread::spawn(move || { // Создание отдельного потока + handle_connection(stream); // Обработка запроса + }); + + thread.join().unwrap(); // Ожидание пока завершится поток + + unsafe { + match TCP_COMMAND { // Сравнивание команд + "stop" => { + app_handle.emit_all("update_api", "").unwrap(); + break; + } + _ => {} + } + } + }; + + tcp + }, + Err(err) => { + println!("{}", err); + TcpListener::bind("").unwrap() + } + }; +} + +fn handle_connection(mut stream: TcpStream) { + let mut buf_reader = BufReader::new(&mut stream); + let mut req = String::new(); // Получение body запроса + match buf_reader.read_line(&mut req) { + Ok(_) => { + if req.len() > 0 { + let res = enc_hex_to_data(req.trim()); // Дешифрование + let json: Value = serde_json::from_str(res.as_str()).unwrap_or(serde_json::json!("")); // Попытка преобразования запроса в JSON + if json != serde_json::json!(""){ + println!("JSON command: {:?}", json["command"]); + match json["command"].as_str().unwrap() { // Сравнивание команд + "stop" => { + unsafe { + TCP_COMMAND = "stop"; + } + }, + "status" => { + let res = encrypt(&hex::decode(get_token()).unwrap(), "200"); + stream.write(hex::encode(res).as_bytes()).unwrap(); + }, + _ => {} + } + } + } + }, + what => { + println!("{:?}", what); + } + } +} + +#[tauri::command] +pub fn start_tcp_server(app_handle: AppHandle){ + let handle = app_handle.clone(); + thread::spawn(move || { // Создание потока + create_tcp_listener(handle); // Создание слушателя + }); + app_handle.emit_all("update_api", "").unwrap(); // Глобальное сообщение для фронтенда для обновления API страницы +} + +#[tauri::command] +pub fn get_status(app_handle: AppHandle) -> bool{ + match TcpListener::bind(format!("127.0.0.1:{}", config_tcp::get_port(app_handle))) { // Получения статуса слушателя, с помощью нового слушателя + Ok(_) => { + false // Если удалось создать возвращаем false, т.к. слушателя не было + }, + Err(_) => { + true // Иначе true + } + } +} + +#[tauri::command] +pub fn send_command_to_server(app_handle: AppHandle, command: String) -> bool{ + match TcpStream::connect(format!("127.0.0.1:{}", config_tcp::get_port(app_handle))) { // Подключение к слушателю + Ok(mut tcp) => { + tcp.write_all(data_to_enc_hex(format!("{}: \"{}\" {}", "{\"command\"", command, "}\n").as_str()).as_bytes()).unwrap(); // Отправление команды + true + }, + Err(err) => { + println!("{}", err); + false + } + } +} \ No newline at end of file diff --git a/src-tauri/src/enc_dec_file.rs b/src-tauri/src/enc_dec_file.rs new file mode 100644 index 0000000..d136d6f --- /dev/null +++ b/src-tauri/src/enc_dec_file.rs @@ -0,0 +1,49 @@ +use std::fs::{read_to_string, write}; +use std::path::PathBuf; +use magic_crypt::{MagicCryptTrait, new_magic_crypt}; + +use crate::config; + +pub fn encrypt_n_save_file(path: PathBuf, content: String){ + let key = config::ENC_KEY; + let mc = new_magic_crypt!(key, 256); + let mut encrypted: String = String::new(); + + for line in content.lines() { + encrypted = encrypted.to_owned() + mc.encrypt_str_to_base64(line).as_str() + "\n"; + } + let res = write(path, encrypted); + match res { + Ok(_) => {}, + Err(_) => println!("File save error!") + } +} + +pub fn decrypt_file(path: PathBuf) -> String { + let key = config::ENC_KEY; + let mc = new_magic_crypt!(key, 256); + let mut decrypted: String = String::new(); + + let lines = read_to_string(path); + match lines { + Ok(lines) => { + for line in lines.lines() { + let dec_line = mc.decrypt_base64_to_string(&line); + match dec_line { + Ok(line) => { + decrypted = decrypted.to_owned() + line.as_str() + "\n"; + return decrypted; + }, + Err(_) => { + println!("Что-то пошло не так!"); + } + } + } + return String::new(); + }, + Err(_) => { + println!("Что-то не так..."); + return String::new(); + } + } +} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 39e3a8b..caa288b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,11 +1,49 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use std::thread; + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command +#[path="api/multithread_tcp_listener.rs"] mod multi_tcp_listener; +#[path= "api/config_tcp.rs"] mod config_tcp; +#[path= "api/aes_enc_dec.rs"] mod aes_enc_dec; + #[path="tasks_functions.rs"] mod tasks; -mod config; +#[path="config.rs"] mod config; +#[path="enc_dec_file.rs"] mod enc_dec_file; + +#[tauri::command] +fn open_api_window(app_handle: tauri::AppHandle){ + thread::spawn(move || { + match tauri::WindowBuilder::new( + &app_handle, + "api", + tauri::WindowUrl::App("api.html".into()) + ).title("Api") + //.skip_taskbar(true) + .decorations(false) + .build() { + Ok(_) => {} + Err(err) => { + println!("{err}"); + } + } + }); +} fn main() { tauri::Builder::default() + .setup(|app| { + let app_handle = app.handle(); + + if config_tcp::get_autostart(app_handle.clone()) { + thread::spawn(move || { + multi_tcp_listener::create_tcp_listener(app_handle); + }); + } + + Ok(()) + }) .invoke_handler(tauri::generate_handler![ tasks::check_or_create_tasks_file, tasks::get_tasks, @@ -13,7 +51,16 @@ fn main() { tasks::add_task, tasks::edit_task, tasks::delete_task, - tasks::set_task_field + tasks::set_task_field, + open_api_window, + multi_tcp_listener::start_tcp_server, + multi_tcp_listener::get_status, + multi_tcp_listener::send_command_to_server, + config_tcp::check_or_create_config_file, + config_tcp::get_port, + config_tcp::get_autostart, + config_tcp::edit_settings, + config_tcp::reset_token ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/tasks_functions.rs b/src-tauri/src/tasks_functions.rs index 6401873..4ac374d 100644 --- a/src-tauri/src/tasks_functions.rs +++ b/src-tauri/src/tasks_functions.rs @@ -1,56 +1,10 @@ -use magic_crypt::{MagicCryptTrait, new_magic_crypt}; -use std::fs::{read_to_string, write}; -use std::path::PathBuf; +use std::fs::{read_to_string}; use std::path::Path; use std::fs::File; use serde_json::{Value, json}; use rust_fuzzy_search::fuzzy_search_best_n; -use crate::config; - -pub fn encrypt_n_save_file(path: PathBuf, content: String){ - let key = config::ENC_KEY; - let mc = new_magic_crypt!(key, 256); - let mut encrypted: String = String::new(); - - for line in content.lines() { - encrypted = encrypted.to_owned() + mc.encrypt_str_to_base64(line).as_str() + "\n"; - } - let res = write(path, encrypted); - match res { - Ok(_) => {}, - Err(_) => println!("File save error!") - } -} - -pub fn decrypt_file(path: PathBuf) -> String { - let key = config::ENC_KEY; - let mc = new_magic_crypt!(key, 256); - let mut decrypted: String = String::new(); - - let lines = read_to_string(path); - match lines { - Ok(lines) => { - for line in lines.lines() { - let dec_line = mc.decrypt_base64_to_string(&line); - match dec_line { - Ok(line) => { - decrypted = decrypted.to_owned() + line.as_str() + "\n"; - return decrypted; - }, - Err(_) => { - println!("Что-то пошло не так!"); - } - } - } - return String::new(); - }, - Err(_) => { - println!("Что-то не так..."); - return String::new(); - } - } -} +use crate::enc_dec_file::{encrypt_n_save_file, decrypt_file}; #[tauri::command] pub fn check_or_create_tasks_file(app_handle: tauri::AppHandle) { diff --git a/src/ApiApp.vue b/src/ApiApp.vue new file mode 100644 index 0000000..8e72a9d --- /dev/null +++ b/src/ApiApp.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 6f7db34..129262b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,12 +9,14 @@ import { appWindow } from '@tauri-apps/api/window'; import {useDark, useToggle} from "@vueuse/core"; import PriorityModal from "./components/modals/PriorityModal.vue"; +import {listen} from "@tauri-apps/api/event"; const isDark = useDark(); const toggleDark = useToggle(isDark); let tasks = ref({}); let pending = ref(true); +let api_status = ref(false); let create_modal = ref(false); let edit_modal = ref(false); @@ -31,22 +33,24 @@ let to_change_id = ref(""); let to_change_name = ref(""); let to_change_priority = ref(""); +async function get_status() { + await invoke('get_status') + .then((res) => { + api_status.value = res; + }); +} + onBeforeMount(async () => { await invoke('check_or_create_tasks_file') .then(async () => { await get_tasks(); + await get_status(); pending.value = false; }); - document - .getElementById('titlebar-minimize') - .addEventListener('click', () => appWindow.minimize()) - document - .getElementById('titlebar-maximize') - .addEventListener('click', () => appWindow.toggleMaximize()) - document - .getElementById('titlebar-close') - .addEventListener('click', () => appWindow.close()) + await listen('update_api', async () => { + await get_status(); + }) }); @@ -134,23 +138,35 @@ function change_priority(id_task, name, priority){ to_change_priority.value = priority; priority_modal.value = true; } + + +async function open_api(){ + await invoke('open_api_window'); +}