Communities
This commit is contained in:
parent
6ae6e85b5a
commit
1c08956387
26 changed files with 1988 additions and 338 deletions
126
Cargo.lock
generated
126
Cargo.lock
generated
|
|
@ -6,6 +6,7 @@ version = 4
|
|||
name = "Iota"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"axum",
|
||||
"base64",
|
||||
"bytes",
|
||||
|
|
@ -13,8 +14,11 @@ dependencies = [
|
|||
"color-eyre",
|
||||
"crossterm 0.29.0",
|
||||
"der",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"json",
|
||||
"once_cell",
|
||||
"pkcs8",
|
||||
|
|
@ -29,6 +33,7 @@ dependencies = [
|
|||
"sysinfo",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tungstenite",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"x448",
|
||||
|
|
@ -50,6 +55,41 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[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 = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
|
|
@ -235,6 +275,16 @@ dependencies = [
|
|||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
@ -409,9 +459,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
|
|
@ -501,6 +561,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -751,6 +812,16 @@ dependencies = [
|
|||
"wasi 0.14.4+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
|
|
@ -799,6 +870,24 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
|
|
@ -1085,6 +1174,15 @@ version = "2.0.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.9"
|
||||
|
|
@ -1321,6 +1419,12 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
|
|
@ -1434,6 +1538,18 @@ version = "0.3.32"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.3"
|
||||
|
|
@ -2332,6 +2448,16 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
|
|
|||
|
|
@ -35,3 +35,8 @@ x509 = "*"
|
|||
sha2 = "*"
|
||||
der = "*"
|
||||
pkcs8 = { version = "*", features = ["alloc"] }
|
||||
tungstenite = "*"
|
||||
futures = "*"
|
||||
aes-gcm = "*"
|
||||
hkdf = "*"
|
||||
hmac = "*"
|
||||
|
|
|
|||
138
src/auth/crypto_helper.rs
Normal file
138
src/auth/crypto_helper.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use aes_gcm::{
|
||||
Aes256Gcm, // AES‑256 GCM
|
||||
Nonce,
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use base64::{decode as b64_decode, encode as b64_encode};
|
||||
use hex;
|
||||
use json::JsonValue;
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::Rng;
|
||||
use rand_core::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use x448::{PublicKey, Secret, SharedSecret}; // from the `x448` crate
|
||||
|
||||
/// Errors for crypto operations
|
||||
#[derive(Debug)]
|
||||
pub enum CryptoError {
|
||||
Base64Decode(base64::DecodeError),
|
||||
InvalidKey,
|
||||
AgreementError,
|
||||
EncryptionError(aes_gcm::Error),
|
||||
DecryptionError(aes_gcm::Error),
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for CryptoError {
|
||||
fn from(err: base64::DecodeError) -> Self {
|
||||
CryptoError::Base64Decode(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyPair {
|
||||
pub secret: Secret,
|
||||
pub public: PublicKey,
|
||||
}
|
||||
|
||||
pub fn generate_keypair() -> KeyPair {
|
||||
let mut buf = [0u8; 56];
|
||||
let mut rng = OsRng;
|
||||
rng.fill_bytes(&mut buf);
|
||||
let secret = Secret::from_bytes(&buf).unwrap();
|
||||
let public = PublicKey::from(&secret);
|
||||
KeyPair { secret, public }
|
||||
}
|
||||
|
||||
pub fn public_key_to_base64(pubkey: &PublicKey) -> String {
|
||||
b64_encode(pubkey.as_bytes().as_ref())
|
||||
}
|
||||
|
||||
pub fn secret_key_to_base64(secret: &Secret) -> String {
|
||||
b64_encode(secret.as_bytes().as_ref())
|
||||
}
|
||||
|
||||
pub fn load_public_key(base64_pub: &str) -> Option<PublicKey> {
|
||||
let bytes = b64_decode(base64_pub).unwrap();
|
||||
PublicKey::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn load_secret_key(base64_secret: &str) -> Option<Secret> {
|
||||
let bytes = b64_decode(base64_secret).unwrap();
|
||||
Secret::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(shared.as_bytes());
|
||||
let result = hasher.finalize();
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(&result[..32]);
|
||||
key
|
||||
}
|
||||
|
||||
pub fn encrypt(
|
||||
base64_secret: &str,
|
||||
base64_peer_pub: &str,
|
||||
plaintext: &str,
|
||||
) -> Result<String, CryptoError> {
|
||||
let secret = load_secret_key(base64_secret).unwrap();
|
||||
let peer_pub = load_public_key(base64_peer_pub).unwrap();
|
||||
let shared = secret
|
||||
.to_diffie_hellman(&peer_pub)
|
||||
.ok_or(CryptoError::AgreementError)?;
|
||||
let key_bytes = derive_aes_key(&shared);
|
||||
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
OsRng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_bytes())
|
||||
.map_err(CryptoError::EncryptionError)?;
|
||||
// prefix nonce to ciphertext
|
||||
let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
|
||||
out.extend_from_slice(&nonce_bytes);
|
||||
out.extend_from_slice(&ciphertext);
|
||||
Ok(b64_encode(&out))
|
||||
}
|
||||
|
||||
pub fn decrypt(
|
||||
base64_secret: &str,
|
||||
base64_peer_pub: &str,
|
||||
encrypted_base64: &str,
|
||||
) -> Result<String, CryptoError> {
|
||||
let secret = load_secret_key(base64_secret).unwrap();
|
||||
let peer_pub = load_public_key(base64_peer_pub).unwrap();
|
||||
let shared = secret
|
||||
.to_diffie_hellman(&peer_pub)
|
||||
.ok_or(CryptoError::AgreementError)?;
|
||||
let key_bytes = derive_aes_key(&shared);
|
||||
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
|
||||
|
||||
let encrypted = b64_decode(encrypted_base64)?;
|
||||
if encrypted.len() < 12 {
|
||||
return Err(CryptoError::DecryptionError(aes_gcm::Error));
|
||||
}
|
||||
let nonce_bytes = &encrypted[..12];
|
||||
let ciphertext = &encrypted[12..];
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
let plaintext_bytes = cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.map_err(CryptoError::DecryptionError)?;
|
||||
let plaintext = String::from_utf8(plaintext_bytes)
|
||||
.map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?;
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
pub fn hash_it(input: &str) -> Vec<u8> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(input.as_bytes());
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
|
||||
pub fn hex_hash(input: &str) -> String {
|
||||
let digest = hash_it(input);
|
||||
digest.iter().map(|b| format!("{:02x}", b)).collect()
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
pub mod auth_connector;
|
||||
pub mod crypto_helper;
|
||||
|
|
|
|||
280
src/communities/community.rs
Normal file
280
src/communities/community.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
use crate::communities::interactables::category::Category;
|
||||
use crate::communities::interactables::registry;
|
||||
use crate::communities::interactables::text_chat::TextChat;
|
||||
use crate::communities::interactables::voice_chat::VoiceChat;
|
||||
use crate::communities::{
|
||||
community_connection::CommunityConnection, interactables::interactable::Interactable,
|
||||
};
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue};
|
||||
use crate::util::file_util;
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use json::JsonValue;
|
||||
use json::object::Object;
|
||||
use rand::RngCore;
|
||||
use rand_core::OsRng;
|
||||
use ratatui::text;
|
||||
use serde::de::value::StringDeserializer;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use x448::{PublicKey, Secret};
|
||||
/// Permissions
|
||||
// uuid -> interactable/path/like/this/interactable_name:permission
|
||||
// -> role
|
||||
|
||||
/// Roles
|
||||
// rolename -> interactable/path/like/this/interactable_name:permission
|
||||
// -> other/path/like/this/interactable_name:permission
|
||||
|
||||
pub struct Community {
|
||||
name: String,
|
||||
owner_id: Uuid,
|
||||
members: Vec<Uuid>,
|
||||
permissions: HashMap<Uuid, Vec<String>>,
|
||||
roles: HashMap<String, Vec<String>>,
|
||||
private_key: Secret,
|
||||
public_key: PublicKey,
|
||||
pub interactables: Arc<RwLock<Vec<Arc<Box<dyn Interactable>>>>>,
|
||||
pub connections: Arc<RwLock<HashMap<Uuid, Vec<Arc<CommunityConnection>>>>>,
|
||||
}
|
||||
|
||||
impl Community {
|
||||
pub fn new() -> Self {
|
||||
let mut buf = [0u8; 56];
|
||||
let mut rng = OsRng;
|
||||
rng.fill_bytes(&mut buf);
|
||||
let private_key = Secret::from_bytes(&buf).unwrap();
|
||||
let public_key = PublicKey::from(&private_key);
|
||||
Community {
|
||||
name: String::new(),
|
||||
owner_id: Uuid::new_v4(),
|
||||
members: Vec::new(),
|
||||
permissions: HashMap::new(),
|
||||
roles: HashMap::new(),
|
||||
private_key,
|
||||
public_key,
|
||||
interactables: Arc::new(RwLock::new(Vec::new())),
|
||||
connections: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_member(&mut self, member_id: Uuid) {
|
||||
self.members.push(member_id);
|
||||
}
|
||||
|
||||
pub fn remove_member(&mut self, member_id: Uuid) {
|
||||
self.members.retain(|id| *id != member_id);
|
||||
}
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn get_private_key(&self) -> Secret {
|
||||
Secret::from_bytes(self.private_key.as_bytes()).unwrap()
|
||||
}
|
||||
pub fn get_public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
pub async fn add_connection(self: &Arc<Self>, other: Arc<CommunityConnection>) {
|
||||
let mut vec = self
|
||||
.connections
|
||||
.read()
|
||||
.await
|
||||
.get(&other.get_user_id().await.unwrap())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
vec.push(other.clone());
|
||||
self.connections
|
||||
.write()
|
||||
.await
|
||||
.insert(other.get_user_id().await.unwrap(), vec);
|
||||
}
|
||||
pub async fn get_connections(&self) -> HashMap<Uuid, Vec<Arc<CommunityConnection>>> {
|
||||
self.connections.read().await.clone()
|
||||
}
|
||||
pub async fn get_connections_for_user(&self, user_id: Uuid) -> Vec<Arc<CommunityConnection>> {
|
||||
self.connections
|
||||
.read()
|
||||
.await
|
||||
.get(&user_id)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
pub async fn get_interactables(
|
||||
&self,
|
||||
user_id: Uuid,
|
||||
) -> Vec<Arc<Box<dyn Interactable + 'static>>> {
|
||||
self.interactables.read().await.clone()
|
||||
}
|
||||
pub async fn add_interactable(self: &mut Arc<Self>, interactable: Arc<Box<dyn Interactable>>) {
|
||||
self.interactables.write().await.push(interactable);
|
||||
}
|
||||
pub async fn remove_interactable(
|
||||
self: &mut Arc<Self>,
|
||||
interactable: Arc<Box<dyn Interactable>>,
|
||||
) {
|
||||
self.interactables
|
||||
.write()
|
||||
.await
|
||||
.retain(|i| !Arc::ptr_eq(i, &interactable));
|
||||
}
|
||||
pub async fn run_function(
|
||||
self: &mut Arc<Self>,
|
||||
user_id: Uuid,
|
||||
name: &str,
|
||||
path: &str,
|
||||
function: &str,
|
||||
cv: &CommunicationValue,
|
||||
) -> CommunicationValue {
|
||||
if path.is_empty() {
|
||||
let mut target_interactables = &self.interactables.read().await.clone();
|
||||
for interactable in target_interactables.iter() {
|
||||
if interactable.get_name() == name {
|
||||
if interactable.get_codec() == "category" {
|
||||
return CommunicationValue::new(CommunicationType::error);
|
||||
} else {
|
||||
return interactable.run_function(cv.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let target_interactables = &self.interactables.read().await.clone();
|
||||
for interactable in target_interactables.iter() {
|
||||
if interactable.get_name() == name {
|
||||
if interactable.get_codec() == "category" {
|
||||
let category: &Category =
|
||||
interactable.as_any().downcast_ref::<Category>().unwrap();
|
||||
return category
|
||||
.get_child(path.to_string(), name.to_string())
|
||||
.unwrap()
|
||||
.run_function(cv.clone());
|
||||
} else {
|
||||
return CommunicationValue::new(CommunicationType::error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CommunicationValue::new(CommunicationType::add_chat)
|
||||
}
|
||||
|
||||
pub async fn save(&self) {
|
||||
let mut json = Object::new();
|
||||
json.insert("name", JsonValue::String(self.name.clone()));
|
||||
json.insert("owner_id", JsonValue::String(self.owner_id.to_string()));
|
||||
|
||||
json.insert(
|
||||
"private_key",
|
||||
JsonValue::String(STANDARD.encode(&self.private_key.as_bytes())),
|
||||
);
|
||||
json.insert(
|
||||
"public_key",
|
||||
JsonValue::String(STANDARD.encode(&self.public_key.as_bytes())),
|
||||
);
|
||||
|
||||
file_util::save_file(
|
||||
&format!("communities/{}/", self.name),
|
||||
"config.json",
|
||||
&json.dump(),
|
||||
);
|
||||
|
||||
let mut user_data = Object::new();
|
||||
for user in self.members.iter() {
|
||||
let mut data = JsonValue::new_object();
|
||||
|
||||
let mut permissions = JsonValue::new_array();
|
||||
for perm in self.permissions.get(user).unwrap() {
|
||||
permissions.push(perm.to_string());
|
||||
}
|
||||
|
||||
data.insert("permissions", permissions);
|
||||
user_data.insert(&user.to_string(), data);
|
||||
}
|
||||
file_util::save_file(
|
||||
&format!("communities/{}/", self.name),
|
||||
"users.json",
|
||||
&user_data.dump(),
|
||||
);
|
||||
|
||||
for interactable in self.interactables.read().await.clone().iter() {
|
||||
registry::save(interactable).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn load(name: &String) -> Option<Arc<Community>> {
|
||||
let file_contents = file_util::load_file(&format!("communities/{}/", name), "config.json");
|
||||
let json_content = json::parse(&file_contents).unwrap();
|
||||
|
||||
let user_data = file_util::load_file(&format!("communities/{}/", name), "users.json");
|
||||
let user_json: JsonValue = json::parse(&user_data).unwrap();
|
||||
let mut users = Vec::new();
|
||||
let mut permissions: HashMap<Uuid, Vec<String>> = HashMap::new();
|
||||
|
||||
for user in user_json.entries() {
|
||||
let (str, json): (&str, &JsonValue) = user;
|
||||
let perms_j = &json["permissions"];
|
||||
let perms = Vec::new();
|
||||
for i in perms_j.entries() {
|
||||
// let perm_j = i.as_str().unwrap();
|
||||
// perms.push(perm_j.to_string());
|
||||
}
|
||||
|
||||
let user_id = Uuid::parse_str(str).unwrap();
|
||||
|
||||
users.push(user_id);
|
||||
permissions.insert(user_id, perms);
|
||||
}
|
||||
|
||||
let role_data = file_util::load_file(&format!("communities/{}/", name), "roles.json");
|
||||
if let Ok(user_json) = json::parse(&role_data) {
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let mut roles: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
let community = Community {
|
||||
name: json_content["name"].as_str().unwrap().to_string(),
|
||||
owner_id: Uuid::parse_str(json_content["owner_id"].as_str().unwrap()).unwrap(),
|
||||
members: users,
|
||||
roles,
|
||||
permissions,
|
||||
private_key: Secret::from_bytes(
|
||||
&STANDARD
|
||||
.decode(json_content["private_key"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
public_key: PublicKey::from(
|
||||
&Secret::from_bytes(
|
||||
&STANDARD
|
||||
.decode(json_content["private_key"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
interactables: Arc::new(RwLock::new(Vec::new())),
|
||||
connections: Arc::new(RwLock::new(HashMap::new())),
|
||||
};
|
||||
let mut comarc = Arc::new(community);
|
||||
|
||||
let interactable_files: Vec<String> =
|
||||
file_util::get_children(&format!("communities/{}/interactables/", name));
|
||||
for file in interactable_files {
|
||||
if file.contains(".json") {
|
||||
let name = file.split('.').next().unwrap().to_string();
|
||||
let interactable: Box<dyn Interactable> =
|
||||
registry::load(comarc.clone(), String::new(), name).await;
|
||||
comarc.add_interactable(Arc::new(interactable)).await;
|
||||
}
|
||||
}
|
||||
let mut text_chat: TextChat = TextChat::new();
|
||||
text_chat.load(
|
||||
comarc.clone(),
|
||||
String::new(),
|
||||
String::from("a"),
|
||||
&JsonValue::Null,
|
||||
);
|
||||
|
||||
comarc.add_interactable(Arc::new(Box::new(text_chat))).await;
|
||||
|
||||
Some(comarc)
|
||||
}
|
||||
415
src/communities/community_connection.rs
Normal file
415
src/communities/community_connection.rs
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
use crate::auth::auth_connector::AuthUser;
|
||||
use crate::auth::auth_connector::get_user;
|
||||
use crate::communities::community::Community;
|
||||
use crate::communities::interactables::interactable::Interactable;
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use futures::SinkExt;
|
||||
use hkdf::Hkdf;
|
||||
use json::JsonValue;
|
||||
use json::object::Object;
|
||||
use rand::{Rng, distributions::Alphanumeric};
|
||||
use sha2::Sha256;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
|
||||
use uuid::Uuid;
|
||||
use x448::PublicKey;
|
||||
pub struct CommunityConnection {
|
||||
pub session: Arc<Mutex<WebSocketStream<tokio::net::TcpStream>>>,
|
||||
pub user_id: Arc<RwLock<Option<Uuid>>>,
|
||||
pub community: Arc<RwLock<Option<Arc<Community>>>>,
|
||||
identified: Arc<RwLock<bool>>,
|
||||
challenged: Arc<RwLock<bool>>,
|
||||
challenge: Arc<RwLock<String>>,
|
||||
auth: Arc<RwLock<Option<AuthUser>>>,
|
||||
pub ping: Arc<RwLock<i64>>,
|
||||
}
|
||||
impl CommunityConnection {
|
||||
pub fn new(
|
||||
session: WebSocketStream<tokio::net::TcpStream>,
|
||||
community: Arc<Community>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
session: Arc::new(Mutex::new(session)),
|
||||
user_id: Arc::new(RwLock::new(None)),
|
||||
community: Arc::new(RwLock::new(Some(community))),
|
||||
identified: Arc::new(RwLock::new(false)),
|
||||
challenged: Arc::new(RwLock::new(false)),
|
||||
challenge: Arc::new(RwLock::new(String::new())),
|
||||
auth: Arc::new(RwLock::new(None)),
|
||||
ping: Arc::new(RwLock::new(-1)),
|
||||
})
|
||||
}
|
||||
pub async fn send_message(&self, message: &CommunicationValue) {
|
||||
let mut session = self.session.lock().await;
|
||||
session
|
||||
.send(Message::Text(message.to_json().to_string()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
pub async fn get_community(&self) -> Option<Arc<Community>> {
|
||||
self.community.read().await.clone()
|
||||
}
|
||||
pub async fn get_user_id(&self) -> Option<Uuid> {
|
||||
*self.user_id.read().await
|
||||
}
|
||||
pub async fn is_identified(&self) -> bool {
|
||||
*self.identified.read().await && *self.challenged.read().await
|
||||
}
|
||||
|
||||
pub async fn handle_message(self: Arc<Self>, message: String) {
|
||||
let cv = CommunicationValue::from_json(&message);
|
||||
|
||||
if cv.is_type(CommunicationType::identification) && !self.is_identified().await {
|
||||
self.handle_identification(cv).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if cv.is_type(CommunicationType::challenge_response) && !self.is_identified().await {
|
||||
self.handle_challenge_response(cv).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.is_identified().await {
|
||||
return;
|
||||
}
|
||||
|
||||
if cv.is_type(CommunicationType::ping) {
|
||||
self.handle_ping(cv).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if cv.is_type(CommunicationType::client_changed) {
|
||||
//self.handle_client_changed(cv).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if cv.is_type(CommunicationType::function) {
|
||||
self.handle_function(cv).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
async fn handle_function(&self, cv: CommunicationValue) {
|
||||
/*
|
||||
* {
|
||||
* "type": "function",
|
||||
* "id": "<uuid>",
|
||||
* "log": {
|
||||
* "log_level": 0,
|
||||
* "message": "running function"
|
||||
* },
|
||||
* "data": {
|
||||
* "codec": "<channel_codec>", // optional
|
||||
* "name": "<channel_name>",
|
||||
* "path": "<overlord_category_name>/<overlord_category_name>...",
|
||||
* "function": "<function_to_execute>",
|
||||
* "payload": {
|
||||
* "<ARG_1>": "<VAL_1>",
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
let name = cv.get_data(DataTypes::name).unwrap().as_str().unwrap();
|
||||
let path = cv.get_data(DataTypes::path).unwrap().as_str().unwrap();
|
||||
let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap();
|
||||
|
||||
let result = self
|
||||
.get_community()
|
||||
.await
|
||||
.unwrap()
|
||||
.run_function(self.get_user_id().await.unwrap(), name, path, function, &cv)
|
||||
.await;
|
||||
|
||||
self.send_message(&result).await;
|
||||
}
|
||||
async fn handle_identification(&self, cv: CommunicationValue) {
|
||||
let user_id = match cv.get_data(DataTypes::user_id) {
|
||||
Some(id_str) => match Uuid::parse_str(&id_str.to_string()) {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
self.send_error_response(
|
||||
&cv.get_id(),
|
||||
CommunicationType::error_invalid_user_id,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(user) = get_user(user_id).await else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
{
|
||||
let mut auth_guard = self.auth.write().await;
|
||||
*auth_guard = Some(user.clone());
|
||||
|
||||
let mut user_id_guard = self.user_id.write().await;
|
||||
*user_id_guard = Some(user_id);
|
||||
|
||||
let mut identified_guard = self.identified.write().await;
|
||||
*identified_guard = true;
|
||||
}
|
||||
|
||||
let challenge_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
{
|
||||
let mut challenge_guard = self.challenge.write().await;
|
||||
*challenge_guard = challenge_str.clone();
|
||||
}
|
||||
|
||||
let user_public_key_bytes = match STANDARD.decode(&user.public_key) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(community) = self.community.read().await.clone() else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let community_private_key = community.get_private_key();
|
||||
let community_public_key = community.get_public_key();
|
||||
|
||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||
Some(secret) => secret,
|
||||
None => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let aes_key = {
|
||||
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
|
||||
let mut key_bytes = [0u8; 32];
|
||||
hk.expand(b"challenge", &mut key_bytes)
|
||||
.expect("HKDF failure");
|
||||
key_bytes
|
||||
};
|
||||
|
||||
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
|
||||
|
||||
let nonce_bytes: [u8; 12] = rand::random();
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
let encrypted_challenge = match cipher.encrypt(nonce, challenge_str.as_bytes()) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut encrypted_out = nonce_bytes.to_vec();
|
||||
encrypted_out.extend(encrypted_challenge);
|
||||
|
||||
let response = CommunicationValue::new(CommunicationType::challenge)
|
||||
.add_data_str(
|
||||
DataTypes::public_key,
|
||||
STANDARD.encode(community_public_key.as_bytes()),
|
||||
)
|
||||
.add_data_str(DataTypes::challenge, STANDARD.encode(&encrypted_out))
|
||||
.with_id(cv.get_id());
|
||||
|
||||
self.send_message(&response).await;
|
||||
}
|
||||
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
|
||||
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
|
||||
Some(data) => data.to_string(),
|
||||
None => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let challenge_response_bytes = match STANDARD.decode(&client_challenge_response_b64) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if challenge_response_bytes.len() < 12 {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(user) = self.auth.read().await.clone() else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(user_pub_bytes) = STANDARD.decode(&user.public_key).ok() else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(user_pub_key) = PublicKey::from_bytes(&user_pub_bytes) else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(community) = self.community.read().await.clone() else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let community_private_key = community.get_private_key();
|
||||
|
||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||
Some(secret) => secret,
|
||||
None => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let aes_key = {
|
||||
use hkdf::Hkdf;
|
||||
use sha2::Sha256;
|
||||
|
||||
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
|
||||
let mut key_bytes = [0u8; 32];
|
||||
hk.expand(b"challenge", &mut key_bytes)
|
||||
.expect("HKDF failure");
|
||||
key_bytes
|
||||
};
|
||||
|
||||
let (nonce_bytes, ciphertext) = challenge_response_bytes.split_at(12);
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
|
||||
|
||||
let decrypted_bytes = match cipher.decrypt(nonce, ciphertext) {
|
||||
Ok(pt) => pt,
|
||||
Err(_) => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let client_response = match String::from_utf8(decrypted_bytes) {
|
||||
Ok(str) => str,
|
||||
Err(_) => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let expected_challenge = self.challenge.read().await.clone();
|
||||
|
||||
if client_response != expected_challenge {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
self.close().await;
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mut authenticated_guard = self.challenged.write().await;
|
||||
*authenticated_guard = true;
|
||||
}
|
||||
|
||||
let Some(community) = self.community.read().await.clone() else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
let arc = Arc::new(community);
|
||||
|
||||
let Some(user_id) = self.get_user_id().await else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
arc.add_connection(self.clone()).await;
|
||||
|
||||
let response = CommunicationValue::new(CommunicationType::identification_response)
|
||||
.add_data(DataTypes::interactables, {
|
||||
let a: Vec<Arc<Box<dyn Interactable>>> = arc.get_interactables(user_id).await;
|
||||
let mut c: JsonValue = JsonValue::new_object();
|
||||
for b in a {
|
||||
let mut subject = JsonValue::new_object();
|
||||
subject["codec"] = JsonValue::String(b.get_codec());
|
||||
subject["data"] = b.get_data();
|
||||
c[b.get_name()] = subject;
|
||||
}
|
||||
c
|
||||
})
|
||||
.with_id(cv.get_id());
|
||||
|
||||
self.send_message(&response).await;
|
||||
}
|
||||
|
||||
async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) {
|
||||
let error = CommunicationValue::new(error_type).with_id(*message_id);
|
||||
self.send_message(&error).await;
|
||||
}
|
||||
pub async fn close(&self) {
|
||||
let mut session = self.session.lock().await;
|
||||
let _ = session.close(None).await;
|
||||
}
|
||||
pub async fn handle_close(&self) {
|
||||
if self.is_identified().await {
|
||||
if let Some(user_id) = self.get_user_id().await {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_ping(&self, cv: CommunicationValue) {
|
||||
if let Some(last_ping) = cv.get_data(DataTypes::last_ping) {
|
||||
if let Ok(ping_val) = last_ping.to_string().parse::<i64>() {
|
||||
let mut ping_guard = self.ping.write().await;
|
||||
*ping_guard = ping_val;
|
||||
}
|
||||
}
|
||||
|
||||
let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id());
|
||||
|
||||
self.send_message(&response).await;
|
||||
}
|
||||
}
|
||||
45
src/communities/community_manager.rs
Normal file
45
src/communities/community_manager.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::communities::community::{self, Community};
|
||||
use crate::gui::log_panel;
|
||||
use crate::util::file_util;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub static COMMUNITY_REGISTRY: Lazy<Arc<Mutex<HashMap<String, Arc<Community>>>>> =
|
||||
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
|
||||
|
||||
pub async fn add_community(community: Arc<Community>) {
|
||||
COMMUNITY_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(community.get_name().to_string(), community);
|
||||
}
|
||||
pub async fn get_community(name: &str) -> Option<Arc<Community>> {
|
||||
if let Some(c) = COMMUNITY_REGISTRY.lock().await.get(name) {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_communities() {
|
||||
let community_names = file_util::get_children("communities");
|
||||
for name in community_names {
|
||||
if let Some(community) = community::load(&name).await {
|
||||
add_community(community).await;
|
||||
} else {
|
||||
log_panel::log_message(format!("failed to load the {} community", &name));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn save_communities() {
|
||||
for community in COMMUNITY_REGISTRY.lock().await.values() {
|
||||
community.save().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_communities() -> Vec<Arc<Community>> {
|
||||
COMMUNITY_REGISTRY.lock().await.values().cloned().collect()
|
||||
}
|
||||
67
src/communities/community_socket.rs
Normal file
67
src/communities/community_socket.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use crate::communities::{
|
||||
community::Community, community_connection::CommunityConnection, community_manager,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_tungstenite::accept_hdr_async;
|
||||
use tungstenite::handshake::server::{Request, Response};
|
||||
|
||||
pub async fn start(port: u16) -> bool {
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
|
||||
if let Err(_) = listener {
|
||||
return false;
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
tokio::spawn(async move {
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
tokio::spawn(async move {
|
||||
let mut path: String = "/".to_string();
|
||||
let callback = |req: &Request, response: Response| {
|
||||
path = format!("{}", &req.uri().path());
|
||||
Ok(response)
|
||||
};
|
||||
let ws_stream = match accept_hdr_async(stream, callback).await {
|
||||
Ok(ws) => ws,
|
||||
Err(e) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if path.starts_with("/community/") {
|
||||
let community_id = path.split("/").nth(2).unwrap();
|
||||
if let Some(community) = community_manager::get_community(community_id).await {
|
||||
let community_conn: Arc<CommunityConnection> =
|
||||
Arc::from(CommunityConnection::new(ws_stream, community));
|
||||
loop {
|
||||
let msg_result = {
|
||||
let mut session_lock = community_conn.session.lock().await;
|
||||
session_lock.next().await
|
||||
};
|
||||
|
||||
match msg_result {
|
||||
Some(Ok(msg)) => {
|
||||
if msg.is_text() {
|
||||
let text = msg.into_text().unwrap();
|
||||
community_conn.clone().handle_message(text).await;
|
||||
} else if msg.is_close() {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
108
src/communities/interactables/category.rs
Normal file
108
src/communities/interactables/category.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use axum::Json;
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Category {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
children: Vec<Arc<Box<dyn Interactable>>>,
|
||||
}
|
||||
impl Category {
|
||||
pub fn new() -> Category {
|
||||
Category {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn get_child(&self, path: String, name: String) -> Option<Arc<Box<dyn Interactable>>> {
|
||||
if path.is_empty() {
|
||||
self.children
|
||||
.iter()
|
||||
.find(|child| child.get_name() == &name)
|
||||
.cloned()
|
||||
} else {
|
||||
let sub_module = path.split("/").next().unwrap();
|
||||
let next = self
|
||||
.children
|
||||
.iter()
|
||||
.find(|child| child.get_name() == sub_module)
|
||||
.unwrap();
|
||||
if next.get_codec() == "category" {
|
||||
let next_cat = next.as_any().downcast_ref::<Category>().unwrap();
|
||||
next_cat.get_child(path, name)
|
||||
} else {
|
||||
Some(next.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_children(&self) -> Vec<Arc<Box<dyn Interactable>>> {
|
||||
self.children.iter().map(|child| child.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Interactable for Category {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"category".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
for child in &self.children {
|
||||
let mut subject = JsonValue::new_object();
|
||||
subject["codec"] = JsonValue::String(child.get_codec());
|
||||
subject["data"] = child.get_data();
|
||||
v[child.get_name()] = subject;
|
||||
}
|
||||
v
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::error)
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v["children"] = JsonValue::new_array();
|
||||
for child in &self.children {
|
||||
v["children"].push(child.to_json());
|
||||
}
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
27
src/communities/interactables/interactable.rs
Normal file
27
src/communities/interactables/interactable.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use crate::{
|
||||
communities::community::Community,
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use axum::Json;
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type InteractableFactory = fn() -> Box<dyn Interactable>;
|
||||
|
||||
pub trait Interactable: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn get_codec(&self) -> String;
|
||||
fn get_name(&self) -> &String;
|
||||
fn get_path(&self) -> &String;
|
||||
fn get_total_path(&self) -> String;
|
||||
fn set_name(&mut self, name: String);
|
||||
fn set_path(&mut self, path: String);
|
||||
fn get_community(&self) -> &Arc<Community>;
|
||||
fn set_community(&mut self, community: Arc<Community>);
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue;
|
||||
fn get_data(&self) -> JsonValue;
|
||||
fn to_json(&self) -> JsonValue;
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue);
|
||||
}
|
||||
73
src/communities/interactables/registry.rs
Normal file
73
src/communities/interactables/registry.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::communities::community::Community;
|
||||
use crate::communities::interactables::category::Category;
|
||||
use crate::communities::interactables::interactable::{Interactable, InteractableFactory};
|
||||
use crate::communities::interactables::text_chat::TextChat;
|
||||
use crate::communities::interactables::voice_chat::VoiceChat;
|
||||
use crate::gui::log_panel;
|
||||
use crate::util::file_util;
|
||||
use json::JsonValue;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub static INTERACTABLE_REGISTRY: Lazy<Arc<Mutex<HashMap<String, InteractableFactory>>>> =
|
||||
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
|
||||
pub async fn load_interactables() {
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(TextChat::new().get_codec(), || {
|
||||
Box::new(TextChat::new()) as Box<dyn Interactable>
|
||||
});
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(VoiceChat::new().get_codec(), || {
|
||||
Box::new(VoiceChat::new()) as Box<dyn Interactable>
|
||||
});
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(Category::new().get_codec(), || {
|
||||
Box::new(Category::new()) as Box<dyn Interactable>
|
||||
});
|
||||
}
|
||||
pub async fn register_interactable(name: String, interactable: InteractableFactory) {
|
||||
INTERACTABLE_REGISTRY
|
||||
.lock()
|
||||
.await
|
||||
.insert(name.to_string(), interactable)
|
||||
.unwrap();
|
||||
}
|
||||
pub async fn get_interactable(name: &str) -> Box<dyn Interactable> {
|
||||
INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()()
|
||||
}
|
||||
pub async fn save(interactable: &Arc<Box<dyn Interactable>>) {
|
||||
let mut json_object: JsonValue = interactable.to_json().clone();
|
||||
json_object["codec"] = JsonValue::String(interactable.get_codec());
|
||||
file_util::save_file(
|
||||
&format!(
|
||||
"communities/{}/interactables/{}",
|
||||
interactable.get_community().get_name(),
|
||||
interactable.get_path()
|
||||
),
|
||||
&format!("{}.json", interactable.get_name()),
|
||||
&json_object.to_string(),
|
||||
);
|
||||
}
|
||||
pub async fn load(
|
||||
c: Arc<Community>,
|
||||
path: String,
|
||||
name: String,
|
||||
) -> Box<dyn Interactable + 'static> {
|
||||
let s = file_util::load_file(
|
||||
&format!("communities/{}/interactables/{}", c.get_name(), path),
|
||||
&format!("{}.json", name),
|
||||
);
|
||||
let json_object: JsonValue = json::parse(&s).unwrap();
|
||||
let codec: String = json_object["codec"].as_str().unwrap().to_string();
|
||||
let mut interactable = get_interactable(&codec).await;
|
||||
interactable.load(c, path, name, &json_object);
|
||||
interactable
|
||||
}
|
||||
225
src/communities/interactables/text_chat.rs
Normal file
225
src/communities/interactables/text_chat.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||
gui::log_panel::log_message,
|
||||
util::file_util::{get_children, load_file, save_file},
|
||||
};
|
||||
use aes_gcm::aead::Payload;
|
||||
use axum::Json;
|
||||
use json::{JsonValue, array, object};
|
||||
use std::any::Any;
|
||||
use std::fs::{self, File};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
pub struct TextChat {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
}
|
||||
impl TextChat {
|
||||
pub fn new() -> TextChat {
|
||||
TextChat {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
}
|
||||
}
|
||||
pub fn add_message(&self, send_time: u128, sender: Uuid, message: &str) {
|
||||
let user_dir = &format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(user_dir) {
|
||||
log_message(format!("Failed to create chat directory: {}", e));
|
||||
return;
|
||||
}
|
||||
|
||||
let mut chunk_index = 0;
|
||||
let mut message_chunk = array![];
|
||||
|
||||
// find latest chunk not full (max 800 msgs)
|
||||
loop {
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
let file_content = load_file(&user_dir, &file_name);
|
||||
|
||||
if !file_content.is_empty() {
|
||||
if let Ok(current_chunk) = json::parse(&file_content) {
|
||||
if current_chunk.is_array() && current_chunk.len() < 800 {
|
||||
message_chunk = current_chunk;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
||||
}
|
||||
} else {
|
||||
// New file, use empty array
|
||||
break;
|
||||
}
|
||||
|
||||
chunk_index += 1;
|
||||
if chunk_index > 1000 {
|
||||
log_message(format!("Too many message chunks. Aborting add."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let json_obj = object! {
|
||||
"timestamp" => send_time as i64,
|
||||
"content" => message,
|
||||
"sender" => sender.to_string(),
|
||||
};
|
||||
|
||||
if let Err(e) = message_chunk.push(json_obj) {
|
||||
log_message(format!("Failed to push new message into JSON array: {}", e));
|
||||
return;
|
||||
}
|
||||
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
log_message(format!("Saving message to {}/{}", user_dir, file_name));
|
||||
save_file(&user_dir, &file_name, &message_chunk.dump());
|
||||
}
|
||||
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
|
||||
let mut messages = array![];
|
||||
|
||||
let mut latest_chunk_index: i32 = -1;
|
||||
let files = get_children(&format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
));
|
||||
|
||||
for entry in files {
|
||||
if let Some(num) = {
|
||||
entry
|
||||
.strip_prefix("msgs_")
|
||||
.and_then(|s| s.strip_suffix(".json"))
|
||||
} {
|
||||
if let Ok(index) = num.parse::<i32>() {
|
||||
if index > latest_chunk_index {
|
||||
latest_chunk_index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if latest_chunk_index == -1 {
|
||||
return messages;
|
||||
}
|
||||
|
||||
let mut to_skip = loaded_messages;
|
||||
let mut needed = amount;
|
||||
|
||||
for chunk_index in (0..=latest_chunk_index).rev() {
|
||||
if needed == 0 {
|
||||
break;
|
||||
}
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
let file_content = load_file(
|
||||
&format!(
|
||||
"communities/{}/interactables/{}/{}",
|
||||
self.get_community().get_name(),
|
||||
self.get_path(),
|
||||
self.get_name()
|
||||
),
|
||||
&file_name,
|
||||
);
|
||||
if file_content.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(chunk) = json::parse(&file_content) {
|
||||
for i in (0..chunk.len()).rev() {
|
||||
if needed == 0 {
|
||||
break;
|
||||
}
|
||||
if to_skip > 0 {
|
||||
to_skip -= 1;
|
||||
continue;
|
||||
}
|
||||
messages.push(chunk[i].clone()).unwrap();
|
||||
needed -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
}
|
||||
impl Interactable for TextChat {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"text".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
JsonValue::new_object()
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
let payload = cv.get_data(DataTypes::payload).unwrap();
|
||||
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" {
|
||||
let amount = payload["amount"].as_i64().unwrap();
|
||||
let loaded_messages = payload["loaded_messages"].as_i64().unwrap();
|
||||
let messages = self.get_messages(loaded_messages, amount);
|
||||
let mut payload = JsonValue::new_object();
|
||||
payload["messages"] = messages;
|
||||
return CommunicationValue::new(CommunicationType::function)
|
||||
.with_id(cv.get_id())
|
||||
.add_data_str(DataTypes::name, self.name.clone())
|
||||
.add_data_str(DataTypes::path, self.path.clone())
|
||||
.add_data_str(DataTypes::result, "message_chunk".to_string())
|
||||
.add_data(DataTypes::payload, payload);
|
||||
}
|
||||
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" {
|
||||
let message = payload["message"].as_str().unwrap();
|
||||
let milliseconds_timestamp: u128 = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
self.add_message(milliseconds_timestamp, cv.get_sender().unwrap(), message);
|
||||
return CommunicationValue::new(CommunicationType::function)
|
||||
.with_id(cv.get_id())
|
||||
.add_data_str(DataTypes::name, self.name.clone())
|
||||
.add_data_str(DataTypes::path, self.path.clone())
|
||||
.add_data_str(DataTypes::result, "message_received".to_string())
|
||||
.add_data(DataTypes::payload, JsonValue::new_object());
|
||||
}
|
||||
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
83
src/communities/interactables/voice_chat.rs
Normal file
83
src/communities/interactables/voice_chat.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
communities::{community::Community, interactables::interactable::Interactable},
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
};
|
||||
use json::JsonValue;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
pub enum CallUserState {
|
||||
Active,
|
||||
Muted,
|
||||
Deafed,
|
||||
}
|
||||
|
||||
pub struct CallUser {
|
||||
user_id: Uuid,
|
||||
user_state: CallUserState,
|
||||
streaming: bool,
|
||||
}
|
||||
|
||||
pub struct VoiceChat {
|
||||
name: String,
|
||||
path: String,
|
||||
community: Arc<Community>,
|
||||
users: Vec<CallUser>,
|
||||
}
|
||||
impl VoiceChat {
|
||||
pub fn new() -> VoiceChat {
|
||||
VoiceChat {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
community: Arc::new(Community::new()),
|
||||
users: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Interactable for VoiceChat {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
fn get_codec(&self) -> String {
|
||||
"voice".to_string()
|
||||
}
|
||||
fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
fn set_path(&mut self, path: String) {
|
||||
self.path = path;
|
||||
}
|
||||
fn get_community(&self) -> &Arc<Community> {
|
||||
&self.community
|
||||
}
|
||||
fn set_community(&mut self, community: Arc<Community>) {
|
||||
self.community = community;
|
||||
}
|
||||
fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
fn get_path(&self) -> &String {
|
||||
&self.path
|
||||
}
|
||||
fn get_total_path(&self) -> String {
|
||||
String::new() + &self.path + "/" + &self.name
|
||||
}
|
||||
fn get_data(&self) -> JsonValue {
|
||||
JsonValue::new_object()
|
||||
}
|
||||
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::error)
|
||||
}
|
||||
fn to_json(&self) -> JsonValue {
|
||||
let mut v = JsonValue::new_object();
|
||||
v
|
||||
}
|
||||
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
|
||||
self.community = community;
|
||||
self.name = name;
|
||||
self.path = path;
|
||||
}
|
||||
}
|
||||
11
src/communities/mod.rs
Normal file
11
src/communities/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
pub mod community_manager;
|
||||
pub mod community_socket;
|
||||
pub mod interactables {
|
||||
pub mod category;
|
||||
pub mod interactable;
|
||||
pub mod registry;
|
||||
pub mod text_chat;
|
||||
pub mod voice_chat;
|
||||
}
|
||||
pub mod community;
|
||||
pub mod community_connection;
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
use axum::Json;
|
||||
use json::number::Number;
|
||||
use json::{Array, JsonValue, object, parse, stringify};
|
||||
use std::any::Any;
|
||||
use json::{Array, JsonValue, object, parse};
|
||||
use std::collections::HashMap;
|
||||
use std::env::VarsOs;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
|
|
@ -157,6 +154,9 @@ impl DataTypes {
|
|||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum CommunicationType {
|
||||
error,
|
||||
error_invalid_user_id,
|
||||
error_not_found,
|
||||
error_invalid_challenge,
|
||||
success,
|
||||
message,
|
||||
message_send,
|
||||
|
|
@ -213,7 +213,7 @@ impl CommunicationType {
|
|||
"messageotheriota" => CommunicationType::message_other_iota,
|
||||
"messagechunk" => CommunicationType::message_chunk,
|
||||
"messagesget" => CommunicationType::messages_get,
|
||||
"message_send" => CommunicationType::message_send,
|
||||
"messagesend" => CommunicationType::message_send,
|
||||
"changeconfirm" => CommunicationType::change_confirm,
|
||||
"confirmreceive" => CommunicationType::confirm_receive,
|
||||
"confirmread" => CommunicationType::confirm_read,
|
||||
|
|
@ -259,8 +259,8 @@ impl CommunicationType {
|
|||
pub struct CommunicationValue {
|
||||
pub id: Uuid,
|
||||
pub comm_type: CommunicationType,
|
||||
pub sender: Uuid,
|
||||
pub receiver: Uuid,
|
||||
pub sender: Option<Uuid>,
|
||||
pub receiver: Option<Uuid>,
|
||||
pub data: HashMap<DataTypes, JsonValue>,
|
||||
}
|
||||
|
||||
|
|
@ -269,8 +269,8 @@ impl CommunicationValue {
|
|||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
comm_type,
|
||||
sender: Uuid::new_v4(),
|
||||
receiver: Uuid::new_v4(),
|
||||
sender: None,
|
||||
receiver: None,
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -282,17 +282,17 @@ impl CommunicationValue {
|
|||
self.id.clone()
|
||||
}
|
||||
pub fn with_sender(mut self, sender: Uuid) -> Self {
|
||||
self.sender = sender;
|
||||
self.sender = Some(sender);
|
||||
self
|
||||
}
|
||||
pub fn get_sender(&self) -> Uuid {
|
||||
pub fn get_sender(&self) -> Option<Uuid> {
|
||||
self.sender.clone()
|
||||
}
|
||||
pub fn with_receiver(mut self, receiver: Uuid) -> Self {
|
||||
self.receiver = receiver;
|
||||
self.receiver = Some(receiver);
|
||||
self
|
||||
}
|
||||
pub fn get_receiver(&self) -> Uuid {
|
||||
pub fn get_receiver(&self) -> Option<Uuid> {
|
||||
self.receiver.clone()
|
||||
}
|
||||
pub fn add_data_num(mut self, key: DataTypes, value: Number) -> Self {
|
||||
|
|
@ -323,29 +323,59 @@ impl CommunicationValue {
|
|||
for (k, v) in &self.data {
|
||||
jdata[&format!("{:?}", k)] = JsonValue::from(v.clone());
|
||||
}
|
||||
|
||||
if self.sender.is_some() && self.receiver.is_some() {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
sender: self.sender.to_string(),
|
||||
receiver: self.receiver.to_string(),
|
||||
sender: self.sender.unwrap().to_string(),
|
||||
receiver: self.receiver.unwrap().to_string(),
|
||||
data: jdata
|
||||
}
|
||||
} else if self.sender.is_some() {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
sender: self.sender.unwrap().to_string(),
|
||||
data: jdata
|
||||
}
|
||||
} else if self.receiver.is_some() {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
receiver: self.receiver.unwrap().to_string(),
|
||||
data: jdata
|
||||
}
|
||||
} else {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
data: jdata
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json(json_str: &str) -> Self {
|
||||
let parsed = parse(json_str).unwrap();
|
||||
|
||||
let comm_type = CommunicationType::parse(parsed["type"].to_string());
|
||||
|
||||
let sender: Uuid = parsed["sender"]
|
||||
let mut sender: Option<Uuid> = None;
|
||||
if parsed.has_key("sender") {
|
||||
sender = Some(
|
||||
parsed["sender"]
|
||||
.as_str()
|
||||
.and_then(|s| Uuid::parse_str(s).ok())
|
||||
.unwrap_or(Uuid::new_v4());
|
||||
let receiver: Uuid = parsed["receiver"]
|
||||
.unwrap_or(Uuid::new_v4()),
|
||||
);
|
||||
}
|
||||
let mut receiver: Option<Uuid> = None;
|
||||
if parsed.has_key("receiver") {
|
||||
receiver = Some(
|
||||
parsed["receiver"]
|
||||
.as_str()
|
||||
.and_then(|s| Uuid::parse_str(s).ok())
|
||||
.unwrap_or(Uuid::new_v4());
|
||||
.unwrap_or(Uuid::new_v4()),
|
||||
);
|
||||
}
|
||||
|
||||
let uuid = Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4());
|
||||
let mut data = HashMap::new();
|
||||
|
|
@ -386,17 +416,22 @@ impl CommunicationValue {
|
|||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let cv = CommunicationValue::new(CommunicationType::message_other_iota)
|
||||
let sender = original.get_sender();
|
||||
CommunicationValue::new(CommunicationType::message_other_iota)
|
||||
.with_id(original.get_id())
|
||||
.with_receiver(receiver.unwrap())
|
||||
.add_data( DataTypes::receiver_id,
|
||||
JsonValue::String(receiver.unwrap().to_string()),
|
||||
)
|
||||
.with_sender(sender.unwrap())
|
||||
.add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string()))
|
||||
.add_data(
|
||||
DataTypes::sender_id,
|
||||
JsonValue::String(sender.unwrap().to_string()),
|
||||
)
|
||||
.add_data(
|
||||
DataTypes::content,
|
||||
JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()),
|
||||
);
|
||||
|
||||
// include sender_id if the original had one
|
||||
let sender = original.get_sender();
|
||||
cv.add_data(DataTypes::sender_id, JsonValue::String(sender.to_string()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,18 @@ pub fn create_languages() {
|
|||
general_texts.insert("user_id", "USER ID: {}");
|
||||
general_texts.insert("user_ids", "USER IDS: {}");
|
||||
general_texts.insert("setup_completed", "Launched");
|
||||
|
||||
general_texts.insert(
|
||||
"community_active",
|
||||
"Communities active on ws://0.0.0.0:{}/community/...",
|
||||
);
|
||||
general_texts.insert(
|
||||
"community_start_error",
|
||||
"Failed to start community socket on port {}!",
|
||||
);
|
||||
general_texts.insert(
|
||||
"community_start_error_admin",
|
||||
"Failed to start community socket on port {}! Run with admin privileges",
|
||||
);
|
||||
// DEBUG
|
||||
debug_messages.insert("", "");
|
||||
save_file(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::util::file_util::{self, load_file};
|
||||
use json::{JsonValue, parse};
|
||||
use crate::util::file_util::{self};
|
||||
use json::parse;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
|
|
|||
40
src/main.rs
40
src/main.rs
|
|
@ -1,13 +1,12 @@
|
|||
use json::JsonValue::String;
|
||||
use json::{self};
|
||||
use json::{self, JsonValue::String};
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex;
|
||||
use sysinfo::User;
|
||||
use tokio::time::{Duration, sleep};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod auth;
|
||||
mod communities;
|
||||
mod data;
|
||||
mod eula;
|
||||
mod gui;
|
||||
|
|
@ -16,13 +15,16 @@ mod omikron;
|
|||
mod users;
|
||||
mod util;
|
||||
|
||||
use crate::communities::interactables::registry;
|
||||
use crate::communities::{community_manager, community_socket};
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use crate::gui::app_state::AppState;
|
||||
use crate::gui::log_panel::{log_message, log_message_trans};
|
||||
use crate::gui::{log_panel, ratatui_interface};
|
||||
use crate::langu::language_creator;
|
||||
use crate::langu::language_manager::format;
|
||||
use crate::omikron::omikron_connection::OmikronConnection;
|
||||
use crate::users::user_manager::UserManager;
|
||||
use crate::users::user_manager;
|
||||
use crate::util::config_util::CONFIG;
|
||||
|
||||
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
||||
|
|
@ -50,9 +52,9 @@ async fn main() {
|
|||
}
|
||||
|
||||
// USER MANAGEMENT
|
||||
UserManager::load_users().await;
|
||||
user_manager::load_users().await;
|
||||
let mut sb = "".to_string();
|
||||
for up in UserManager::get_users() {
|
||||
for up in user_manager::get_users() {
|
||||
sb = sb + "," + &up.user_id.to_string().as_str();
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +74,32 @@ async fn main() {
|
|||
.unwrap()
|
||||
));
|
||||
log_message(format!("User IDS: {}", sb));
|
||||
|
||||
// COMMUNITY MANAGEMENT
|
||||
registry::load_interactables().await;
|
||||
community_manager::load_communities().await;
|
||||
community_manager::save_communities().await;
|
||||
let mut sb1 = "".to_string();
|
||||
for cp in community_manager::get_communities().await {
|
||||
sb1 = sb1 + "," + &cp.get_name().to_string().as_str();
|
||||
}
|
||||
|
||||
if !sb1.is_empty() {
|
||||
sb1.remove(0);
|
||||
sb1 = sb1 + ",";
|
||||
}
|
||||
log_message(format!("Community IDS: {}", sb1));
|
||||
let port = CONFIG.lock().unwrap().get_port();
|
||||
if community_socket::start(port).await {
|
||||
log_message(format("community_active", &[&port.to_string()]));
|
||||
} else {
|
||||
if port < 1024 {
|
||||
log_message(format("community_start_error_admin", &[&port.to_string()]));
|
||||
} else {
|
||||
log_message(format("community_start_error", &[&port.to_string()]));
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let omikron: OmikronConnection = OmikronConnection::new();
|
||||
omikron.connect().await;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use crate::gui::log_panel::log_message_trans;
|
||||
use crate::gui::log_panel::{log_cv, log_message};
|
||||
use crate::users::contact::Contact;
|
||||
use crate::users::user_community_util::UserCommunityUtil;
|
||||
use crate::util::chat_files::ChatFiles;
|
||||
use crate::util::chat_files;
|
||||
use crate::util::chats_util::{get_user, get_users, mod_user};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use json::JsonValue;
|
||||
|
|
@ -33,6 +32,7 @@ pub struct OmikronConnection {
|
|||
>,
|
||||
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync>>>>, // waiting for responses
|
||||
pingpong: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, // ping-pong handler
|
||||
pub last_ping: Arc<Mutex<i64>>,
|
||||
pub message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>,
|
||||
pub is_connected: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ impl OmikronConnection {
|
|||
writer: Arc::new(Mutex::new(None)),
|
||||
waiting: Arc::new(Mutex::new(HashMap::new())),
|
||||
pingpong: Arc::new(Mutex::new(None)),
|
||||
last_ping: Arc::new(Mutex::new(-1)),
|
||||
message_send_times: Arc::new(Mutex::new(HashMap::new())),
|
||||
is_connected: Arc::new(Mutex::new(false)),
|
||||
}
|
||||
|
|
@ -112,14 +113,14 @@ impl OmikronConnection {
|
|||
// ************************************************ //
|
||||
log_cv(&cv);
|
||||
if cv.is_type(CommunicationType::message_other_iota) {
|
||||
let sender_id = &cv.get_sender();
|
||||
let receiver_id = &cv.get_receiver();
|
||||
let sender_id = &cv.get_sender().unwrap();
|
||||
let receiver_id = &cv.get_receiver().unwrap();
|
||||
|
||||
ChatFiles::add_message(
|
||||
chat_files::add_message(
|
||||
cv.get_data(DataTypes::send_time)
|
||||
.unwrap()
|
||||
.as_i64()
|
||||
.unwrap_or(-1),
|
||||
.unwrap_or(0) as u128,
|
||||
false,
|
||||
*receiver_id,
|
||||
*sender_id,
|
||||
|
|
@ -127,7 +128,7 @@ impl OmikronConnection {
|
|||
);
|
||||
let response = CommunicationValue::new(CommunicationType::message_live)
|
||||
.with_id(cv.get_id())
|
||||
.with_receiver(cv.get_receiver())
|
||||
.with_receiver(cv.get_receiver().unwrap())
|
||||
.add_data(
|
||||
DataTypes::send_time,
|
||||
cv.get_data(DataTypes::send_time).unwrap().clone(),
|
||||
|
|
@ -138,7 +139,7 @@ impl OmikronConnection {
|
|||
)
|
||||
.add_data(
|
||||
DataTypes::sender_id,
|
||||
JsonValue::String(cv.get_sender().clone().to_string()),
|
||||
JsonValue::String(cv.get_sender().unwrap().to_string()),
|
||||
);
|
||||
Self::send_message_static(
|
||||
&writer.clone(),
|
||||
|
|
@ -161,16 +162,16 @@ impl OmikronConnection {
|
|||
],
|
||||
"content": "<enc markdown>"
|
||||
*/
|
||||
let my_id = cv.get_sender();
|
||||
let my_id = cv.get_sender().unwrap();
|
||||
let other_id = Uuid::from_str(
|
||||
&*cv.get_data(DataTypes::receiver_id).unwrap().to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
ChatFiles::add_message(
|
||||
chat_files::add_message(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64,
|
||||
.as_millis() as u128,
|
||||
true,
|
||||
my_id,
|
||||
other_id,
|
||||
|
|
@ -189,7 +190,7 @@ impl OmikronConnection {
|
|||
}
|
||||
|
||||
if cv.is_type(CommunicationType::messages_get) {
|
||||
let my_id = cv.get_sender();
|
||||
let my_id = cv.get_sender().unwrap();
|
||||
let partner_id = Uuid::from_str(
|
||||
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
|
||||
)
|
||||
|
|
@ -207,7 +208,7 @@ impl OmikronConnection {
|
|||
.parse::<i64>()
|
||||
.unwrap_or(0);
|
||||
let messages =
|
||||
ChatFiles::get_messages(my_id, partner_id, offset, amount);
|
||||
chat_files::get_messages(my_id, partner_id, offset, amount);
|
||||
let resp = CommunicationValue::new(CommunicationType::messages_get)
|
||||
.with_id(cv.get_id())
|
||||
.with_receiver(my_id)
|
||||
|
|
@ -219,7 +220,7 @@ impl OmikronConnection {
|
|||
}
|
||||
|
||||
if cv.is_type(CommunicationType::get_chats) {
|
||||
let user_id = cv.get_sender();
|
||||
let user_id = cv.get_sender().unwrap();
|
||||
let users = get_users(user_id);
|
||||
let resp = CommunicationValue::new(CommunicationType::get_chats)
|
||||
.with_id(cv.get_id())
|
||||
|
|
@ -231,7 +232,7 @@ impl OmikronConnection {
|
|||
}
|
||||
|
||||
if cv.is_type(CommunicationType::add_chat) {
|
||||
let user_id = cv.get_sender();
|
||||
let user_id = cv.get_sender().unwrap();
|
||||
let other_id = Uuid::from_str(
|
||||
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
|
||||
)
|
||||
|
|
@ -255,7 +256,7 @@ impl OmikronConnection {
|
|||
|
||||
if cv.is_type(CommunicationType::add_community) {
|
||||
UserCommunityUtil::add_community(
|
||||
cv.get_sender(),
|
||||
cv.get_sender().unwrap(),
|
||||
cv.get_data(DataTypes::community_address)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
|
|
@ -264,7 +265,7 @@ impl OmikronConnection {
|
|||
);
|
||||
let resp = CommunicationValue::new(CommunicationType::add_community)
|
||||
.with_id(cv.get_id())
|
||||
.with_receiver(cv.get_sender());
|
||||
.with_receiver(cv.get_sender().unwrap());
|
||||
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
|
||||
.await;
|
||||
continue;
|
||||
|
|
@ -273,11 +274,11 @@ impl OmikronConnection {
|
|||
if cv.is_type(CommunicationType::get_communities) {
|
||||
let resp = CommunicationValue::new(CommunicationType::get_communities)
|
||||
.with_id(cv.get_id())
|
||||
.with_receiver(cv.get_sender())
|
||||
.with_receiver(cv.get_sender().unwrap())
|
||||
.add_array(
|
||||
DataTypes::communities,
|
||||
UserCommunityUtil::get_communities(cv.get_sender()),
|
||||
); // needs UserCommunityUtil
|
||||
UserCommunityUtil::get_communities(cv.get_sender().unwrap()),
|
||||
);
|
||||
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
|
||||
.await;
|
||||
continue;
|
||||
|
|
@ -285,14 +286,14 @@ impl OmikronConnection {
|
|||
|
||||
if cv.is_type(CommunicationType::remove_community) {
|
||||
UserCommunityUtil::remove_community(
|
||||
cv.get_sender(),
|
||||
cv.get_sender().unwrap(),
|
||||
cv.get_data(DataTypes::community_address)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
); // needs UserCommunityUtil
|
||||
let resp = CommunicationValue::new(CommunicationType::remove_community)
|
||||
.with_id(cv.get_id())
|
||||
.with_receiver(cv.get_sender());
|
||||
.with_receiver(cv.get_sender().unwrap());
|
||||
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
|
||||
.await;
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::APP_STATE;
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use crate::omikron::omikron_connection::OmikronConnection;
|
||||
use json::JsonValue;
|
||||
use json::number::Number;
|
||||
use std::sync::Arc;
|
||||
use tokio::time::Instant;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -17,7 +19,10 @@ impl OmikronConnection {
|
|||
pub async fn send_ping_message(&self, uuid: Uuid) {
|
||||
let ping_message = CommunicationValue::new(CommunicationType::ping)
|
||||
.with_id(uuid)
|
||||
.add_data_num(DataTypes::last_ping, Number::from(2))
|
||||
.add_data_num(
|
||||
DataTypes::last_ping,
|
||||
Number::from(*self.last_ping.lock().await),
|
||||
)
|
||||
.to_json()
|
||||
.to_string();
|
||||
|
||||
|
|
@ -33,11 +38,13 @@ impl OmikronConnection {
|
|||
};
|
||||
|
||||
if let Some(send_time) = send_time_opt {
|
||||
let ping = Instant::now().duration_since(send_time).as_millis() as f64;
|
||||
let ping = Instant::now().duration_since(send_time).as_millis() as i64;
|
||||
self.message_send_times.lock().await.remove(&id);
|
||||
|
||||
*self.last_ping.lock().await = ping as i64;
|
||||
|
||||
if log {
|
||||
APP_STATE.lock().unwrap().push_ping_val(ping);
|
||||
APP_STATE.lock().unwrap().push_ping_val(ping as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,32 +3,23 @@ use crate::users::user_profile::UserProfile;
|
|||
use crate::users::user_profile_full::UserProfileFull;
|
||||
use crate::util::config_util::CONFIG;
|
||||
use crate::util::file_util::{load_file, save_file};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use der::DerOrd;
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use hex;
|
||||
use json::JsonValue;
|
||||
use once_cell::sync::Lazy;
|
||||
use pkcs8::EncodePublicKey;
|
||||
use pkcs8::spki::SubjectPublicKeyInfoOwned;
|
||||
use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo, der::Encode};
|
||||
use rand::Rng;
|
||||
use rand_core::OsRng;
|
||||
use rand_core::RngCore;
|
||||
use rustls::pki_types::SubjectPublicKeyInfoDer;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use x448::{PublicKey, Secret};
|
||||
use x509::AlgorithmIdentifier;
|
||||
|
||||
pub struct UserManager;
|
||||
|
||||
static USERS: Lazy<Mutex<Vec<UserProfile>>> = Lazy::new(|| Mutex::new(Vec::new()));
|
||||
static UNIQUE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
|
||||
|
||||
impl UserManager {
|
||||
pub async fn create_user(username: &str) -> Option<UserProfileFull> {
|
||||
pub async fn create_user(username: &str) -> Option<UserProfileFull> {
|
||||
let user_id = auth_connector::get_register().await.unwrap();
|
||||
let mut buf = [0u8; 56];
|
||||
let mut rng = OsRng;
|
||||
|
|
@ -37,76 +28,67 @@ impl UserManager {
|
|||
let public_key = PublicKey::from(&private_key);
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(
|
||||
&general_purpose::STANDARD
|
||||
.encode(&private_key.as_bytes())
|
||||
.as_bytes(),
|
||||
);
|
||||
hasher.update(&STANDARD.encode(&private_key.as_bytes()).as_bytes());
|
||||
let result = hasher.finalize();
|
||||
let private_key_hash = hex::encode(result);
|
||||
|
||||
let mut bytes = [0u8; 192];
|
||||
OsRng.fill(bytes.as_mut());
|
||||
let reset_token = general_purpose::STANDARD.encode(&bytes);
|
||||
let reset_token = STANDARD.encode(&bytes);
|
||||
|
||||
let up = UserProfile::new(
|
||||
user_id,
|
||||
username.to_string(),
|
||||
None,
|
||||
general_purpose::STANDARD.encode(&public_key.as_bytes()),
|
||||
STANDARD.encode(&public_key.as_bytes()),
|
||||
private_key_hash,
|
||||
reset_token,
|
||||
);
|
||||
|
||||
let up_full = UserProfileFull {
|
||||
user_profile: up.clone(),
|
||||
private_key: general_purpose::STANDARD.encode(&private_key.as_bytes()),
|
||||
private_key: STANDARD.encode(&private_key.as_bytes()),
|
||||
};
|
||||
|
||||
auth_connector::complete_register(&up, &CONFIG.lock().unwrap().get_iota_id().to_string())
|
||||
.await;
|
||||
auth_connector::complete_register(&up, &CONFIG.lock().unwrap().get_iota_id().to_string()).await;
|
||||
save_file(
|
||||
"",
|
||||
&format!("{}.tu", username),
|
||||
&format!(
|
||||
"{}::{}",
|
||||
user_id,
|
||||
general_purpose::STANDARD.encode(&private_key.as_bytes())
|
||||
),
|
||||
&format!("{}::{}", user_id, STANDARD.encode(&private_key.as_bytes())),
|
||||
);
|
||||
|
||||
USERS.lock().unwrap().push(up);
|
||||
Self::save_users().ok();
|
||||
save_users().ok();
|
||||
Some(up_full)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user(user_id: Uuid) -> Option<UserProfile> {
|
||||
pub fn get_user(user_id: Uuid) -> Option<UserProfile> {
|
||||
USERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|u| u.user_id == user_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_users() -> Vec<UserProfile> {
|
||||
pub fn get_users() -> Vec<UserProfile> {
|
||||
USERS.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_user(up: UserProfile) {
|
||||
pub fn add_user(up: UserProfile) {
|
||||
let mut users = USERS.lock().unwrap();
|
||||
users.retain(|u| u.user_id != up.user_id);
|
||||
users.push(up);
|
||||
*UNIQUE.lock().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_user(user_id: Uuid) {
|
||||
pub fn remove_user(user_id: Uuid) {
|
||||
let mut users = USERS.lock().unwrap();
|
||||
users.retain(|u| u.user_id != user_id);
|
||||
*UNIQUE.lock().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_users() -> io::Result<()> {
|
||||
pub fn save_users() -> io::Result<()> {
|
||||
*UNIQUE.lock().unwrap() = false;
|
||||
let users = USERS.lock().unwrap();
|
||||
let arr: Vec<JsonValue> = users.iter().map(|u| u.to_json()).collect();
|
||||
|
|
@ -114,16 +96,16 @@ impl UserManager {
|
|||
|
||||
save_file("", "users.json", &json_str);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_users() -> io::Result<()> {
|
||||
pub async fn load_users() -> io::Result<()> {
|
||||
let content = load_file("", "users.json");
|
||||
if content.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let parsed = json::parse(&content)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
let parsed =
|
||||
json::parse(&content).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
if let JsonValue::Array(arr) = parsed {
|
||||
let mut users = USERS.lock().unwrap();
|
||||
for j in arr.iter() {
|
||||
|
|
@ -133,12 +115,11 @@ impl UserManager {
|
|||
}
|
||||
}
|
||||
if *UNIQUE.lock().unwrap() {
|
||||
Self::save_users().ok();
|
||||
save_users().ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_unique(val: bool) {
|
||||
pub fn set_unique(val: bool) {
|
||||
*UNIQUE.lock().unwrap() = val;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
use crate::auth::auth_connector;
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::users::user_manager::UserManager;
|
||||
use crate::users::user_manager;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use json::{JsonValue, object, stringify};
|
||||
use json::{JsonValue, object};
|
||||
use rand::Rng;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
// --- UserProfile ---
|
||||
|
|
@ -81,7 +76,7 @@ impl UserProfile {
|
|||
{
|
||||
if auth_connector::migrate_user(&mut up).await {
|
||||
log_message(format!("[INFO] Migration triggered for {}", up.username));
|
||||
UserManager::set_unique(true);
|
||||
user_manager::set_unique(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
use crate::util::file_util::{get_children, load_file, save_file};
|
||||
use json::{self, JsonValue, array, object};
|
||||
use sha2::digest::typenum::Add1;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::{self};
|
||||
use std::path::Path;
|
||||
use sys_info::loadavg;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::langu::language_manager::format;
|
||||
|
||||
pub enum MessageState {
|
||||
Read,
|
||||
|
|
@ -28,16 +24,13 @@ impl MessageState {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ChatFiles;
|
||||
|
||||
impl ChatFiles {
|
||||
pub fn add_message(
|
||||
send_time: i64,
|
||||
pub fn add_message(
|
||||
send_time: u128,
|
||||
storage_owner_is_sender: bool,
|
||||
storage_owner: Uuid,
|
||||
external_user: Uuid,
|
||||
message: &str,
|
||||
) {
|
||||
) {
|
||||
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(&user_dir) {
|
||||
|
|
@ -75,9 +68,9 @@ impl ChatFiles {
|
|||
}
|
||||
|
||||
let json_obj = object! {
|
||||
"message_time" => send_time,
|
||||
"message_content" => message,
|
||||
"sender_is_me" => storage_owner_is_sender,
|
||||
"timestamp" => send_time as i64,
|
||||
"content" => message,
|
||||
"sent_by_self" => storage_owner_is_sender,
|
||||
"message_state" => MessageState::Sending.as_str()
|
||||
};
|
||||
|
||||
|
|
@ -89,13 +82,13 @@ impl ChatFiles {
|
|||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
log_message(format!("Saving message to {}/{}", user_dir, file_name));
|
||||
save_file(&user_dir, &file_name, &message_chunk.dump());
|
||||
}
|
||||
pub fn change_message_state(
|
||||
}
|
||||
pub fn change_message_state(
|
||||
storage_owner: Uuid,
|
||||
external_user: Uuid,
|
||||
timestamp: i64,
|
||||
new_state: MessageState,
|
||||
) -> std::io::Result<()> {
|
||||
) -> std::io::Result<()> {
|
||||
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
|
||||
let path = Path::new(&user_dir);
|
||||
|
||||
|
|
@ -133,14 +126,14 @@ impl ChatFiles {
|
|||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_messages(
|
||||
pub fn get_messages(
|
||||
storage_owner: Uuid,
|
||||
external_user: Uuid,
|
||||
loaded_messages: i64,
|
||||
amount: i64,
|
||||
) -> JsonValue {
|
||||
) -> JsonValue {
|
||||
let mut messages = array![];
|
||||
|
||||
let mut latest_chunk_index: i32 = -1;
|
||||
|
|
@ -195,5 +188,4 @@ impl ChatFiles {
|
|||
}
|
||||
|
||||
messages
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
use axum::Json;
|
||||
use json::{self, JsonValue, array};
|
||||
use std::path::Path;
|
||||
use std::string::String;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::users::contact::Contact;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use crate::util::file_util::{load_file, save_file};
|
||||
use json::JsonValue;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fs::{self, File};
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -36,6 +34,10 @@ impl ConfigUtil {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_port(&self) -> u16 {
|
||||
self.config["port"].as_u16().unwrap_or(1984)
|
||||
}
|
||||
|
||||
pub fn change(&mut self, key: &str, value: Uuid) {
|
||||
self.config[key] = JsonValue::String(value.to_string());
|
||||
self.unique = true;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::time::SystemTime;
|
||||
use sysinfo::System;
|
||||
use uuid::Uuid;
|
||||
use walkdir::WalkDir;
|
||||
|
|
|
|||
Loading…
Reference in a new issue