Local Auth (start of dezentralization)
Prep for WebUI Upgrade to SSL optional
This commit is contained in:
parent
258404b7fa
commit
5f40158b00
11 changed files with 456 additions and 73 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
19
src/auth/local_auth.rs
Normal 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
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod auth_connector;
|
||||
pub mod crypto_helper;
|
||||
pub mod local_auth;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,14 +58,11 @@ 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",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Open the file and return a BufReader for efficient reading
|
||||
let file = File::open(&file_path)?;
|
||||
|
|
|
|||
10
static/web/index.html
Normal file
10
static/web/index.html
Normal 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>
|
||||
Loading…
Reference in a new issue