[Add] basic split

This commit is contained in:
Alex Emmet 2026-04-04 19:46:21 +02:00
commit 3cdf7c62d5
77 changed files with 506 additions and 648 deletions

View file

@ -0,0 +1,61 @@
use json::{self, JsonValue, number::Number};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct Contact {
pub user_id: i64,
pub user_name: Option<String>,
pub last_message_at: Option<i64>,
}
impl Default for Contact {
fn default() -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64;
Contact {
user_id: 0,
user_name: None,
last_message_at: Some(now),
}
}
}
impl Contact {
pub fn new(user_id: i64) -> Self {
Contact {
user_id: user_id,
user_name: None,
last_message_at: None,
}
}
pub fn set_last_message_at(&mut self, p0: i64) {
self.last_message_at = Option::from(p0);
}
pub fn to_json(&self) -> JsonValue {
let mut obj = JsonValue::new_object();
obj["user_id"] = JsonValue::Number(Number::from(self.user_id));
if let Some(name) = &self.user_name {
obj["user_name"] = JsonValue::from(name.as_str());
}
if let Some(ts) = &self.last_message_at {
obj["last_message_at"] = JsonValue::Number(Number::from(*ts));
}
obj
}
pub fn from_json(o: &JsonValue) -> Contact {
let user_id = o["user_id"].as_i64().unwrap_or(0);
let user_name = o["user_name"].as_str().map(|s| s.to_string());
let last_message_at = o["last_message_at"].as_i64();
Contact {
user_id,
user_name,
last_message_at,
}
}
}

View file

@ -0,0 +1,4 @@
pub mod contact;
pub mod user_community_util;
pub mod user_manager;
pub mod user_profile;

View file

@ -0,0 +1,67 @@
use crate::util::file_util::save_file;
use json::{self, Array, JsonValue};
use std::fs;
use std::path::Path;
pub struct UserCommunityUtil;
impl UserCommunityUtil {
pub fn add_community(storage_owner: i64, address: String, title: String, position: String) {
let file_path = format!("users/{}/", storage_owner);
let mut communities = Self::load_array(&file_path);
let mut community = JsonValue::new_object();
community["title"] = JsonValue::String(title);
community["address"] = JsonValue::String(address);
community["position"] = JsonValue::String(position);
communities.push(community);
save_file(
&file_path,
"communities.json",
&JsonValue::Array(communities).to_string(),
);
}
pub fn remove_community(storage_owner: i64, community_address: String) {
let file_path = format!("users/{}/", storage_owner);
let communities = Self::load_array(&file_path);
let filtered: Array = communities
.iter()
.filter(|entry| entry["address"].as_str() != Some(&community_address))
.cloned()
.collect();
save_file(
&file_path,
"communities.json",
&JsonValue::Array(filtered).to_string(),
);
}
pub fn get_communities(storage_owner: i64) -> Array {
let file_path = format!("users/{}/communities.json", storage_owner);
Self::load_array(&file_path)
}
fn load_array(file_path: &str) -> Array {
if !Path::new(file_path).exists() {
return Array::new();
}
match fs::read_to_string(file_path) {
Ok(content) => {
let parsed = json::parse(&content);
match parsed {
Ok(JsonValue::Array(arr)) => arr,
_ => Array::new(),
}
}
Err(err) => {
eprintln!("Failed to read file {}: {}", file_path, err);
Array::new()
}
}
}
}

View file

