Local Auth (start of dezentralization)

Prep for WebUI
Upgrade to SSL optional
This commit is contained in:
Alex Emmet 2025-11-16 02:14:02 +01:00
commit 5f40158b00
11 changed files with 456 additions and 73 deletions

17
Cargo.lock generated
View file

@ -8,6 +8,7 @@ version = "0.1.0"
dependencies = [
"aes-gcm",
"async-trait",
"async-tungstenite",
"aws-lc-rs",
"axum",
"base64",
@ -138,6 +139,22 @@ dependencies = [
"syn",
]
[[package]]
name = "async-tungstenite"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91"
dependencies = [
"atomic-waker",
"futures-core",
"futures-io",
"futures-task",
"futures-util",
"log",
"pin-project-lite",
"tungstenite",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"

View file

@ -35,7 +35,7 @@ serde = { version = "1.0.219", features = ["derive"] }
sha2 = "*"
sys-info = "*"
sysinfo = "0.30"
tokio = { version = "*", features = ["full"] }
tokio = { version = "1.48.0", features = ["full"] }
tokio-util = { version = "*", features = ["full"] }
tokio-tungstenite = { version = "*", features = ["native-tls"] }
tower = "*"
@ -49,3 +49,4 @@ tokio-rustls = "0.26.4"
rustls-pemfile = "2.2.0"
async-trait = "0.1.89"
sha1 = "0.10.6"
async-tungstenite = "0.32.0"

19
src/auth/local_auth.rs Normal file
View file

@ -0,0 +1,19 @@
use json::JsonValue;
use uuid::Uuid;
use crate::util::file_util::load_file;
pub fn is_private_key_valid(user_id: &Uuid, key_hash: &str) -> bool {
let file_contents = load_file("", "users.json");
let users = json::parse(&file_contents).unwrap();
if let JsonValue::Array(users_array) = users {
for user in users_array {
if user["uuid"] == user_id.to_string() && user["private_key_hash"] == key_hash {
return true;
}
}
}
false
}

View file

@ -1,2 +1,3 @@
pub mod auth_connector;
pub mod crypto_helper;
pub mod local_auth;

View file

@ -10,6 +10,9 @@ use uuid::Uuid;
pub enum DataTypes {
error_type,
accepted_ids,
uuid,
settings,
settings_name,
chat_partner_id,
iota_id,
user_id,
@ -84,6 +87,9 @@ impl DataTypes {
match normalized.as_str() {
"errortype" => DataTypes::error_type,
"chatpartnerid" => DataTypes::chat_partner_id,
"uuid" => DataTypes::uuid,
"settings" => DataTypes::settings,
"settingsname" => DataTypes::settings_name,
"iotaid" => DataTypes::iota_id,
"userid" => DataTypes::user_id,
"userids" => DataTypes::user_ids,
@ -163,6 +169,9 @@ pub enum CommunicationType {
error_invalid_secret,
error_invalid_private_key,
success,
settings_save,
settings_load,
settings_list,
message,
message_send,
message_live,
@ -191,6 +200,7 @@ pub enum CommunicationType {
iota_closed,
client_changed,
client_connected,
client_disconnected,
client_closed,
public_key,
private_key,
@ -212,6 +222,9 @@ impl CommunicationType {
match normalized.as_str() {
"error" => CommunicationType::error,
"settingssave" => CommunicationType::settings_save,
"settingsload" => CommunicationType::settings_load,
"settingslist" => CommunicationType::settings_list,
"success" => CommunicationType::success,
"message" => CommunicationType::message,
"messagelive" => CommunicationType::message_live,
@ -241,6 +254,7 @@ impl CommunicationType {
"iotaclosed" => CommunicationType::iota_closed,
"clientchanged" => CommunicationType::client_changed,
"clientconnected" => CommunicationType::client_connected,
"clientdisconnected" => CommunicationType::client_disconnected,
"clientclosed" => CommunicationType::client_closed,
"publickey" => CommunicationType::public_key,
"privatekey" => CommunicationType::private_key,

View file

@ -24,6 +24,7 @@ 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::OMIKRON_CONNECTION;
use crate::omikron::omikron_connection::OmikronConnection;
use crate::server::server::start;
use crate::users::user_manager;
@ -31,6 +32,7 @@ use crate::util::config_util::CONFIG;
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
#[allow(unused_must_use, dead_code)]
async fn main() {
@ -115,7 +117,7 @@ async fn main() {
}
loop {
let omikron: OmikronConnection = OmikronConnection::new();
let omikron: Arc<OmikronConnection> = Arc::new(OmikronConnection::new());
omikron.connect().await;
omikron
.send_message(
@ -131,6 +133,8 @@ async fn main() {
.to_string(),
)
.await;
let mut omikron_connection = OMIKRON_CONNECTION.write().await;
*omikron_connection = Some(omikron.clone());
log_message_trans("setup_completed");
loop {
if !omikron.is_connected().await {

View file

@ -1,3 +1,4 @@
use crate::auth::local_auth;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::log_panel::{log_cv, log_message, log_message_trans};
use crate::langu::language_manager::format;
@ -5,33 +6,39 @@ use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files;
use crate::util::chats_util::{get_user, get_users, mod_user};
use crate::util::file_util::{get_children, load_file, save_file};
use futures::Stream;
use futures::stream::{SplitSink, SplitStream};
use futures_util::sink::Sink;
use futures_util::{SinkExt, StreamExt};
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use json::JsonValue;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::{Arc, LazyLock};
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio::sync::{Mutex, RwLock};
use tokio::time::{Duration, Instant, sleep};
use tokio_tungstenite::{
MaybeTlsStream, WebSocketStream, connect_async, tungstenite::protocol::Message,
};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use tungstenite::Utf8Bytes;
use uuid::Uuid;
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
LazyLock::new(|| Arc::new(RwLock::new(None)));
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionVariant {
Omikron,
ClientUnauthenticated,
ClientAuthenticated,
}
#[derive(Clone)]
pub struct OmikronConnection {
pub(crate) writer: Arc<
Mutex<
Option<
futures_util::stream::SplitSink<
WebSocketStream<MaybeTlsStream<TcpStream>>,
Message,
>,
>,
>,
>,
pub variant: Arc<RwLock<ConnectionVariant>>,
pub user_id: Arc<RwLock<Option<Uuid>>>,
pub(crate) writer:
Arc<Mutex<Option<Box<dyn Sink<Message, Error = tungstenite::Error> + Send + Unpin>>>>,
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>>,
@ -42,6 +49,8 @@ pub struct OmikronConnection {
impl OmikronConnection {
pub fn new() -> Self {
Self {
variant: Arc::new(RwLock::new(ConnectionVariant::Omikron)),
user_id: Arc::new(RwLock::new(None)),
writer: Arc::new(Mutex::new(None)),
waiting: Arc::new(Mutex::new(HashMap::new())),
pingpong: Arc::new(Mutex::new(None)),
@ -50,17 +59,42 @@ impl OmikronConnection {
is_connected: Arc::new(Mutex::new(false)),
}
}
pub async fn client(
writer: SplitSink<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>, Message>,
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
) -> Arc<Self> {
let connection = Arc::new(Self {
variant: Arc::new(RwLock::new(ConnectionVariant::ClientUnauthenticated)),
user_id: Arc::new(RwLock::new(None)),
writer: Arc::new(Mutex::new(Some(Box::new(writer)
as Box<dyn Sink<Message, Error = tungstenite::Error> + Send + Unpin>))),
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)),
});
let boxed_reader: Box<
dyn Stream<Item = Result<Message, tungstenite::Error>> + Send + Unpin,
> = Box::new(reader);
connection.spawn_listener(boxed_reader).await;
connection
}
pub async fn is_connected(&self) -> bool {
*self.is_connected.lock().await
}
/// Connect loop with retry
pub async fn connect(&self) {
pub async fn connect(self: &Arc<Self>) {
loop {
match connect_async("wss://app.tensamin.net/ws/iota/").await {
Ok((ws_stream, _)) => {
let (write_half, read_half) = ws_stream.split();
*self.writer.lock().await = Some(write_half);
self.spawn_listener(read_half).await;
*self.writer.lock().await = Some(Box::new(write_half));
let boxed_reader: Box<
dyn Stream<Item = Result<Message, tungstenite::Error>> + Send + Unpin,
> = Box::new(read_half);
self.clone().spawn_listener(boxed_reader).await;
let cloned_self = self.clone();
let handle = tokio::spawn(async move {
loop {
@ -85,23 +119,32 @@ impl OmikronConnection {
Self::send_message_static(&self.writer, msg).await
}
pub async fn set_variant(self: &Arc<Self>, variant: ConnectionVariant) {
*self.variant.write().await = variant;
}
pub async fn set_user_id(self: &Arc<Self>, user_id: Uuid) {
*self.user_id.write().await = Some(user_id);
}
/// Listener for all incoming messages
async fn spawn_listener(
&self,
mut read_half: futures_util::stream::SplitStream<
WebSocketStream<MaybeTlsStream<TcpStream>>,
>,
self: &Arc<Self>,
mut read_half: Box<dyn Stream<Item = Result<Message, tungstenite::Error>> + Send + Unpin>,
) {
let waiting_out = self.waiting.clone();
let writer_out = self.writer.clone();
let is_connected_out = self.is_connected.clone();
let sel_out = self.clone();
let variant = self.variant.clone();
let sel_arc_out = self.clone();
tokio::spawn(async move {
while let Some(msg) = read_half.next().await {
let waiting = waiting_out.clone();
let writer = writer_out.clone();
let is_connected = is_connected_out.clone();
let sel = sel_out.clone();
let variant = variant.clone();
let sel_arc = sel_arc_out.clone();
tokio::spawn(async move {
match msg {
Ok(Message::Close(Some(frame))) => {
@ -115,6 +158,96 @@ impl OmikronConnection {
sel.handle_pong(&cv, true).await;
return;
}
let com = variant.read().await.clone();
if com == ConnectionVariant::ClientUnauthenticated {
if cv.is_type(CommunicationType::identification) {
// Extract user ID
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(_) => {
sel_arc.send_message(
CommunicationValue::new(CommunicationType::error_invalid_user_id)
.with_id(cv.get_id())
.to_json()
.to_string()
)
.await;
return;
}
}
}
None => {
sel_arc
.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_user_id,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
)
.await;
return;
}
};
// Validate private key
if let Some(private_key_hash) =
cv.get_data(DataTypes::private_key_hash)
{
log_message(format!(
"private_key_hash: {}",
private_key_hash
));
let is_valid = local_auth::is_private_key_valid(
&user_id,
&private_key_hash.to_string(),
);
if !is_valid {
log_message("Invalid private key");
sel_arc.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
)
.await;
return;
}
} else {
log_message("Missing private key");
sel_arc
.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
)
.await;
return;
}
// Set identification data
sel_arc.set_user_id(user_id).await;
sel_arc
.set_variant(ConnectionVariant::ClientAuthenticated)
.await;
let response = CommunicationValue::new(
CommunicationType::identification_response,
)
.with_id(cv.get_id());
sel_arc.send_message(response.to_json().to_string()).await;
}
}
// ************************************************ //
// Direct messages //
// ************************************************ //
@ -326,6 +459,79 @@ impl OmikronConnection {
.await;
return;
}
if cv.is_type(CommunicationType::settings_save) {
let my_id = cv.get_sender().unwrap();
let settings_name =
cv.get_data(DataTypes::settings_name).unwrap().to_string();
let settings_value =
cv.get_data(DataTypes::payload).unwrap().to_string();
save_file(
&format!("users/{}/settings/", my_id),
&format!("{}.settings", settings_name),
&settings_value,
);
let response =
CommunicationValue::new(CommunicationType::settings_save)
.with_receiver(my_id)
.with_id(cv.get_id());
Self::send_message_static(
&writer.clone(),
response.to_json().to_string(),
)
.await;
return;
}
if cv.is_type(CommunicationType::settings_load) {
let my_id = cv.get_sender().unwrap();
let settings_name =
cv.get_data(DataTypes::settings_name).unwrap().to_string();
let settings_value_str = load_file(
&format!("users/{}/settings/", my_id),
&format!("{}.settings", settings_name),
);
let settings_value_json = JsonValue::from(settings_value_str);
let response =
CommunicationValue::new(CommunicationType::settings_load)
.with_id(cv.get_id())
.with_receiver(my_id)
.add_data(DataTypes::payload, settings_value_json)
.add_data_str(DataTypes::settings_name, settings_name);
Self::send_message_static(
&writer.clone(),
response.to_json().to_string(),
)
.await;
return;
}
if cv.is_type(CommunicationType::settings_list) {
let my_id = cv.get_sender().unwrap();
let settings = get_children(&format!("users/{}/settings/", my_id));
let mut settings_json = JsonValue::new_array();
for s in settings {
let s = s.replace(".settings", "");
if s.is_empty() {
continue;
}
let _ = settings_json.push(JsonValue::String(s));
}
let response =
CommunicationValue::new(CommunicationType::settings_list)
.with_id(cv.get_id())
.with_receiver(my_id)
.add_data(DataTypes::settings, settings_json);
Self::send_message_static(
&writer.clone(),
response.to_json().to_string(),
)
.await;
return;
}
}
Err(e) => {
log_message(format!("[Omikron] Error: {}", e));
@ -340,14 +546,7 @@ impl OmikronConnection {
}
pub async fn send_message_static(
writer: &Arc<
Mutex<
Option<
futures_util::stream::SplitSink<
WebSocketStream<MaybeTlsStream<TcpStream>>,
Message,
>,
>,
>,
Mutex<Option<Box<dyn Sink<Message, Error = tungstenite::Error> + Send + Unpin>>>,
>,
msg: String,
) {

View file

@ -9,16 +9,18 @@ use hyper::{
};
use hyper_util::rt::tokio::TokioIo;
use hyper_util::service::TowerToHyperService;
// FIX: Add necessary rustls imports for builder in minimal-feature environment
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use sha1::{Digest, Sha1};
use std::error::Error;
use std::io::ErrorKind;
use std::io::{self, BufReader};
use std::net::{IpAddr, SocketAddr};
use std::result::Result::Ok;
use std::sync::Arc;
use std::{future::Future, pin::Pin, time::Duration};
use tokio::net::TcpListener;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::WebSocketStream;
use tower::Service;
@ -26,8 +28,11 @@ use crate::gui::log_panel::log_message;
use crate::server::socket::handle;
use crate::util::file_util::load_file_buf;
use tokio_rustls::TlsAcceptor;
#[derive(Clone)]
struct HttpService;
struct HttpService {
peer_addr: SocketAddr,
}
impl Service<HttpRequest<Incoming>> for HttpService {
type Response = HttpResponse<Full<Bytes>>;
@ -46,6 +51,9 @@ impl Service<HttpRequest<Incoming>> for HttpService {
let headers = req.headers().clone();
let upgrades = upgrade::on(req);
let peer_ip = self.peer_addr.ip();
let is_local = is_local_network(peer_ip);
let fut = async move {
if path.starts_with("/ws")
&& headers
@ -104,17 +112,14 @@ impl Service<HttpRequest<Incoming>> for HttpService {
let (status, body_text) = match path.as_str() {
"/" => (
StatusCode::OK,
"Server: Try connecting to WebSocket at ws://<host>:<port>/ws or check /status.",
"Server: Try connecting to WebSocket at ws[s]://<host>:<port>/ws or check /status.",
),
"/status" => (StatusCode::OK, "HTTP Server Status: Online"),
"/status" => (StatusCode::OK, "Server Status: Online"),
"/index" => (StatusCode::OK, include_str!("../../static/web/index.html")),
_ => (StatusCode::NOT_FOUND, "404 Not Found"),
};
let body = Full::new(Bytes::from(body_text.to_string()));
let response = HttpResponse::builder()
.status(status)
.header(hyper::header::CONTENT_TYPE, "text/plain")
.body(body)
.unwrap();
let response = HttpResponse::builder().status(status).body(body).unwrap();
Ok(response)
}
};
@ -128,15 +133,84 @@ impl Service<HttpRequest<Incoming>> for HttpService {
}
}
pub async fn start(port: u16) -> bool {
let tls_config = match load_tls_config() {
Ok(config) => config,
Err(e) => {
log_message(format!("Failed to load TLS configuration: {}", e));
log_message("Server stopped. Ensure 'certs/cert.pem' and 'certs/key.pem' exist.");
return false;
fn is_local_network(addr: IpAddr) -> bool {
// 1. Check for standard private ranges (RFC 1918) and loopback
if addr.is_loopback() {
return true;
}
// 2. Check for Link-Local Addresses (169.254.x.x)
if let IpAddr::V4(ipv4) = addr {
if ipv4.octets()[0] == 169 && ipv4.octets()[1] == 254 {
return true;
}
};
}
// 3. Check for IPv6 Unique Local Addresses (fc00::/7)
if let IpAddr::V6(ipv6) = addr {
if (ipv6.segments()[0] & 0xfe00) == 0xfc00 {
return true;
}
}
false
}
/// Runs the standard, unencrypted HTTP/WS server loop.
async fn run_http_server(port: u16) -> bool {
// Bind to the port
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
if let Err(e) = listener {
log_message(format!("Failed to bind to port {}: {:?}", port, e));
return false;
}
let listener = listener.unwrap();
log_message(format!(
"Standard Server listening for HTTP and WS on 0.0.0.0:{}",
port
));
tokio::spawn(async move {
loop {
match listener.accept().await {
std::result::Result::Ok((stream, addr)) => {
let service = HttpService { peer_addr: addr };
let io = TokioIo::new(stream);
tokio::spawn(async move {
if let Err(err) = http1::Builder::new()
.preserve_header_case(true)
.title_case_headers(true)
.serve_connection(io, TowerToHyperService::new(service))
.with_upgrades()
.await
{
if let Some(io_err) =
err.source().and_then(|e| e.downcast_ref::<io::Error>())
{
if io_err.kind() != io::ErrorKind::ConnectionReset
&& io_err.kind() != io::ErrorKind::BrokenPipe
{
log_message(format!("Error serving connection: {:?}", err));
}
} else {
log_message(format!("Error serving connection: {:?}", err));
}
}
});
}
Err(e) => {
log_message(format!("Error accepting connection: {:?}", e));
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
}
});
true
}
/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config.
async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
let acceptor = TlsAcceptor::from(tls_config);
// Bind to the port
@ -147,18 +221,19 @@ pub async fn start(port: u16) -> bool {
}
let listener = listener.unwrap();
log_message(format!(
"Server listening for HTTP and WS on 0.0.0.0:{}",
"Encrypted Server listening for HTTPS and WSS on 0.0.0.0:{}",
port
));
tokio::spawn(async move {
loop {
match listener.accept().await {
std::result::Result::Ok((stream, _addr)) => {
let service = HttpService;
std::result::Result::Ok((stream, addr)) => {
let service = HttpService { peer_addr: addr };
let acceptor = acceptor.clone();
tokio::spawn(async move {
// Perform TLS handshake
let tls_stream = match acceptor.accept(stream).await {
Ok(s) => s,
Err(e) => {
@ -201,6 +276,26 @@ pub async fn start(port: u16) -> bool {
});
true
}
pub async fn start(port: u16) -> bool {
let tls_result = load_tls_config();
match tls_result {
Ok(Some(tls_config)) => {
// Certificates found and config loaded successfully, run the TLS server
run_tls_server(port, tls_config).await
}
Ok(None) => {
// Certificates not found, run the standard HTTP server
run_http_server(port).await
}
Err(e) => {
log_message(format!("Fatal error during TLS config load: {}", e));
// Error, server cannot start
false
}
}
}
fn calculate_accept_key(key: &str) -> String {
let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let mut sha1 = Sha1::new();
@ -209,30 +304,54 @@ fn calculate_accept_key(key: &str) -> String {
let result = sha1.finalize();
STANDARD.encode(result) // Base64 encode the result
}
fn load_tls_config() -> Result<Arc<ServerConfig>, Box<dyn Error>> {
// Load certificate file
let mut cert_file = BufReader::new(load_file_buf("certs", "cert.pem")?);
let cert_ders = rustls_pemfile::certs(&mut cert_file)
/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails.
fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
let cert_file_res = load_file_buf("certs", "cert.pem");
let key_file_res = load_file_buf("certs", "cert.key");
// Check if certificate files are present. If not, return None.
let cert_file_buf = match cert_file_res {
Ok(b) => b,
Err(e) if e.kind() == ErrorKind::NotFound => {
log_message("TLS certificate 'certs/cert.pem' not found.");
return Ok(None);
}
Err(e) => return Err(e.into()), // Other IO error
};
let key_file_buf = match key_file_res {
Ok(b) => b,
Err(e) if e.kind() == ErrorKind::NotFound => {
log_message("TLS key 'certs/cert.key' not found.");
return Ok(None);
}
Err(e) => return Err(e.into()), // Other IO error
};
// Continue with configuration if both files were found
let mut cert_reader = BufReader::new(cert_file_buf);
let cert_ders = rustls_pemfile::certs(&mut cert_reader)
.collect::<Result<Vec<CertificateDer>, io::Error>>()?;
// PKCS8
let mut key_file = BufReader::new(load_file_buf("certs", "cert.key")?);
let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_file)
let mut key_reader = BufReader::new(key_file_buf);
let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader)
.map(|r| r.map(Into::into)) // Explicit conversion
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
if key_ders.is_empty() {
// RSA
key_file = BufReader::new(load_file_buf("certs", "cert.key")?);
key_ders = rustls_pemfile::rsa_private_keys(&mut key_file)
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file
key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader)
.map(|r| r.map(Into::into))
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
}
if key_ders.is_empty() {
// EC
key_file = BufReader::new(load_file_buf("certs", "cert.key")?);
key_ders = rustls_pemfile::ec_private_keys(&mut key_file)
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file
key_ders = rustls_pemfile::ec_private_keys(&mut key_reader)
.map(|r| r.map(Into::into))
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
}
@ -246,5 +365,5 @@ fn load_tls_config() -> Result<Arc<ServerConfig>, Box<dyn Error>> {
.with_single_cert(cert_ders, key_ders.remove(0))
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
Ok(Arc::new(config))
Ok(Some(Arc::new(config)))
}

View file

@ -1,5 +1,6 @@
use crate::communities::{community_connection::CommunityConnection, community_manager};
use crate::gui::log_panel::log_message;
use crate::omikron::omikron_connection::OmikronConnection;
use futures::StreamExt;
use futures::stream::SplitSink;
@ -15,7 +16,9 @@ pub fn handle(
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
) {
tokio::spawn(async move {
if path.starts_with("/ws/community/") {
if path.starts_with("/ws/users/") {
OmikronConnection::client(writer, reader).await;
} else if path.starts_with("/ws/community/") {
let community_id = path.split("/").nth(3).unwrap();
log_message(format!("Community: {}", community_id));
if let Some(community) = community_manager::get_community(community_id).await {

View file

@ -49,7 +49,6 @@ pub fn load_file_buf(path: &str, name: &str) -> io::Result<BufReader<File>> {
// Ensure the directory exists, create if necessary
if !dir.exists() {
if let Err(e) = fs::create_dir_all(&dir) {
println!("[IMPORTANT] Couldn't create directories: {}", e);
return Err(io::Error::new(
io::ErrorKind::NotFound,
"Directory creation failed",
@ -59,13 +58,10 @@ pub fn load_file_buf(path: &str, name: &str) -> io::Result<BufReader<File>> {
// Create the file if it doesn't exist
if !file_path.exists() {
if let Err(e) = File::create(&file_path) {
println!("[IMPORTANT] Couldn't create file: {}", e);
return Err(io::Error::new(
io::ErrorKind::NotFound,
"File creation failed",
));
}
return Err(io::Error::new(
io::ErrorKind::NotFound,
"File creation failed",
));
}
// Open the file and return a BufReader for efficient reading

10
static/web/index.html Normal file
View file

@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<title>Iota</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>Hallo</h1>
</body>
</html>