@ -0,0 +1,197 @@
use crate::omikron::omikron_connection::OMIKRON_CONNECTION;
use crate::users::user_profile::UserProfile;
use crate::util::crypto_helper::{self, public_key_to_base64};
use crate::util::file_util::{load_file, save_file};
use crate::util::logger::PrintType;
use crate::{RELOAD, SHUTDOWN};
use crate::{log, log_cv};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use hex::{self};
use json::JsonValue;
use once_cell::sync::Lazy;
use rand::Rng;
use rand_core::OsRng;
use rand_core::RngCore;
use sha2::{Digest, Sha256};
use std::io::{self};
use std::sync::Mutex;
use std::time::Duration;
use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue};
use x448::{PublicKey, Secret};
static USERS: Lazy<Mutex<Vec<UserProfile>>> = Lazy::new(|| Mutex::new(Vec::new()));
static UNIQUE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
#[allow(dead_code)]
pub async fn load_from_tu(username: &str) -> Result<(), ()> {
let file_content = load_file("", &format!("{}.tu", username));
let segments = file_content.split("::").collect::<Vec<&str>>();
let uuid = segments[0].parse::<i64>().unwrap_or(0);
let b64_private_key = segments[1];
let secret: Secret = crypto_helper::load_secret_key(b64_private_key).unwrap();
let public_key = PublicKey::from(&secret);
let mut bytes = [0u8; 192];
OsRng.fill(bytes.as_mut());
let reset_token = STANDARD.encode(&bytes);
let user_profile = UserProfile::new(
uuid,
username.to_string(),
Some(username.to_string()),
crypto_helper::public_key_to_base64(&public_key),
crypto_helper::hex_hash(b64_private_key),
reset_token,
);
USERS.lock().unwrap().push(user_profile);
Ok(())
}
pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>) {
let register_cv = CommunicationValue::new(CommunicationType::get_register);
let conn = OMIKRON_CONNECTION.clone();
let response_cv = match conn
.await_response(&register_cv, Some(Duration::from_secs(20)))
.await
{
Ok(cv) => cv,
Err(_) => return (None, None),
};
log_cv!(PrintType::Omega, response_cv);
let user_id = match response_cv.get_data(DataTypes::user_id).as_number() {
Some(id) => id,
None => return (None, None),
};
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);
let mut hasher = Sha256::new();
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 = STANDARD.encode(&bytes);
let up = UserProfile::new(
user_id,
username.to_string(),
None,
STANDARD.encode(&public_key.as_bytes()),
private_key_hash,
reset_token.clone(),
);
let cv = CommunicationValue::new(CommunicationType::complete_register_user)
.add_data(DataTypes::user_id, DataValue::Number(user_id))
.add_data(DataTypes::username, DataValue::Str(username.to_string()))
.add_data(
DataTypes::public_key,
DataValue::Str(public_key_to_base64(&public_key)),
)
.add_data(DataTypes::iota_id, DataValue::Number(user_id))
.add_data(DataTypes::reset_token, DataValue::Str(reset_token));
let response_cv = conn
.await_response(&cv, Some(Duration::from_secs(20)))
.await;
if let Ok(resp) = response_cv {
log_cv!(PrintType::Omega, resp);
if !resp.is_type(CommunicationType::success) {
return (None, None);
}
} else {
return (None, None);
}
*SHUTDOWN.write().await = true;
*RELOAD.write().await = true;
log!("Created User");
save_file(
"",
&format!("{}.tu", username),
&format!("{}::{}", user_id, STANDARD.encode(&private_key.as_bytes())),
);
USERS.lock().unwrap().push(up.clone());
save_users();
(Some(up), Some(STANDARD.encode(&private_key.as_bytes())))
}
pub fn get_user_by_username(username: &str) -> Option<UserProfile> {
USERS
.lock()
.unwrap()
.iter()
.cloned()
.find(|u| u.username == username)
}
pub fn get_user(user_id: i64) -> Option<UserProfile> {
USERS
.lock()
.unwrap()
.iter()
.cloned()
.find(|u| u.user_id == user_id)
}
pub fn get_users() -> Vec<UserProfile> {
USERS.lock().unwrap().clone()
}
pub fn remove_user(user_id: i64) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != user_id);
*UNIQUE.lock().unwrap() = true;
}
pub fn save_users() {
*UNIQUE.lock().unwrap() = false;
let users = USERS.lock().unwrap();
let arr: Vec<JsonValue> = users.iter().map(|u| u.to_json()).collect();
let json_str = JsonValue::Array(arr).dump();
save_file("", "users.json", &json_str);
}
pub fn clear() {
let mut users = USERS.lock().unwrap();
users.clear();
*UNIQUE.lock().unwrap() = true;
}
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()))?;
if let JsonValue::Array(arr) = parsed {
let mut users = USERS.lock().unwrap();
for j in arr.iter() {
if let Some(up) = UserProfile::from_json(j).await {
users.push(up);
}
}
}
if *UNIQUE.lock().unwrap() {
save_users();
}
Ok(())
}
#[allow(dead_code)]
pub fn set_unique(val: bool) {
*UNIQUE.lock().unwrap() = val;
}

View file

@ -0,0 +1,126 @@
use std::time::{SystemTime, UNIX_EPOCH};
use crate::util::file_util::{has_file, load_file, used_dir_space};
use base64::{Engine as _, engine::general_purpose};
use json::{JsonValue, object};
use rand::Rng;
use rand::rngs::OsRng;
// --- UserProfile ---
#[derive(Clone, Debug)]
pub struct UserProfile {
pub user_id: i64,
pub username: String,
pub public_key: String,
pub private_key_hash: String,
pub reset_token: String,
pub created_at: i64,
pub display_name: Option<String>,
}
impl UserProfile {
pub fn new(
user_id: i64,
username: String,
display_name: Option<String>,
public_key: String,
private_key_hash: String,
reset_token: String,
) -> Self {
Self {
user_id,
username,
display_name,
public_key,
private_key_hash,
created_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64,
reset_token,
}
}
pub fn to_json(&self) -> JsonValue {
let mut obj = object! {
"uuid" => self.user_id,
"username" => self.username.clone(),
"public_key" => self.public_key.clone(),
"private_key_hash" => self.private_key_hash.clone(),
"created_at" => self.created_at,
"reset_token" => self.reset_token.clone()
};
if let Some(d) = &self.display_name {
obj["display_name"] = d.clone().into();
}
obj
}
pub fn frontend(&self) -> JsonValue {
let mut obj = object! {
"uuid" => self.user_id,
"username" => self.username.clone(),
"public_key" => self.public_key.clone(),
"private_key_hash" => self.private_key_hash.clone(),
"created_at" => self.created_at,
"storage" => used_dir_space(&format!("users/{}", self.user_id.to_string())),
};
if let Some(d) = &self.display_name {
obj["display_name"] = d.clone().into();
}
if has_file("", &format!("{}.tu", self.username.clone())) {
obj["tu"] = load_file("", &format!("{}.tu", self.username.clone())).into();
}
obj
}
pub async fn from_json(j: &JsonValue) -> Option<Self> {
let user_id = j["uuid"].as_i64()?;
let username = j["username"].as_str()?.to_string();
let public_key = j["public_key"].as_str()?.to_string();
let private_key_hash = j["private_key_hash"].as_str()?.to_string();
let reset_token = j["reset_token"].as_str()?.to_string();
let created_at = j["created_at"].as_i64()?;
let display_name = j["display_name"].as_str().map(|s| s.to_string());
let up = UserProfile {
user_id,
username,
display_name,
public_key,
private_key_hash,
created_at,
reset_token,
};
// TODO: Migrate to Omikron / Wss
/* if j.has_key("migrate")
|| j.has_key("migrating")
|| j.has_key("changing")
|| j.has_key("move")
|| j.has_key("moving")
{
if auth_connector::migrate_user(&mut up).await {
log_message(format!("[INFO] Migration triggered for {}", up.username));
user_manager::set_unique(true);
}
} */
Some(up)
}
#[allow(dead_code)]
pub fn randomize_reset_token(&mut self) -> String {
let mut bytes = [0u8; 192];
OsRng.fill(bytes.as_mut());
let new_token = general_purpose::STANDARD.encode(&bytes);
self.reset_token = new_token.clone();
new_token
}
#[allow(dead_code)]
pub fn get_display_name(&self) -> String {
self.display_name
.clone()
.unwrap_or_else(|| self.username.clone())
}
}