From 8160f8d0cbfc149a6f5b0a8b710bfa1f5ccafcf6 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:22:59 +0200 Subject: [PATCH] [Fix] Stability --- Cargo.lock | 737 ++------------------ client/Cargo.toml | 47 -- client/src/client_connection.rs | 8 +- communities/Cargo.toml | 44 -- communities/src/community.rs | 42 +- communities/src/community_connection.rs | 23 +- communities/src/interactables/category.rs | 7 +- iota-auth/Cargo.toml | 50 -- iota-cli/Cargo.toml | 52 -- iota-cli/src/screens/terms_checker.rs | 12 +- iota-cli/src/screens/terms_updater.rs | 24 +- iota-connection/src/relay.rs | 10 +- iota-core/Cargo.toml | 11 - iota-core/src/consent_state.rs | 31 +- iota-daemon-lib/Cargo.toml | 3 +- iota-daemon/Cargo.toml | 2 - iota-daemon/src/main.rs | 17 +- iota-installer/src/lib.rs | 10 + iota-ipc/src/text_commands.rs | 10 + iota-logger/src/lib.rs | 29 +- iota-paths/src/lib.rs | 89 ++- iota-state/Cargo.toml | 4 +- iota-storage/Cargo.toml | 16 - iota-storage/src/util/config_util.rs | 4 +- iota-storage/src/util/db.rs | 48 +- iota-terms/Cargo.toml | 3 +- iota-terms/src/consent.rs | 7 +- iota-terms/src/doc.rs | 63 +- iota-terms/src/lib.rs | 2 +- iota-terms/src/terms_getter.rs | 115 ++- iota-updater/Cargo.toml | 18 - iota-util/Cargo.toml | 5 +- iota-util/src/atomic_file.rs | 158 +++++ iota-util/src/crypto_helper.rs | 10 +- iota-util/src/lib.rs | 1 + iota/Cargo.toml | 1 - iota/src/cli_args.rs | 42 ++ iota/src/main.rs | 62 +- omikron-connector/Cargo.toml | 4 - omikron-connector/src/omikron_connection.rs | 141 +++- other-iota/Cargo.toml | 54 -- web-server/Cargo.toml | 1 - web-ui/Cargo.toml | 44 -- web-ui/src/api.rs | 31 +- 44 files changed, 796 insertions(+), 1296 deletions(-) create mode 100644 iota-util/src/atomic_file.rs diff --git a/Cargo.lock b/Cargo.lock index 7405bb8..75af5ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,29 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "actix" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" -dependencies = [ - "actix-rt", - "bitflags 2.13.1", - "bytes", - "crossbeam-channel", - "futures-core", - "futures-sink", - "futures-task", - "futures-util", - "log", - "once_cell", - "parking_lot", - "pin-project-lite", - "smallvec", - "tokio", - "tokio-util", -] - [[package]] name = "actix-codec" version = "0.5.2" @@ -109,9 +86,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +checksum = "c25da0441692de4ad67950cb7ed6c9ce4b669a6609525e547566c2e1ab4d695c" dependencies = [ "futures-core", "tokio", @@ -119,9 +96,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716aae056e2f869b7b5cfd8a08fcf98890f8455bec61d69c7dff5d8576f9d2b" +checksum = "cc8dcb6fa613d47c3b764a7dc672b4b31f25751dcbcf542f7b16f3e1f700044c" dependencies = [ "actix-rt", "actix-service", @@ -175,9 +152,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.14.1" +version = "4.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58356675d8c86d2e720480645a0316808471a62d0073f6a3b98810a5e0ca0e73" +checksum = "bbacab3593b6b4f7be815076fc52d60a83c873426824675417e2abdd229e2e36" dependencies = [ "actix-codec", "actix-http", @@ -217,24 +194,6 @@ dependencies = [ "url", ] -[[package]] -name = "actix-web-actors" -version = "4.3.1+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" -dependencies = [ - "actix", - "actix-codec", - "actix-http", - "actix-web", - "bytes", - "bytestring", - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "actix-web-codegen" version = "4.3.0" @@ -274,20 +233,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[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 = "aho-corasick" version = "1.1.5" @@ -678,9 +623,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -803,52 +748,14 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" name = "client" version = "0.1.0" dependencies = [ - "actix-web", - "actix-web-actors", - "aes-gcm", - "async-trait", - "base64 0.22.1", - "chrono", - "crossterm", "dashmap", - "futures", - "futures-util", - "hex", - "hkdf 0.12.4", - "hyper", - "hyper-util", - "iota-auth", "iota-connection", "iota-logger", - "iota-state", "iota-storage", "iota-util", - "json", - "lazy_static", "mtp", - "once_cell", - "open", - "pnet", - "rand 0.8.7", - "rand_core 0.6.4", - "ratatui", - "reqwest", - "rusqlite", - "rustls", - "rustls-pemfile", - "serde_json", - "sha2 0.11.0", - "strum", - "strum_macros", - "sysinfo", "tokio", - "tokio-tungstenite", - "tungstenite", "uuid", - "walkdir", - "warp", - "x448", - "zip", ] [[package]] @@ -1008,15 +915,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -1082,15 +980,6 @@ dependencies = [ "phf", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -1145,9 +1034,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -1155,9 +1044,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", @@ -1168,9 +1057,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", @@ -1307,16 +1196,6 @@ dependencies = [ "ctutils", ] -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.1", - "objc2", -] - [[package]] name = "displaydoc" version = "0.2.7" @@ -1392,22 +1271,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encoding_rs" @@ -1483,12 +1351,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1553,21 +1415,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1720,16 +1567,6 @@ dependencies = [ "wasm-bindgen", ] -[[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 = "glob" version = "0.3.4" @@ -1757,9 +1594,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" +checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228" dependencies = [ "atomic-waker", "bytes", @@ -1867,30 +1704,6 @@ dependencies = [ "hashbrown 0.17.1", ] -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64 0.22.1", - "bytes", - "headers-core", - "http 1.5.0", - "httpdate", - "mime", - "sha1 0.10.7", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.5.0", -] - [[package]] name = "heck" version = "0.5.0" @@ -1903,15 +1716,6 @@ 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 0.12.1", -] - [[package]] name = "hkdf" version = "0.13.0" @@ -2021,7 +1825,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.16", + "h2 0.4.18", "http 1.5.0", "http-body", "httparse", @@ -2167,9 +1971,9 @@ checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -2276,114 +2080,34 @@ dependencies = [ "serde_json", "serde_yaml", "tokio", - "tokio-util", ] [[package]] name = "iota-auth" version = "0.1.0" dependencies = [ - "aes-gcm", - "async-trait", - "base64 0.22.1", - "chrono", - "crossterm", - "dashmap", - "futures", - "futures-util", - "hex", - "hkdf 0.12.4", - "hyper", - "hyper-util", - "iota-logger", - "iota-state", - "iota-storage", - "iota-util", "json", - "lazy_static", - "mtp", - "once_cell", - "open", - "pnet", - "rand 0.8.7", - "rand_core 0.6.4", - "ratatui", - "reqwest", - "rusqlite", - "rustls", - "rustls-pemfile", - "serde_json", - "sha2 0.11.0", - "strum", - "strum_macros", - "sysinfo", - "tokio", - "tokio-tungstenite", - "tungstenite", - "uuid", - "walkdir", - "warp", - "x448", - "zip", ] [[package]] name = "iota-cli" version = "0.1.0" dependencies = [ - "actix-web", - "actix-web-actors", - "aes-gcm", - "async-trait", - "base64 0.22.1", "chrono", "crossterm", - "dashmap", - "futures", - "futures-util", - "hex", - "hkdf 0.12.4", - "hyper", - "hyper-util", "iota-ipc", - "iota-logger", "iota-paths", - "iota-process-manager", "iota-state", - "iota-storage", "iota-terms", - "iota-util", - "json", - "lazy_static", - "mtp", - "omikron-connector", "once_cell", "open", - "pnet", - "rand 0.8.7", - "rand_core 0.6.4", "ratatui", - "reqwest", - "rusqlite", - "rustls", - "rustls-pemfile", "serde", - "serde_json", "serde_yaml", - "sha2 0.11.0", - "strum", - "strum_macros", - "sysinfo", "tempfile", "tokio", - "tokio-tungstenite", "tokio-util", - "tungstenite", "unicode-width", - "walkdir", - "warp", - "x448", - "zip", ] [[package]] @@ -2400,7 +2124,6 @@ dependencies = [ name = "iota-core" version = "0.1.0" dependencies = [ - "dashmap", "iota-cli", "iota-logger", "iota-paths", @@ -2409,17 +2132,9 @@ dependencies = [ "iota-terms", "iota-updater", "iota-util", - "json", - "mtp", - "omikron-connector", - "once_cell", "pnet", - "ratatui", - "reqwest", "tokio", - "tokio-util", "web-server", - "web-ui", ] [[package]] @@ -2430,13 +2145,11 @@ dependencies = [ "iota-ipc", "iota-logger", "iota-paths", - "iota-state", "iota-storage", "iota-terms", "iota-util", "omikron-connector", "tokio", - "tokio-util", "web-server", ] @@ -2445,7 +2158,6 @@ name = "iota-daemon-lib" version = "0.1.0" dependencies = [ "async-trait", - "dashmap", "iota-ipc", "iota-logger", "iota-state", @@ -2515,9 +2227,7 @@ version = "0.1.0" dependencies = [ "dashmap", "json", - "mtp", "once_cell", - "serde", "sysinfo", "tokio", ] @@ -2526,77 +2236,44 @@ dependencies = [ name = "iota-storage" version = "0.1.0" dependencies = [ - "aes-gcm", "arc-swap", "base64 0.22.1", - "hex", - "hkdf 0.12.4", "iota-logger", "iota-paths", - "iota-state", "iota-util", "json", - "mtp", "once_cell", "r2d2", "rand 0.8.7", - "rand_core 0.6.4", - "ratatui", - "reqwest", "rusqlite", "serde", - "serde_json", "serde_yaml", - "sha2 0.11.0", - "sysinfo", "thiserror 2.0.20", "tokio", - "uuid", - "walkdir", - "x448", - "zip", ] [[package]] name = "iota-terms" version = "0.1.0" dependencies = [ - "iota-state", "iota-util", - "json", "reqwest", + "tokio", ] [[package]] name = "iota-updater" version = "0.1.0" dependencies = [ - "aes-gcm", "anyhow", - "base64 0.22.1", "ed25519-dalek 2.2.0", "hex", - "hkdf 0.12.4", - "iota-logger", "iota-paths", - "json", - "mtp", - "once_cell", - "pnet", - "rand_core 0.6.4", - "ratatui", - "reqwest", - "semver", "serde", "serde_json", "sha2 0.11.0", - "sysinfo", "tempfile", "tokio", - "uuid", - "walkdir", - "x448", - "zip", ] [[package]] @@ -2609,6 +2286,7 @@ dependencies = [ "mtp", "reqwest", "sysinfo", + "tempfile", "tokio", "uuid", "walkdir", @@ -2768,9 +2446,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -2943,16 +2621,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3037,7 +2705,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-client", "mtp-codec", @@ -3053,7 +2721,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-codec", "mtp-common", @@ -3066,7 +2734,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "base64 0.23.1", "byteorder", @@ -3080,10 +2748,9 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "quinn", - "rustls", "thiserror 2.0.20", "wtransport", ] @@ -3091,14 +2758,14 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "argon2", "base64 0.22.1", "chacha20poly1305", "ed25519-dalek 3.0.0", "getrandom 0.4.3", - "hkdf 0.13.0", + "hkdf", "ml-dsa", "mlkem-tls", "rand 0.10.2", @@ -3114,7 +2781,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -3125,7 +2792,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "mtp-codec", "mtp-common", @@ -3141,7 +2808,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "async-trait", "mtp-codec", @@ -3161,7 +2828,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "serde", "serde_yaml", @@ -3170,7 +2837,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#c30315af944bda05ecdb9c4e9cc350cb99cf7b2e" dependencies = [ "async-trait", "bytes", @@ -3195,23 +2862,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.29.0" @@ -3304,15 +2954,6 @@ dependencies = [ "libc", ] -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -3320,24 +2961,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.1", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.1", - "objc2", ] [[package]] @@ -3350,17 +2973,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-open-directory" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" -dependencies = [ - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - [[package]] name = "octets" version = "0.3.6" @@ -3383,7 +2995,6 @@ dependencies = [ "async-trait", "base64 0.22.1", "dashmap", - "hex", "iota-connection", "iota-logger", "iota-state", @@ -3391,14 +3002,11 @@ dependencies = [ "iota-util", "json", "mtp", - "rand 0.8.7", "rand_core 0.6.4", "reqwest", - "sha2 0.11.0", "tokio", "tokio-util", "uuid", - "x448", ] [[package]] @@ -3429,49 +3037,12 @@ dependencies = [ "libc", ] -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-float" version = "4.6.0" @@ -3484,53 +3055,6 @@ dependencies = [ [[package]] name = "other-iota" version = "0.1.0" -dependencies = [ - "actix-web", - "actix-web-actors", - "aes-gcm", - "async-trait", - "base64 0.22.1", - "chrono", - "crossterm", - "dashmap", - "futures", - "futures-util", - "hex", - "hkdf 0.12.4", - "hyper", - "hyper-util", - "iota-auth", - "iota-logger", - "iota-state", - "iota-storage", - "iota-util", - "json", - "lazy_static", - "mtp", - "once_cell", - "open", - "pnet", - "rand 0.8.7", - "rand_core 0.6.4", - "ratatui", - "reqwest", - "rusqlite", - "rustls", - "rustls-pemfile", - "serde_json", - "sha2 0.11.0", - "strum", - "strum_macros", - "sysinfo", - "tokio", - "tokio-tungstenite", - "tungstenite", - "uuid", - "walkdir", - "warp", - "x448", - "zip", -] [[package]] name = "palette" @@ -3728,26 +3252,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -3882,18 +3386,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - [[package]] name = "portable-atomic" version = "1.15.0" @@ -4063,12 +3555,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -4263,7 +3749,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.16", + "h2 0.4.18", "http 1.5.0", "http-body", "http-body-util", @@ -4443,9 +3929,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.14" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -4492,12 +3978,6 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -4662,7 +4142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" dependencies = [ "digest 0.11.3", - "keccak 0.2.1", + "keccak 0.2.2", "sponge-cursor", ] @@ -4910,16 +4390,15 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.39.6" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "objc2-open-directory", "windows", ] @@ -4951,7 +4430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -5158,16 +4637,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -5189,20 +4658,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.7.19" @@ -5301,23 +4756,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" -dependencies = [ - "bytes", - "data-encoding", - "http 1.5.0", - "httparse", - "log", - "native-tls", - "rand 0.10.2", - "sha1 0.11.0", - "thiserror 2.0.20", -] - [[package]] name = "typenum" version = "1.20.1" @@ -5330,12 +4768,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -5475,33 +4907,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "warp" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a808122a8a77eecdabaefd88ddb1913c4be5ea1465399f63ba64c7aa705fea" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http 1.5.0", - "http-body", - "http-body-util", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -5579,7 +4984,6 @@ dependencies = [ "bytes", "http 1.5.0", "iota-logger", - "iota-util", "mtp", "tokio", "tokio-util", @@ -5610,50 +5014,14 @@ name = "web-ui" version = "0.1.0" dependencies = [ "actix-web", - "actix-web-actors", - "aes-gcm", - "async-trait", - "base64 0.22.1", - "chrono", - "crossterm", - "dashmap", - "futures", - "futures-util", - "hex", - "hkdf 0.12.4", - "hyper", - "hyper-util", "iota-logger", "iota-state", "iota-storage", "iota-util", - "json", - "lazy_static", - "mtp", - "omikron-connector", - "once_cell", - "open", - "pnet", - "rand 0.8.7", - "rand_core 0.6.4", - "ratatui", - "reqwest", - "rusqlite", "rustls", "rustls-pemfile", "serde_json", - "sha2 0.11.0", - "strum", - "strum_macros", - "sysinfo", "tokio", - "tokio-tungstenite", - "tungstenite", - "uuid", - "walkdir", - "warp", - "x448", - "zip", ] [[package]] @@ -6031,17 +5399,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - [[package]] name = "x509-parser" version = "0.18.1" @@ -6168,9 +5525,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -6179,9 +5536,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", diff --git a/client/Cargo.toml b/client/Cargo.toml index d63431f..8759d48 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -9,53 +9,6 @@ iota-connection = { path = "../iota-connection" } iota-logger = { path = "../iota-logger" } iota-util = { path = "../iota-util" } iota-storage = { path = "../iota-storage" } -iota-state = { path = "../iota-state" } -iota-auth = { path = "../iota-auth" } - -actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" -chrono = "0.4.43" -crossterm = "*" dashmap = "6.1.0" -futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } -json = "*" -lazy_static = "1.5.0" -once_cell = "1.21.3" -open = "5.3.3" -pnet = "0.35.0" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" -rustls = { version = "0.23.37", features = ["aws-lc-rs"] } -rustls-pemfile = "2.2.0" -serde_json = "1.0.149" -sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" tokio = { version = "1.50.0", features = ["full"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -warp = "*" -x448 = { version = "*" } -zip = "6.0.0" diff --git a/client/src/client_connection.rs b/client/src/client_connection.rs index a90df86..a207471 100644 --- a/client/src/client_connection.rs +++ b/client/src/client_connection.rs @@ -288,8 +288,8 @@ impl ClientConnection { if cv.is_type(CommunicationType::SettingsSave) { let my_id = cv.get_sender(); - let settings_name = cv.get_data(DataType::SettingsName).as_str().unwrap(); - let settings_value = cv.get_data(DataType::Payload).as_str().unwrap(); + let Some(settings_name) = cv.get_data(DataType::SettingsName).as_str() else { return }; + let Some(settings_value) = cv.get_data(DataType::Payload).as_str() else { return }; let _ = iota_storage::util::settings::save( my_id as i64, @@ -308,7 +308,7 @@ impl ClientConnection { if cv.is_type(CommunicationType::SettingsLoad) { let my_id = cv.get_sender(); - let settings_name = cv.get_data(DataType::SettingsName).as_string().unwrap(); + let Some(settings_name) = cv.get_data(DataType::SettingsName).as_string() else { return }; let settings_value_str = iota_storage::util::settings::load( my_id as i64, iota_storage::util::settings::GLOBAL_SESSION_ID, @@ -350,7 +350,7 @@ impl ClientConnection { return; }; - let encrypted_challenge = cv.get_data(DataType::Challenge).as_str().unwrap(); + let Some(encrypted_challenge) = cv.get_data(DataType::Challenge).as_str() else { return }; let solved = crypto_util::decrypt_challenge(encrypted_challenge, &keyring).ok(); diff --git a/communities/Cargo.toml b/communities/Cargo.toml index 89c4f57..36ef42e 100644 --- a/communities/Cargo.toml +++ b/communities/Cargo.toml @@ -5,56 +5,12 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } -iota-logger = { path = "../iota-logger" } iota-util = { path = "../iota-util" } -iota-storage = { path = "../iota-storage" } -iota-state = { path = "../iota-state" } -iota-auth = { path = "../iota-auth" } -actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" -chrono = "0.4.43" -crossterm = "*" -dashmap = "6.1.0" futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } -json = "*" -lazy_static = "1.5.0" -once_cell = "1.21.3" -open = "5.3.3" -pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" -rustls = { version = "0.23.37", features = ["aws-lc-rs"] } -rustls-pemfile = "2.2.0" -serde_json = "1.0.149" sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" tokio = { version = "1.50.0", features = ["full"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -warp = "*" x448 = { version = "*" } -zip = "6.0.0" diff --git a/communities/src/community.rs b/communities/src/community.rs index 69ba273..7b23247 100644 --- a/communities/src/community.rs +++ b/communities/src/community.rs @@ -40,7 +40,7 @@ impl Community { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); - let private_key = Secret::from_bytes(&buf).unwrap(); + let private_key = Secret::from(buf); let public_key = PublicKey::from(&private_key); Community { name: String::new(), @@ -58,7 +58,7 @@ impl Community { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); - let private_key = Secret::from_bytes(&buf).unwrap(); + let private_key = Secret::from(buf); let public_key = PublicKey::from(&private_key); let c = Community { name, @@ -125,7 +125,7 @@ impl Community { self.members.clone() } pub fn get_private_key(&self) -> Secret { - Secret::from_bytes(self.private_key.as_bytes()).unwrap() + Secret::from(*self.private_key.as_bytes()) } pub fn get_public_key(&self) -> &PublicKey { &self.public_key @@ -222,12 +222,14 @@ impl Community { for interactable in target_interactables.iter() { if interactable.get_name() == name { if interactable.get_codec() == "category" { - let category: &Category = - interactable.as_any().downcast_ref::().unwrap(); + let Some(category) = interactable.as_any().downcast_ref::() else { + return CommunicationValue::new(CommunicationType::ErrorInternal); + }; // cannot move a value of type dyn Interactable the size of dyn Interactable cannot be statically determined (rustc E0161) return category .get_child(path.to_string(), name.to_string()) - .unwrap() + .ok_or(CommunicationValue::new(CommunicationType::ErrorInternal)) + .unwrap_or_else(|error| return error) .run_function(cv.clone()) .await; } else { @@ -267,7 +269,7 @@ impl Community { let mut data = JsonValue::new_object(); let mut permissions = JsonValue::new_array(); - for perm in self.permissions.get(user).unwrap() { + for perm in self.permissions.get(user).into_iter().flatten() { if let Ok(_) = permissions.push(perm.to_string()) {} } @@ -287,10 +289,10 @@ impl Community { } pub async fn load(name: &String) -> Option> { let file_contents = file_util::load_file(&format!("communities/{}/", name), "config.json"); - let json_content = json::parse(&file_contents).unwrap(); + let json_content = json::parse(&file_contents).ok()?; let user_data = file_util::load_file(&format!("communities/{}/", name), "users.json"); - let user_json: JsonValue = json::parse(&user_data).unwrap(); + let user_json: JsonValue = json::parse(&user_data).ok()?; let mut users = Vec::new(); let mut permissions: HashMap> = HashMap::new(); @@ -318,25 +320,17 @@ pub async fn load(name: &String) -> Option> { }; let community = Community { - name: json_content["name"].as_str().unwrap().to_string(), + name: json_content["name"].as_str()?.to_string(), owner_id: Arc::new(RwLock::new(json_content["owner_id"].as_i64().unwrap_or(0))), 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(), - ), + &STANDARD.decode(json_content["private_key"].as_str()?).ok()?, + )?, + public_key: PublicKey::from(&Secret::from_bytes( + &STANDARD.decode(json_content["private_key"].as_str()?).ok()?, + )?), interactables: Arc::new(RwLock::new(Vec::new())), connections: Arc::new(RwLock::new(HashMap::new())), }; @@ -346,7 +340,7 @@ pub async fn load(name: &String) -> Option> { 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 Some(name) = file.split('.').next().map(str::to_string) else { continue }; let interactable: Box = registry::load(comarc.clone(), String::new(), name).await; comarc.add_interactable(Arc::new(interactable)).await; diff --git a/communities/src/community_connection.rs b/communities/src/community_connection.rs index 685d9d8..b607a28 100644 --- a/communities/src/community_connection.rs +++ b/communities/src/community_connection.rs @@ -52,7 +52,9 @@ impl CommunityConnection { pub async fn send_message(&self, message: &CommunicationValue) { let mut sender = self.sender.write().await; // Access the SplitSink let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string())); - sender.send(message_text).await.unwrap(); // Send the message via the SplitSink + if let Err(error) = sender.send(message_text).await { + log::error!("failed to send community message: {error}"); + } } pub async fn get_community(&self) -> Option> { self.community.read().await.clone() @@ -94,14 +96,15 @@ impl CommunityConnection { } } async fn handle_function(&self, cv: CommunicationValue) { - let name = cv.get_data(DataType::Name).unwrap().as_str().unwrap(); - let path = cv.get_data(DataType::Path).unwrap().as_str().unwrap(); - let function = cv.get_data(DataType::Function).unwrap().as_str().unwrap(); + let Some(name) = cv.get_data(DataType::Name).as_str() else { return }; + let Some(path) = cv.get_data(DataType::Path).as_str() else { return }; + let Some(function) = cv.get_data(DataType::Function).as_str() else { return }; let result = self .get_community() .await - .unwrap() + .ok_or(()) + .unwrap_or_else(|_| return) .run_function(self.get_user_id().await, name, path, function, &cv) .await; @@ -364,13 +367,9 @@ impl CommunityConnection { pub async fn handle_close(self: Arc) { if self.is_identified().await { if self.get_user_id().await != 0 { - self.community - .read() - .await - .as_ref() - .unwrap() - .remove_connection(self.clone()) - .await; + if let Some(community) = self.community.read().await.as_ref() { + community.remove_connection(self.clone()).await; + } } } } diff --git a/communities/src/interactables/category.rs b/communities/src/interactables/category.rs index f2836ed..2896e8c 100644 --- a/communities/src/interactables/category.rs +++ b/communities/src/interactables/category.rs @@ -30,14 +30,13 @@ impl Category { .find(|child| child.get_name() == &name) .cloned() } else { - let sub_module = path.split("/").next().unwrap(); + let sub_module = path.split('/').next()?; let next = self .children .iter() - .find(|child| child.get_name() == sub_module) - .unwrap(); + .find(|child| child.get_name() == sub_module)?; if next.get_codec() == "category" { - let next_cat = next.as_any().downcast_ref::().unwrap(); + let next_cat = next.as_any().downcast_ref::()?; next_cat.get_child(path, name) } else { Some(next.clone()) diff --git a/iota-auth/Cargo.toml b/iota-auth/Cargo.toml index dc43347..f35935a 100644 --- a/iota-auth/Cargo.toml +++ b/iota-auth/Cargo.toml @@ -4,54 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } -iota-logger = { path = "../iota-logger" } -iota-util = { path = "../iota-util" } -iota-storage = { path = "../iota-storage" } -iota-state = { path = "../iota-state" } - -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" -chrono = "0.4.43" -crossterm = "*" -dashmap = "6.1.0" -futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } json = "*" -lazy_static = "1.5.0" -once_cell = "1.21.3" -open = "5.3.3" -pnet = "0.35.0" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" -rustls = { version = "0.23.37", features = ["aws-lc-rs"] } -rustls-pemfile = "2.2.0" -serde_json = "1.0.149" -sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" -tokio = { version = "1.50.0", features = ["full"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" -uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -warp = "*" -x448 = { version = "*" } -zip = "6.0.0" diff --git a/iota-cli/Cargo.toml b/iota-cli/Cargo.toml index 4489cdf..87b3954 100644 --- a/iota-cli/Cargo.toml +++ b/iota-cli/Cargo.toml @@ -5,76 +5,24 @@ edition = "2024" [features] legacy-commands = [ - "dep:iota-logger", - "dep:iota-storage", - "dep:iota-util", - "dep:mtp", - "dep:omikron-connector", ] [dependencies] -iota-logger = { path = "../iota-logger", optional = true } iota-state = { path = "../iota-state" } -iota-storage = { path = "../iota-storage", optional = true } iota-terms = { path = "../iota-terms" } -iota-util = { path = "../iota-util", optional = true } iota-ipc = { path = "../iota-ipc" } -iota-process-manager = { path = "../iota-process-manager" } iota-paths = { path = "../iota-paths" } -omikron-connector = { path = "../omikron-connector", optional = true } - -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", optional = true } -actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" chrono = "0.4.43" crossterm = "*" -dashmap = "6.1.0" -futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } -json = "*" -lazy_static = "1.5.0" once_cell = "1.21.3" open = "5.3.3" -pnet = "0.35.0" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" -rustls = { version = "0.23.37", features = ["aws-lc-rs"] } -rustls-pemfile = "2.2.0" -serde_json = "1.0.149" serde = { version = "1", features = ["derive"] } serde_yaml = "0.9" -sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" tokio = { version = "1.50.0", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" -walkdir = "2.5.0" -warp = "*" -x448 = { version = "*" } -zip = "6.0.0" unicode-width = "0.2" [dev-dependencies] diff --git a/iota-cli/src/screens/terms_checker.rs b/iota-cli/src/screens/terms_checker.rs index 628134f..885bd65 100644 --- a/iota-cli/src/screens/terms_checker.rs +++ b/iota-cli/src/screens/terms_checker.rs @@ -94,19 +94,19 @@ impl Screen for TermsCheckerScreen { height: content_height, }); let eula_text = if size.width < 70 { - "EULA ¹ (https://legal.tensamin.net/eula/)" + "EULA ¹ (https://legal.methanium.net/tensamin/eula)" } else { - "End User Licence Agreement ¹ (https://legal.tensamin.net/eula/)" + "End User Licence Agreement ¹ (https://legal.methanium.net/tensamin/eula)" }; let tos_text = if size.width < 72 { - "ToS ² (https://legal.tensamin.net/terms-of-service/)" + "ToS ² (https://legal.methanium.net/tensamin/terms-of-service)" } else { - "Terms of Service ² (https://legal.tensamin.net/terms-of-service/)" + "Terms of Service ² (https://legal.methanium.net/tensamin/terms-of-service)" }; let pp_text = if size.width < 68 { - "PP ² (https://legal.tensamin.net/privacy-policy/)" + "PP ² (https://legal.methanium.net/tensamin/privacy-policy)" } else { - "Privacy Policy ² (https://legal.tensamin.net/privacy-policy/)" + "Privacy Policy ² (https://legal.methanium.net/tensamin/privacy-policy)" }; let (mut optional_lines, agree_lines): (Vec, Vec<&str>) = if size.width > 143 { diff --git a/iota-cli/src/screens/terms_updater.rs b/iota-cli/src/screens/terms_updater.rs index 2dc502e..21e3247 100644 --- a/iota-cli/src/screens/terms_updater.rs +++ b/iota-cli/src/screens/terms_updater.rs @@ -201,7 +201,7 @@ impl Screen for TermsUpdaterScreen { if self.eula_future { if size.width < 80 { text_lines.push(checkbox( - "EULA ¹³ (https://legal.tensamin.net/eula/newest/)", + "EULA ¹³ (https://legal.methanium.net/tensamin/eula)", self.eula, self.focus == Focus::Eula, true, @@ -214,7 +214,7 @@ impl Screen for TermsUpdaterScreen { text_lines.push(Line::from(format!(" Goes into effect on {}", date))); } else { text_lines.push(checkbox( - "End User Licence Agreement ¹³ (https://legal.tensamin.net/eula/newest/)", + "End User Licence Agreement ¹³ (https://legal.methanium.net/tensamin/eula)", self.eula, self.focus == Focus::Eula, true, @@ -229,14 +229,14 @@ impl Screen for TermsUpdaterScreen { } else { if size.width < 80 { text_lines.push(checkbox( - "EULA ¹ (https://legal.tensamin.net/eula/newest/)", + "EULA ¹ (https://legal.methanium.net/tensamin/eula)", self.eula, self.focus == Focus::Eula, true, )); } else { text_lines.push(checkbox( - "End User Licence Agreement ¹ (https://legal.tensamin.net/eula/newest/)", + "End User Licence Agreement ¹ (https://legal.methanium.net/tensamin/eula)", self.eula, self.focus == Focus::Eula, true, @@ -250,7 +250,7 @@ impl Screen for TermsUpdaterScreen { if self.tos_future { if size.width < 80 { text_lines.push(checkbox( - "ToS ²³ (https://legal.tensamin.net/tos/newest/)", + "ToS ²³ (https://legal.methanium.net/tensamin/terms-of-service)", self.tos, self.focus == Focus::Tos, self.eula, @@ -263,7 +263,7 @@ impl Screen for TermsUpdaterScreen { text_lines.push(Line::from(format!(" Goes into effect on {}", date))); } else { text_lines.push(checkbox( - "Terms of Service ²³ (https://legal.tensamin.net/terms-of-service/newest/)", + "Terms of Service ²³ (https://legal.methanium.net/tensamin/terms-of-service)", self.tos, self.focus == Focus::Tos, self.eula, @@ -278,14 +278,14 @@ impl Screen for TermsUpdaterScreen { } else { if size.width < 80 { text_lines.push(checkbox( - "ToS ² (https://legal.tensamin.net/tos/newest/)", + "ToS ² (https://legal.methanium.net/tensamin/terms-of-service)", self.tos, self.focus == Focus::Tos, self.eula, )); } else { text_lines.push(checkbox( - "Terms of Service ² (https://legal.tensamin.net/terms-of-service/newest/)", + "Terms of Service ² (https://legal.methanium.net/tensamin/terms-of-service)", self.tos, self.focus == Focus::Tos, self.eula, @@ -299,7 +299,7 @@ impl Screen for TermsUpdaterScreen { if self.pp_future { if size.width < 80 { text_lines.push(checkbox( - "PP ²³ (https://legal.tensamin.net/privacy-policy/newest/)", + "PP ²³ (https://legal.methanium.net/tensamin/privacy-policy)", self.pp, self.focus == Focus::Pp, self.eula, @@ -312,7 +312,7 @@ impl Screen for TermsUpdaterScreen { text_lines.push(Line::from(format!(" Goes into effect on {}", date))); } else { text_lines.push(checkbox( - "Privacy Policy ²³ (https://legal.tensamin.net/privacy-policy/newest/)", + "Privacy Policy ²³ (https://legal.methanium.net/tensamin/privacy-policy)", self.pp, self.focus == Focus::Pp, self.eula, @@ -327,14 +327,14 @@ impl Screen for TermsUpdaterScreen { } else { if size.width < 80 { text_lines.push(checkbox( - "PP ² (https://legal.tensamin.net/privacy-policy/newest/)", + "PP ² (https://legal.methanium.net/tensamin/privacy-policy)", self.pp, self.focus == Focus::Pp, self.eula, )); } else { text_lines.push(checkbox( - "Privacy Policy ² (https://legal.tensamin.net/privacy-policy/newest/)", + "Privacy Policy ² (https://legal.methanium.net/tensamin/privacy-policy)", self.pp, self.focus == Focus::Pp, self.eula, diff --git a/iota-connection/src/relay.rs b/iota-connection/src/relay.rs index 298a0df..a75c5a5 100644 --- a/iota-connection/src/relay.rs +++ b/iota-connection/src/relay.rs @@ -1,8 +1,9 @@ use iota_util::route_target::RouteTarget; use mtp::codec::{ - CommunicationValue, ProtectionPolicy, RelayError, SignaturePolicy, TypeMap, + CommunicationValue, ProtectionPolicy, RelayError, RelayOpenOptions, SignaturePolicy, TypeMap, VerifiedRelayContent, VerifiedRelayMetadata, forward_relay_frame, - open_relay_content_with_keyrings, open_relay_metadata_with, relay_metadata_claimed_signer_id, + open_relay_content_with_keyrings, open_relay_metadata_with_without_replay, + relay_metadata_claimed_signer_id, }; use mtp::crypto::{Keyring, PublicKeyBundle}; use std::fmt; @@ -157,13 +158,12 @@ where .type_map() .cloned() .ok_or(RelayValidationError::MissingTypeMap)?; - let metadata = open_relay_metadata_with( + let metadata = open_relay_metadata_with_without_replay( frame, &[keyring], Some(claimed_signer), move |signer_id| (signer_id == claimed_signer).then(|| resolver_keys.clone()), - RELAY_PROTECTION_POLICY, - None, + RelayOpenOptions::new(RELAY_PROTECTION_POLICY), )?; let context = VerifiedRelayContext { diff --git a/iota-core/Cargo.toml b/iota-core/Cargo.toml index 03250d9..f28cc45 100644 --- a/iota-core/Cargo.toml +++ b/iota-core/Cargo.toml @@ -13,17 +13,6 @@ iota-terms = { path = "../iota-terms" } iota-updater = { path = "../iota-updater" } iota-util = { path = "../iota-util" } iota-paths = { path = "../iota-paths" } -omikron-connector = { path = "../omikron-connector" } web-server = { path = "../web-server" } -web-ui = { path = "../web-ui" } - -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } - -dashmap = "6.1.0" -json = "*" -once_cell = "1.21.3" pnet = "0.35.0" -ratatui = "0.30.0" -reqwest = "0.13.2" tokio = { version = "1.50.0", features = ["full"] } -tokio-util = { version = "0.7", features = ["rt"] } diff --git a/iota-core/src/consent_state.rs b/iota-core/src/consent_state.rs index de88a04..556ebe5 100644 --- a/iota-core/src/consent_state.rs +++ b/iota-core/src/consent_state.rs @@ -2,9 +2,8 @@ use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use iota_cli::screens::terms_checker::{TermsCheckerScreen, UserChoice}; -use iota_cli::screens::terms_updater::{TermsUpdaterScreen, UpdateDecision}; use iota_cli::ui::UI; -use iota_terms::{Doc, TermsType as Type, get_current_docs, get_newest_docs}; +use iota_terms::{Doc, TermsType as Type, get_current_docs}; use iota_util::file_util::{load_file, save_file}; use tokio::sync::oneshot; @@ -12,9 +11,11 @@ pub async fn check(ui: Arc) -> Result<(bool, bool), String> { let mut state = ConsentState::load_state(); ensure_initial_consent(ui.clone(), &mut state).await?; - // A mandatory document update is a hard bootstrap gate. In particular, - // refusing it must not allow service setup or daemon access to continue. - ensure_updates(ui, &mut state).await?; + /* + * The raw legal endpoint exposes only the current document. Restore this + * flow when it provides future versions that users can accept early. + */ + // ensure_updates(ui, &mut state).await?; state = state.sanitize(); state.save_state(); @@ -42,11 +43,9 @@ async fn ensure_initial_consent(ui: Arc, state: &mut ConsentState) -> Result return Ok(()); } - let docs = get_current_docs().await; - if docs.is_none() { - return Err("Could not connect to the legal endpoint to fetch the current agreements. Please check your internet connection.".to_string()); - } - let (current_eula, current_tos, current_privacy) = docs.unwrap(); + let (current_eula, current_tos, current_privacy) = get_current_docs().await.ok_or_else(|| { + "Could not connect to the legal endpoint to fetch the current agreements. Please check your internet connection.".to_string() + })?; let (tx, rx) = oneshot::channel(); @@ -73,6 +72,7 @@ async fn ensure_initial_consent(ui: Arc, state: &mut ConsentState) -> Result UserChoice::Deny => Ok(()), } } +/* async fn ensure_updates(ui: Arc, state: &mut ConsentState) -> Result<(), String> { let Some((eula_update, tos_update, privacy_update)) = get_updates().await else { return Ok(()); @@ -115,6 +115,8 @@ async fn ensure_updates(ui: Arc, state: &mut ConsentState) -> Result<(), Str state.save_state(); Ok(()) } +*/ +/* fn apply_future_updates( state: &mut ConsentState, result: UserChoice, @@ -225,6 +227,7 @@ async fn get_updates() -> Option<( None } } +*/ #[derive(Debug, Clone)] pub struct ConsentState { @@ -299,7 +302,7 @@ impl ConsentState { if let Some(eula) = &self.eula { file_out.push_str(&format!("\ - \n\"EULA=true\" indicates that you read, understood and accepted Tensamin's End User Licence agreement. You can find our EULA at https://legal.tensamin.net/eula/\ + \n\"EULA=true\" indicates that you read, understood and accepted Tensamin's End User Licence agreement. You can find our EULA at https://legal.methanium.net/tensamin/eula\ \nEULA={}\ \nEULA-VERSION={}\ \nEULA-HASH={}\ @@ -309,7 +312,7 @@ impl ConsentState { && let Some(tos) = &self.tos { file_out.push_str(&format!("\ - \n\"Terms-of-Service=true\" indicates that you read, understood and accepted Tensamin's Terms of Service. You can find our Terms of Serivce at https://legal.tensamin.net/tos/\ + \n\"Terms-of-Service=true\" indicates that you read, understood and accepted Tensamin's Terms of Service. You can find our Terms of Serivce at https://legal.methanium.net/tensamin/terms-of-service\ \nTerms-of-Service={}\ \nTerms-of-Service-VERSION={}\ \nTerms-of-Service-HASH={}\ @@ -319,7 +322,7 @@ impl ConsentState { && let Some(pp) = &self.privacy { file_out.push_str(&format!("\ - \n\"Privacy-Policy=true\" indicates that you read, understood and accepted Tensamin's Privacy Policy. You can find our Privacy Policy at https://legal.tensamin.net/privacy/\ + \n\"Privacy-Policy=true\" indicates that you read, understood and accepted Tensamin's Privacy Policy. You can find our Privacy Policy at https://legal.methanium.net/tensamin/privacy-policy\ \nPrivacy-Policy={}\ \nPrivacy-Policy-VERSION={}\ \nPrivacy-Policy-HASH={}\ @@ -327,7 +330,7 @@ impl ConsentState { } } else { file_out.push_str("\ - \n\"EULA=true\" indicates that you read, understood and accepted Tensamin's End User Licence Agreement. You can find Tensamin's EULA at https://legal.tensamin.net/eula/\ + \n\"EULA=true\" indicates that you read, understood and accepted Tensamin's End User Licence Agreement. You can find Tensamin's EULA at https://legal.methanium.net/tensamin/eula\ \nEULA=false\ "); } diff --git a/iota-daemon-lib/Cargo.toml b/iota-daemon-lib/Cargo.toml index 4eb0b7c..1aec94c 100644 --- a/iota-daemon-lib/Cargo.toml +++ b/iota-daemon-lib/Cargo.toml @@ -13,9 +13,8 @@ iota-updater = { path = "../iota-updater" } iota-util = { path = "../iota-util" } omikron-connector = { path = "../omikron-connector" } mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } -dashmap = "6.1.0" libc = "0.2" -sysinfo = "0.39.0" +sysinfo = "0.38.0" serde_yaml = "0.9" tokio = { version = "1.50.0", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } diff --git a/iota-daemon/Cargo.toml b/iota-daemon/Cargo.toml index 29de9e1..87a5636 100644 --- a/iota-daemon/Cargo.toml +++ b/iota-daemon/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" iota-daemon-lib = { path = "../iota-daemon-lib" } iota-ipc = { path = "../iota-ipc" } iota-logger = { path = "../iota-logger" } -iota-state = { path = "../iota-state" } iota-paths = { path = "../iota-paths" } iota-storage = { path = "../iota-storage" } iota-util = { path = "../iota-util" } @@ -15,4 +14,3 @@ iota-terms = { path = "../iota-terms" } omikron-connector = { path = "../omikron-connector" } web-server = { path = "../web-server" } tokio = { version = "1.50.0", features = ["full"] } -tokio-util = { version = "0.7", features = ["rt"] } diff --git a/iota-daemon/src/main.rs b/iota-daemon/src/main.rs index cfe0337..d2ff2c6 100644 --- a/iota-daemon/src/main.rs +++ b/iota-daemon/src/main.rs @@ -102,15 +102,18 @@ async fn main() -> ExitCode { let (state_tx, state_rx) = watch::channel(runtime.snapshot()); runtime.set_startup_phase(StartupPhase::LoadingUsers); - if tokio::task::spawn_blocking(user_manager::load_users_sync) - .await - .ok() - .and_then(Result::ok) - .is_none() - { + let storage_error = match iota_storage::util::db::verify_and_backup_database() { + Ok(()) => tokio::task::spawn_blocking(user_manager::load_users_sync) + .await + .map_err(|error| format!("user storage task failed: {error}")) + .and_then(|result| result.map_err(|error| error.to_string())) + .err(), + Err(error) => Some(error.to_string()), + }; + if let Some(error) = storage_error { runtime.set_component_failed( iota_ipc::ComponentId::Storage, - "user storage failed to load".into(), + format!("user storage failed to load: {error}"), ); } else { runtime.set_component_healthy(iota_ipc::ComponentId::Storage, None); diff --git a/iota-installer/src/lib.rs b/iota-installer/src/lib.rs index 29724d6..0de2518 100644 --- a/iota-installer/src/lib.rs +++ b/iota-installer/src/lib.rs @@ -19,6 +19,10 @@ pub fn install_linux_bundle(bundle: &Path) -> Result<()> { install_linux_bundle_with_operator(bundle, None) } +pub fn bootstrap_linux_bundle(bundle: &Path, operator: Option<&str>) -> Result<()> { + install_linux_bundle_with_operator(bundle, operator) +} + pub fn install_linux_bundle_with_operator(bundle: &Path, operator: Option<&str>) -> Result<()> { if std::env::consts::OS != "linux" { bail!("Linux systemd bundles are not supported on this platform"); @@ -118,6 +122,12 @@ pub fn install_linux_bundle_with_operator(bundle: &Path, operator: Option<&str>) ], )?; run("systemd-sysusers", &[])?; + for directory in ["/var/lib/iota", "/var/cache/iota", "/var/log/iota"] { + run( + "install", + &["-d", "-m", "0750", "-o", "iota", "-g", "iota", directory], + )?; + } if let Some(operator) = operator { run("usermod", &["-aG", "iota-operators", operator])?; } else { diff --git a/iota-ipc/src/text_commands.rs b/iota-ipc/src/text_commands.rs index f8ee611..c6daa2b 100644 --- a/iota-ipc/src/text_commands.rs +++ b/iota-ipc/src/text_commands.rs @@ -15,6 +15,7 @@ pub const COMMANDS: &[&str] = &[ "config get", "config set ", "config reload", + "health", "components", "logs", "update check", @@ -84,6 +85,7 @@ pub fn parse(line: &str) -> Option { }), ["config", "reload"] => Some(LocalRequest::ReloadConfig), ["omikron", "status"] => Some(LocalRequest::GetOmikronStatus), + ["health"] => Some(LocalRequest::ListComponents), ["components"] => Some(LocalRequest::ListComponents), ["logs"] => Some(LocalRequest::GetLogs { limit: 100 }), ["update", "check"] => Some(LocalRequest::CheckUpdate), @@ -229,6 +231,14 @@ mod tests { )); } + #[test] + fn parses_health() { + assert!(matches!( + parse("health"), + Some(LocalRequest::ListComponents) + )); + } + #[test] fn parses_users_show() { let req = parse("users show 42").unwrap(); diff --git a/iota-logger/src/lib.rs b/iota-logger/src/lib.rs index d7d336a..b704910 100644 --- a/iota-logger/src/lib.rs +++ b/iota-logger/src/lib.rs @@ -355,7 +355,7 @@ fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) - let key_str = key.to_string(); match value { - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + DataValue::Str(s) => format!("{}=\"{}\"", key_str, abbreviate_string(&s)), DataValue::Container(inner) => { let inner_formatted = format_data_container(inner, version.clone()); @@ -386,7 +386,7 @@ fn format_array(arr: Vec, version: Version) -> String { let parts: Vec = arr .into_iter() .map(|value| match value { - DataValue::Str(s) => format!("\"{}\"", s), + DataValue::Str(s) => format!("\"{}\"", abbreviate_string(&s)), DataValue::Container(inner) => { let inner_formatted = format_data_container(inner, version.clone()); @@ -412,6 +412,31 @@ fn format_array(arr: Vec, version: Version) -> String { parts.join(", ") } +fn abbreviate_string(value: &str) -> String { + const EDGE_LENGTH: usize = 4; + + let chars: Vec = value.chars().collect(); + if chars.len() <= EDGE_LENGTH * 2 { + return value.to_string(); + } + + let prefix: String = chars.iter().take(EDGE_LENGTH).collect(); + let suffix: String = chars.iter().rev().take(EDGE_LENGTH).rev().collect(); + format!("{prefix}...{suffix}") +} + +#[cfg(test)] +mod tests { + use super::abbreviate_string; + + #[test] + fn abbreviates_only_strings_longer_than_eight_characters() { + assert_eq!(abbreviate_string("12345678"), "12345678"); + assert_eq!(abbreviate_string("123456789"), "1234...6789"); + assert_eq!(abbreviate_string("YWJjZGVmZ2hpag=="), "YWJj...ag=="); + } +} + #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { diff --git a/iota-paths/src/lib.rs b/iota-paths/src/lib.rs index 9143ea1..0083012 100644 --- a/iota-paths/src/lib.rs +++ b/iota-paths/src/lib.rs @@ -70,20 +70,37 @@ pub struct IotaPaths { impl IotaPaths { pub fn resolve(scope: Scope) -> Result { let defaults = Defaults::for_scope(scope)?; - let config_dir = override_first(&["IOTA_CONFIG_DIR"])?.unwrap_or(defaults.config_dir); + let data_root = if scope == Scope::User { + absolute_env("IOTA_DATA_ROOT")? + } else { + None + }; + let config_dir = override_first(&["IOTA_CONFIG_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("config"))) + .unwrap_or(defaults.config_dir); // IOTA_DATA_DIR is intentionally only a compatibility alias. Parse it // exactly like every other override; do not hide an invalid value. - let state_dir = - override_first(&["IOTA_STATE_DIR", "IOTA_DATA_DIR"])?.unwrap_or(defaults.state_dir); - let cache_dir = override_first(&["IOTA_CACHE_DIR"])?.unwrap_or(defaults.cache_dir); - let runtime_dir = override_first(&["IOTA_RUNTIME_DIR"])?.or(defaults.runtime_dir); - let log_dir = override_first(&["IOTA_LOG_DIR"])?.unwrap_or(defaults.log_dir); + let state_dir = override_first(&["IOTA_STATE_DIR", "IOTA_DATA_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("state"))) + .unwrap_or(defaults.state_dir); + let cache_dir = override_first(&["IOTA_CACHE_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("cache"))) + .unwrap_or(defaults.cache_dir); + let runtime_dir = override_first(&["IOTA_RUNTIME_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("runtime"))) + .or(defaults.runtime_dir); + let log_dir = override_first(&["IOTA_LOG_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("logs"))) + .unwrap_or(defaults.log_dir); let asset_dir = override_first(&["IOTA_ASSET_DIR", "IOTA_WEB_ASSET_DIR"])? + .or_else(|| data_root.as_ref().map(|root| root.join("web"))) .unwrap_or(defaults.asset_dir); - let install_root = override_first(&["IOTA_INSTALL_ROOT"])?.unwrap_or(defaults.install_root); + let install_root = override_first(&["IOTA_INSTALL_ROOT"])? + .or_else(|| data_root.as_ref().map(|root| root.join("bin"))) + .unwrap_or(defaults.install_root); let config_file = override_first(&["IOTA_CONFIG_FILE"])? .unwrap_or_else(|| config_dir.join("config.yaml")); - let ipc_endpoint = resolve_ipc(scope, defaults.ipc_endpoint)?; + let ipc_endpoint = resolve_ipc(scope, defaults.ipc_endpoint, data_root.as_deref())?; let runtime_dir = runtime_dir.or_else(|| match &ipc_endpoint { IpcEndpoint::UnixSocket(path) => path.parent().map(Path::to_path_buf), IpcEndpoint::WindowsPipe(_) => None, @@ -136,10 +153,20 @@ impl IotaPaths { &self.cache_dir, &self.log_dir, ] { - create_directory(directory, self.scope == Scope::User)?; + create_directory(directory, self.scope == Scope::User).map_err(|error| { + std::io::Error::new( + error.kind(), + format!("cannot prepare {}: {error}", directory.display()), + ) + })?; } if let Some(runtime) = &self.runtime_dir { - create_directory(runtime, self.scope == Scope::User)?; + create_directory(runtime, self.scope == Scope::User).map_err(|error| { + std::io::Error::new( + error.kind(), + format!("cannot prepare {}: {error}", runtime.display()), + ) + })?; } Ok(()) } @@ -337,12 +364,19 @@ fn override_first(names: &[&'static str]) -> Result, PathError> } Ok(None) } -fn resolve_ipc(scope: Scope, default: Option) -> Result { +fn resolve_ipc( + scope: Scope, + default: Option, + data_root: Option<&Path>, +) -> Result { #[cfg(unix)] { if let Some(path) = absolute_env("IOTA_SOCKET")? { return Ok(IpcEndpoint::UnixSocket(path)); } + if let Some(root) = data_root { + return Ok(IpcEndpoint::UnixSocket(root.join("runtime/iota.sock"))); + } if scope == Scope::User { return Err(PathError::MissingRequiredOverride("IOTA_SOCKET")); } @@ -451,9 +485,13 @@ pub fn daemon_endpoints() -> Vec { #[cfg(test)] mod tests { use super::*; - use std::sync::atomic::{AtomicU64, Ordering}; + use std::sync::{ + Mutex, + atomic::{AtomicU64, Ordering}, + }; static TEST_ID: AtomicU64 = AtomicU64::new(0); + static ENVIRONMENT: Mutex<()> = Mutex::new(()); #[test] fn system_layout_is_fhs() { let p = IotaPaths::resolve(Scope::System).unwrap(); @@ -501,4 +539,31 @@ mod tests { assert!(state.join("path-layout-v2.json").is_file()); let _ = std::fs::remove_dir_all(root); } + + #[test] + fn data_root_keeps_unmanaged_user_paths_together() { + let _guard = ENVIRONMENT.lock().unwrap(); + let root = std::env::temp_dir().join(format!( + "iota-data-root-test-{}-{}", + std::process::id(), + TEST_ID.fetch_add(1, Ordering::Relaxed) + )); + unsafe { + std::env::set_var("IOTA_DATA_ROOT", &root); + } + let paths = IotaPaths::resolve(Scope::User).unwrap(); + unsafe { + std::env::remove_var("IOTA_DATA_ROOT"); + } + + assert_eq!(paths.config_file, root.join("config/config.yaml")); + assert_eq!(paths.state_dir, root.join("state")); + assert_eq!(paths.cache_dir, root.join("cache")); + assert_eq!(paths.log_dir, root.join("logs")); + assert_eq!(paths.runtime_dir, Some(root.join("runtime"))); + assert_eq!( + paths.ipc_endpoint, + IpcEndpoint::UnixSocket(root.join("runtime/iota.sock")) + ); + } } diff --git a/iota-state/Cargo.toml b/iota-state/Cargo.toml index 8c9ab8a..4db4406 100644 --- a/iota-state/Cargo.toml +++ b/iota-state/Cargo.toml @@ -12,6 +12,4 @@ dashmap = "6.1.0" once_cell = "1.21.3" tokio = { version = "1.50.0", features = ["full"] } json = "*" -sysinfo = "0.39.0" -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } -serde = { version = "1", features = ["derive"] } +sysinfo = "0.38.0" diff --git a/iota-storage/Cargo.toml b/iota-storage/Cargo.toml index caf6417..a579f7f 100644 --- a/iota-storage/Cargo.toml +++ b/iota-storage/Cargo.toml @@ -5,33 +5,17 @@ edition = "2024" [dependencies] iota-logger = { path = "../iota-logger" } -iota-state = { path = "../iota-state" } iota-util = { path = "../iota-util" } iota-paths = { path = "../iota-paths" } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } - -aes-gcm = "0.10.3" base64 = "0.22.1" -hex = "*" -hkdf = "0.12.4" json = "*" arc-swap = "1" once_cell = "1.21.3" r2d2 = "0.8" serde = { version = "1", features = ["derive"] } -serde_json = "1" serde_yaml = "0.9" thiserror = "2" rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" rusqlite = "0.40.0" -sha2 = "0.11.0" -sysinfo = "0.39.0" tokio = { version = "1.50.0", features = ["full"] } -uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -x448 = { version = "*" } -zip = "6.0.0" diff --git a/iota-storage/src/util/config_util.rs b/iota-storage/src/util/config_util.rs index 60b1cda..62b65e9 100644 --- a/iota-storage/src/util/config_util.rs +++ b/iota-storage/src/util/config_util.rs @@ -149,10 +149,8 @@ pub fn save_config_to(path: &Path) { return; } } - let temporary = path.with_extension("yaml.tmp"); - if let Err(error) = fs::write(&temporary, yaml).and_then(|_| fs::rename(&temporary, path)) { + if let Err(error) = iota_util::atomic_file::replace(path, yaml.as_bytes(), 3) { eprintln!("Cannot save {}: {error}", path.display()); - let _ = fs::remove_file(temporary); } } } diff --git a/iota-storage/src/util/db.rs b/iota-storage/src/util/db.rs index 01ae3a5..46363cf 100644 --- a/iota-storage/src/util/db.rs +++ b/iota-storage/src/util/db.rs @@ -19,7 +19,7 @@ impl ManageConnection for SqliteManager { fn connect(&self) -> Result { let path = db_file_path(DB_NAME); let conn = Connection::open(path)?; - conn.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?; + conn.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;")?; conn.busy_timeout(Duration::from_millis(250))?; Ok(conn) } @@ -55,6 +55,52 @@ where f(&conn) } +/* Verify the persistent database before the pool is initialized. A corrupt + * database is moved aside rather than opened again, preserving material for + * operator recovery while allowing the daemon to report the failed storage. */ +pub fn verify_and_backup_database() -> Result<(), StorageError> { + let storage_dir = iota_util::file_util::storage_directory(); + std::fs::create_dir_all(&storage_dir)?; + let path = storage_dir.join(format!("{DB_NAME}.sqlite3")); + if !path.exists() { + return Ok(()); + } + + let connection = Connection::open(&path)?; + connection.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;")?; + connection.execute_batch("PRAGMA wal_checkpoint(FULL);")?; + let integrity: String = connection.query_row("PRAGMA integrity_check", [], |row| row.get(0))?; + drop(connection); + if integrity != "ok" { + let recovery = storage_dir.join("recovery"); + std::fs::create_dir_all(&recovery)?; + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + for suffix in ["", "-wal", "-shm"] { + let source = PathBuf::from(format!("{}{}", path.display(), suffix)); + if source.exists() { + let destination = recovery.join(format!("{DB_NAME}.sqlite3.{timestamp}{suffix}")); + std::fs::rename(source, destination)?; + } + } + return Err(StorageError::Other(format!( + "database integrity check failed ({integrity}); moved database files to {}", + recovery.display() + ))); + } + + let backup_dir = storage_dir.join("backups"); + std::fs::create_dir_all(&backup_dir)?; + let backup = backup_dir.join(format!("{DB_NAME}.sqlite3")); + let temporary = backup_dir.join(format!(".{DB_NAME}.sqlite3.tmp")); + std::fs::copy(&path, &temporary)?; + std::fs::File::open(&temporary)?.sync_all()?; + std::fs::rename(temporary, backup)?; + Ok(()) +} + fn db_file_path(db_name: &str) -> PathBuf { let storage_dir = iota_util::file_util::storage_directory(); // Creating storage belongs to initialization/connection setup, never to a diff --git a/iota-terms/Cargo.toml b/iota-terms/Cargo.toml index 607dce1..c53c3cf 100644 --- a/iota-terms/Cargo.toml +++ b/iota-terms/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -iota-state = { path = "../iota-state" } iota-util = { path = "../iota-util" } -json = "*" reqwest = "0.13.2" +tokio = { version = "1.50.0", features = ["macros"] } diff --git a/iota-terms/src/consent.rs b/iota-terms/src/consent.rs index ef968f9..601ab1f 100644 --- a/iota-terms/src/consent.rs +++ b/iota-terms/src/consent.rs @@ -40,7 +40,7 @@ impl ConsentRecord { } fn matches_doc(value: &Option<(String, String)>, doc: &Doc) -> bool { - matches!(value, Some((version, hash)) if version == &doc.get_version() && hash == &doc.get_hash()) + matches!(value, Some((_, hash)) if hash == &doc.get_hash()) } pub fn load(state_dir: &Path) -> ConsentRecord { @@ -82,8 +82,5 @@ pub fn save(state_dir: &Path, record: &ConsentRecord) -> io::Result<()> { text.push_str(&format!("{name}={version}:{hash}\n")); } } - let path = state_dir.join(FILE_NAME); - let temporary = state_dir.join(format!(".{FILE_NAME}.{}.tmp", std::process::id())); - fs::write(&temporary, text)?; - fs::rename(temporary, path) + iota_util::atomic_file::replace(&state_dir.join(FILE_NAME), text.as_bytes(), 3) } diff --git a/iota-terms/src/doc.rs b/iota-terms/src/doc.rs index 0c2e0d2..7510407 100644 --- a/iota-terms/src/doc.rs +++ b/iota-terms/src/doc.rs @@ -1,7 +1,5 @@ -use json::{JsonValue, object::Object}; - use crate::terms_getter::Type; -use iota_util::file_util::load_file; +use iota_util::crypto_helper::hex_hash; #[derive(Clone, Debug, PartialEq, Eq)] #[allow(unused)] @@ -23,15 +21,24 @@ impl Doc { } } + pub fn from_raw(doc_type: Type, content: String, timestamp: u64) -> Doc { + Doc::new( + timestamp.to_string(), + hex_hash(&content), + doc_type, + timestamp, + ) + } + pub fn equals_some(&self, other: &Option) -> bool { if let Some(other) = other { - self.get_version() == other.get_version() && self.get_hash() == other.get_hash() + self.equals(other) } else { false } } pub fn equals(&self, other: &Self) -> bool { - self.get_version() == other.get_version() && self.get_hash() == other.get_hash() + self.get_hash() == other.get_hash() } pub fn get_version(&self) -> String { @@ -41,34 +48,36 @@ impl Doc { self.hash.clone() } pub fn get_time(&self) -> u64 { - self.timestamp.clone() + self.timestamp } - pub fn get_content(&self) -> String { - load_file( - format!("docs/{}/", self.doc_type.to_str()).as_str(), - format!("{}.md", self.version).as_str(), - ) + #[cfg(test)] + fn timestamp(&self) -> u64 { + self.timestamp } +} - pub fn to_json(&self) -> JsonValue { - let mut json = JsonValue::new_object(); +#[cfg(test)] +mod tests { + use super::Doc; + use crate::terms_getter::Type; + use iota_util::crypto_helper::hex_hash; - let _ = json.insert("version", self.version.clone()); - let _ = json.insert("hash", self.hash.clone()); - let _ = json.insert("unix", self.timestamp.clone()); + #[test] + fn raw_documents_use_a_local_timestamp_and_content_hash() { + let content = "# EULA\n".to_owned(); + let document = Doc::from_raw(Type::EULA, content.clone(), 123); - json + assert_eq!(document.doc_type, Type::EULA); + assert_eq!(document.get_version(), "123"); + assert_eq!(document.timestamp(), 123); + assert_eq!(document.get_hash(), hex_hash(&content)); } - pub fn from_json(doc_type: Type, json: Object) -> Option { - let hash = json.get("hash")?.as_str()?.to_string(); - let version = json.get("version")?.as_str()?.to_string(); - let timestamp = json.get("unix")?.as_u64()?; - Some(Doc { - version, - hash, - doc_type, - timestamp, - }) + #[test] + fn matching_documents_ignore_the_fetch_timestamp() { + let earlier = Doc::from_raw(Type::EULA, "# EULA\n".to_owned(), 123); + let later = Doc::from_raw(Type::EULA, "# EULA\n".to_owned(), 456); + + assert!(earlier.equals(&later)); } } diff --git a/iota-terms/src/lib.rs b/iota-terms/src/lib.rs index e878f9b..58e6de0 100644 --- a/iota-terms/src/lib.rs +++ b/iota-terms/src/lib.rs @@ -4,7 +4,7 @@ pub mod terms_getter; pub use terms_getter::Type as TermsType; pub use terms_getter::get_current_docs; pub use terms_getter::get_link; -pub use terms_getter::get_newest_docs; +// pub use terms_getter::get_newest_docs; pub use terms_getter::get_newest_link; pub use terms_getter::get_terms; diff --git a/iota-terms/src/terms_getter.rs b/iota-terms/src/terms_getter.rs index 65de9b1..9ccfffa 100755 --- a/iota-terms/src/terms_getter.rs +++ b/iota-terms/src/terms_getter.rs @@ -1,6 +1,5 @@ -use json::JsonValue::Object; - use crate::doc::Doc; +use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Type { @@ -13,8 +12,8 @@ impl Type { pub fn to_str(&self) -> &str { match self { Self::EULA => "eula", - Self::TOS => "tos", - Self::PP => "privacy", + Self::TOS => "terms-of-service", + Self::PP => "privacy-policy", } } pub fn to_string(&self) -> String { @@ -27,79 +26,73 @@ impl Type { } pub fn get_link(terms_type: Type) -> String { - format!("https://legal.tensamin.net/{}/", terms_type.to_str()) + format!( + "https://legal.methanium.net/tensamin/{}", + terms_type.to_str() + ) } + +/* + * The legal service exposes only its latest raw documents. Keep this helper + * for the dormant pre-emptive-acceptance UI until it has a source of future + * document versions again. + */ pub fn get_newest_link(terms_type: Type) -> String { - format!("https://legal.tensamin.net/{}/newest/", terms_type.to_str()) + get_link(terms_type) } pub async fn get_current_docs() -> Option<(Doc, Doc, Doc)> { - let body = reqwest::get("https://legal.tensamin.net/api/current/") - .await - .ok()? - .text() - .await - .ok()?; + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs(); + let (eula, tos, privacy) = tokio::join!( + get_terms(Type::EULA), + get_terms(Type::TOS), + get_terms(Type::PP), + ); - let json = json::parse(&body).ok()?; - - if let Object(eula) = &json["eula"] { - if let Object(tos) = &json["tos"] { - if let Object(pp) = &json["pp"] { - Some(( - Doc::from_json(Type::EULA, eula.clone())?, - Doc::from_json(Type::TOS, tos.clone())?, - Doc::from_json(Type::PP, pp.clone())?, - )) - } else { - None - } - } else { - None - } - } else { - None - } + Some(( + Doc::from_raw(Type::EULA, eula?, timestamp), + Doc::from_raw(Type::TOS, tos?, timestamp), + Doc::from_raw(Type::PP, privacy?, timestamp), + )) } +/* + * Future documents are unavailable from the raw endpoint. Restore this API + * with the pre-emptive-acceptance flow when the service provides them again. + * pub async fn get_newest_docs() -> Option<(Doc, Doc, Doc)> { - let body = reqwest::get("https://legal.tensamin.net/api/newest/") - .await - .ok()? - .text() - .await - .ok()?; - - let json = json::parse(&body).ok()?; - - if let Object(eula) = &json["eula"] { - if let Object(tos) = &json["tos"] { - if let Object(pp) = &json["pp"] { - Some(( - Doc::from_json(Type::EULA, eula.clone())?, - Doc::from_json(Type::TOS, tos.clone())?, - Doc::from_json(Type::PP, pp.clone())?, - )) - } else { - None - } - } else { - None - } - } else { - None - } + None } +*/ + pub async fn get_terms(terms_type: Type) -> Option { - let body = reqwest::get(format!( - "https://legal.tensamin.net/api/text/{}/", + reqwest::get(format!( + "https://legal.methanium.net/tensamin/{}/raw", terms_type.to_str() )) .await .ok()? .text() .await - .ok()?; + .ok() +} - Some(body) +#[cfg(test)] +mod tests { + use super::{Type, get_link}; + + #[test] + fn maps_document_types_to_tensamin_raw_document_names() { + assert_eq!(Type::EULA.to_str(), "eula"); + assert_eq!(Type::TOS.to_str(), "terms-of-service"); + assert_eq!(Type::PP.to_str(), "privacy-policy"); + } + + #[test] + fn links_to_the_tensamin_document_page() { + assert_eq!( + get_link(Type::TOS), + "https://legal.methanium.net/tensamin/terms-of-service" + ); + } } diff --git a/iota-updater/Cargo.toml b/iota-updater/Cargo.toml index 2f6bf04..8467afd 100644 --- a/iota-updater/Cargo.toml +++ b/iota-updater/Cargo.toml @@ -4,31 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] -iota-logger = { path = "../iota-logger" } iota-paths = { path = "../iota-paths" } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } - -json = "*" -pnet = "0.35.0" -ratatui = "0.30.0" -reqwest = "0.13.2" tokio = { version = "1.50.0", features = ["full"] } -sysinfo = "0.39.0" -uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -zip = "6.0.0" -aes-gcm = "0.10.3" -base64 = "0.22.1" -rand_core = { version = "0.6", features = ["getrandom", "std"] } sha2 = "0.11.0" -x448 = { version = "*" } -hkdf = "0.12.4" -once_cell = "1.21.3" hex = "*" serde = "1.0.228" tempfile = "3.27.0" anyhow = "1.0.102" -semver = "1.0.28" ed25519-dalek = "2.2.0" serde_json = "1.0" diff --git a/iota-util/Cargo.toml b/iota-util/Cargo.toml index 49bfe51..676d02f 100644 --- a/iota-util/Cargo.toml +++ b/iota-util/Cargo.toml @@ -11,9 +11,12 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ reqwest = "0.13.2" tokio = { version = "1.50.0", features = ["full"] } -sysinfo = "0.39.0" +sysinfo = "0.38.0" uuid = { version = "*", features = ["v4"] } walkdir = "2.5.0" zip = "6.0.0" base64 = "0.22.1" hex = "*" + +[dev-dependencies] +tempfile = "3" diff --git a/iota-util/src/atomic_file.rs b/iota-util/src/atomic_file.rs new file mode 100644 index 0000000..9800a91 --- /dev/null +++ b/iota-util/src/atomic_file.rs @@ -0,0 +1,158 @@ +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +static TEMPORARY_ID: AtomicU64 = AtomicU64::new(0); + +/* Persist small state files without exposing a partially written version after + * a crash. Backups give operators a local recovery point for keys and config. */ +pub fn replace(path: &Path, contents: &[u8], backup_limit: usize) -> io::Result<()> { + replace_with_mode(path, contents, backup_limit, false) +} + +pub fn replace_private(path: &Path, contents: &[u8], backup_limit: usize) -> io::Result<()> { + replace_with_mode(path, contents, backup_limit, true) +} + +fn replace_with_mode( + path: &Path, + contents: &[u8], + backup_limit: usize, + private: bool, +) -> io::Result<()> { + let parent = path.parent().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "persistent file has no parent directory", + ) + })?; + fs::create_dir_all(parent)?; + + if backup_limit > 0 && path.is_file() { + create_backup(path, backup_limit)?; + } + + let temporary = temporary_path(path)?; + let write_result = (|| { + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .open(&temporary)?; + set_private_permissions(&temporary, private)?; + file.write_all(contents)?; + file.sync_all()?; + fs::rename(&temporary, path)?; + sync_directory(parent) + })(); + if write_result.is_err() { + let _ = fs::remove_file(&temporary); + } + write_result +} + +#[cfg(unix)] +fn set_private_permissions(path: &Path, private: bool) -> io::Result<()> { + if private { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(path, fs::Permissions::from_mode(0o600))?; + } + Ok(()) +} + +#[cfg(not(unix))] +fn set_private_permissions(_path: &Path, _private: bool) -> io::Result<()> { + Ok(()) +} + +fn create_backup(path: &Path, backup_limit: usize) -> io::Result<()> { + let parent = path.parent().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "persistent file has no parent directory", + ) + })?; + let name = path.file_name().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "persistent file has no name") + })?; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); + let id = TEMPORARY_ID.fetch_add(1, Ordering::Relaxed); + let backup = parent.join(format!( + ".{}.backup-{timestamp}-{id}", + name.to_string_lossy() + )); + fs::copy(path, &backup)?; + File::open(&backup)?.sync_all()?; + sync_directory(parent)?; + + let prefix = format!(".{}.backup-", name.to_string_lossy()); + let mut backups = fs::read_dir(parent)? + .filter_map(Result::ok) + .filter(|entry| entry.file_name().to_string_lossy().starts_with(&prefix)) + .collect::>(); + backups.sort_by_key(|entry| entry.file_name()); + let obsolete = backups.len().saturating_sub(backup_limit); + for entry in backups.into_iter().take(obsolete) { + fs::remove_file(entry.path())?; + } + Ok(()) +} + +fn temporary_path(path: &Path) -> io::Result { + let parent = path.parent().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "persistent file has no parent directory", + ) + })?; + let name = path.file_name().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "persistent file has no name") + })?; + let id = TEMPORARY_ID.fetch_add(1, Ordering::Relaxed); + Ok(parent.join(format!( + ".{}.{}.{}.tmp", + name.to_string_lossy(), + std::process::id(), + id + ))) +} + +#[cfg(unix)] +fn sync_directory(path: &Path) -> io::Result<()> { + File::open(path)?.sync_all() +} + +#[cfg(not(unix))] +fn sync_directory(_path: &Path) -> io::Result<()> { + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::replace; + + #[test] + fn replace_preserves_a_previous_version_as_a_backup() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("state"); + replace(&path, b"first", 2).unwrap(); + replace(&path, b"second", 2).unwrap(); + + assert_eq!(std::fs::read(&path).unwrap(), b"second"); + let backups = std::fs::read_dir(directory.path()) + .unwrap() + .filter_map(Result::ok) + .filter(|entry| { + entry + .file_name() + .to_string_lossy() + .contains(".state.backup-") + }) + .count(); + assert_eq!(backups, 1); + } +} diff --git a/iota-util/src/crypto_helper.rs b/iota-util/src/crypto_helper.rs index 9375eb4..339dd1f 100644 --- a/iota-util/src/crypto_helper.rs +++ b/iota-util/src/crypto_helper.rs @@ -6,7 +6,10 @@ pub fn generate_keyring() -> Keyring { } pub fn keyring_to_base64(keyring: &Keyring) -> String { - STANDARD.encode(keyring.to_bytes()) + keyring + .try_to_bytes() + .map(|bytes| STANDARD.encode(bytes)) + .unwrap_or_default() } pub fn keyring_from_base64(s: &str) -> Option { @@ -15,7 +18,10 @@ pub fn keyring_from_base64(s: &str) -> Option { } pub fn public_key_bundle_to_base64(bundle: &PublicKeyBundle) -> String { - STANDARD.encode(bundle.as_bytes()) + bundle + .try_as_bytes() + .map(|bytes| STANDARD.encode(bytes)) + .unwrap_or_default() } pub fn public_key_bundle_from_base64(s: &str) -> Option { diff --git a/iota-util/src/lib.rs b/iota-util/src/lib.rs index 1fdc888..1b87352 100644 --- a/iota-util/src/lib.rs +++ b/iota-util/src/lib.rs @@ -1,3 +1,4 @@ +pub mod atomic_file; pub mod crypto_helper; pub mod crypto_util; pub mod file_util; diff --git a/iota/Cargo.toml b/iota/Cargo.toml index b05cfe8..1fc0858 100644 --- a/iota/Cargo.toml +++ b/iota/Cargo.toml @@ -13,7 +13,6 @@ iota-paths = { path = "../iota-paths" } iota-terms = { path = "../iota-terms" } iota-util = { path = "../iota-util" } tokio = { version = "1.50.0", features = ["full"] } -tokio-util = { version = "0.7", features = ["rt"] } serde_json = "1" serde_yaml = "0.9" clap = { version = "4.5", features = ["derive"] } diff --git a/iota/src/cli_args.rs b/iota/src/cli_args.rs index b1447a0..ca914f8 100644 --- a/iota/src/cli_args.rs +++ b/iota/src/cli_args.rs @@ -93,6 +93,7 @@ enum CliCommand { yes: bool, }, Components, + Health, Logs { #[arg(long, default_value_t = 100)] limit: usize, @@ -209,6 +210,12 @@ enum DaemonAction { #[arg(long)] operator: Option, }, + Bootstrap { + #[arg(long)] + bundle: String, + #[arg(long)] + operator: Option, + }, } #[derive(Args, Debug)] struct UpdateArgs { @@ -276,6 +283,10 @@ pub enum Command { bundle: String, operator: Option, }, + Bootstrap { + bundle: String, + operator: Option, + }, Status, Tasks, UsersList, @@ -329,6 +340,7 @@ pub enum Command { confirmed: bool, }, Components, + Health, Logs { limit: usize, }, @@ -388,6 +400,7 @@ impl CliInvocation { Some(CliCommand::Status) => Command::Status, Some(CliCommand::Tasks) => Command::Tasks, Some(CliCommand::Components) => Command::Components, + Some(CliCommand::Health) => Command::Health, Some(CliCommand::Completions { shell }) => Command::Completions { shell }, Some(CliCommand::Man) => Command::ManPage, Some(CliCommand::Users(users)) => match users.action { @@ -463,6 +476,9 @@ impl CliInvocation { DaemonAction::RestartService => Command::DaemonRestartService, DaemonAction::StopService => Command::DaemonStopService, DaemonAction::Install { bundle, operator } => Command::Install { bundle, operator }, + DaemonAction::Bootstrap { bundle, operator } => { + Command::Bootstrap { bundle, operator } + } }, }; Ok(Self { @@ -541,6 +557,12 @@ mod tests { assert_eq!(invocation.command, Command::UsersList); } + #[test] + fn parses_health() { + let invocation = CliInvocation::parse(["health".into()]).unwrap(); + assert_eq!(invocation.command, Command::Health); + } + #[test] fn parses_terminal_capability_overrides() { let invocation = @@ -599,6 +621,26 @@ mod tests { ); } + #[test] + fn parses_bootstrap_operator() { + let invocation = CliInvocation::parse([ + "daemon".into(), + "bootstrap".into(), + "--bundle".into(), + "release.zip".into(), + "--operator".into(), + "alice".into(), + ]) + .unwrap(); + assert_eq!( + invocation.command, + Command::Bootstrap { + bundle: "release.zip".into(), + operator: Some("alice".into()), + } + ); + } + #[test] fn parses_unconfirmed_destructive_commands_explicitly() { let invocation = CliInvocation::parse(["daemon".into(), "stop".into()]).unwrap(); diff --git a/iota/src/main.rs b/iota/src/main.rs index d5c79d8..3746707 100644 --- a/iota/src/main.rs +++ b/iota/src/main.rs @@ -40,33 +40,6 @@ async fn run() -> Result<(), StartupError> { unicode, command, } = invocation; - let local_endpoint = match iota_paths::IotaPaths::resolve(iota_paths::Scope::User) - .map_err(|error| StartupError::Other(format!("Cannot resolve user IPC path: {error}")))? - .ipc_endpoint - { - iota_paths::IpcEndpoint::UnixSocket(path) => path, - iota_paths::IpcEndpoint::WindowsPipe(name) => { - return Err(StartupError::Other(format!( - "Windows IPC endpoint {name} is not supported by this client build" - ))); - } - }; - let system_endpoint = match iota_paths::IotaPaths::resolve(iota_paths::Scope::System) - .map_err(|error| StartupError::Other(format!("Cannot resolve system IPC path: {error}")))? - .ipc_endpoint - { - iota_paths::IpcEndpoint::UnixSocket(path) => path, - iota_paths::IpcEndpoint::WindowsPipe(name) => { - return Err(StartupError::Other(format!( - "Windows IPC endpoint {name} is not supported by this client build" - ))); - } - }; - let endpoints = daemon_setup_flow::DaemonEndpoints { - local: local_endpoint, - system: system_endpoint, - }; - match command { Command::Help => { print_help(); @@ -91,7 +64,12 @@ async fn run() -> Result<(), StartupError> { ) .map_err(|error| StartupError::Other(format!("Installation failed: {error}"))) } + Command::Bootstrap { bundle, operator } => { + iota_installer::bootstrap_linux_bundle(Path::new(&bundle), operator.as_deref()) + .map_err(|error| StartupError::Other(format!("Bootstrap failed: {error}"))) + } command => { + let endpoints = resolve_endpoints()?; if matches!( command, Command::DaemonEnable { .. } @@ -123,6 +101,32 @@ async fn run() -> Result<(), StartupError> { } } +fn resolve_endpoints() -> Result { + let local = match iota_paths::IotaPaths::resolve(iota_paths::Scope::User) + .map_err(|error| StartupError::Other(format!("Cannot resolve user IPC path: {error}")))? + .ipc_endpoint + { + iota_paths::IpcEndpoint::UnixSocket(path) => path, + iota_paths::IpcEndpoint::WindowsPipe(name) => { + return Err(StartupError::Other(format!( + "Windows IPC endpoint {name} is not supported by this client build" + ))); + } + }; + let system = match iota_paths::IotaPaths::resolve(iota_paths::Scope::System) + .map_err(|error| StartupError::Other(format!("Cannot resolve system IPC path: {error}")))? + .ipc_endpoint + { + iota_paths::IpcEndpoint::UnixSocket(path) => path, + iota_paths::IpcEndpoint::WindowsPipe(name) => { + return Err(StartupError::Other(format!( + "Windows IPC endpoint {name} is not supported by this client build" + ))); + } + }; + Ok(daemon_setup_flow::DaemonEndpoints { local, system }) +} + async fn run_startup_command(command: Command) -> Result<(), StartupError> { let manager = iota_process_manager::detect() .await @@ -406,6 +410,7 @@ fn print_help() { println!(" config get Show current configuration"); println!(" config set Set a configuration value"); println!(" config reload Reload configuration"); + println!(" health Show component health"); println!(" components Show component health"); println!(" logs [--limit N] Show recent log entries"); println!(" update check Check for updates"); @@ -422,6 +427,7 @@ fn print_help() { println!(" daemon restart-service Restart the daemon service"); println!(" daemon stop-service Stop the daemon service"); println!(" daemon install Install from a bundle"); + println!(" daemon bootstrap Install and enable a Linux systemd bundle"); println!(" help Show this help message"); println!(" completions Generate shell completions"); println!(" man Show the man page"); @@ -630,6 +636,7 @@ async fn run_command( Command::ConfigGet => LocalRequest::GetConfig, Command::ConfigSet { key, value } => LocalRequest::SetConfig { key, value }, Command::ConfigReload => LocalRequest::ReloadConfig, + Command::Health => LocalRequest::ListComponents, Command::Components => LocalRequest::ListComponents, Command::Logs { limit } => LocalRequest::GetLogs { limit }, Command::UpdateCheck => LocalRequest::CheckUpdate, @@ -657,6 +664,7 @@ async fn run_command( | Command::Completions { .. } | Command::ManPage | Command::Install { .. } + | Command::Bootstrap { .. } | Command::TermsStatus { .. } | Command::TermsShow { .. } | Command::TermsAccept { .. } diff --git a/omikron-connector/Cargo.toml b/omikron-connector/Cargo.toml index 31796b0..5902891 100644 --- a/omikron-connector/Cargo.toml +++ b/omikron-connector/Cargo.toml @@ -24,8 +24,4 @@ tokio = { version = "1.50.0", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } uuid = { version = "*", features = ["v4"] } base64 = "0.22.1" -hex = "*" -rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -sha2 = "0.11.0" -x448 = { version = "*" } diff --git a/omikron-connector/src/omikron_connection.rs b/omikron-connector/src/omikron_connection.rs index 1e1f5c7..1aae831 100755 --- a/omikron-connector/src/omikron_connection.rs +++ b/omikron-connector/src/omikron_connection.rs @@ -39,11 +39,18 @@ use iota_util::route_target::RouteTarget; const IOTA_KEYRING_PATH: &str = "iota.mk"; static IDENTITY_PATH: std::sync::OnceLock = std::sync::OnceLock::new(); +static OMIKRON_PUBLIC_KEY_PATH: std::sync::OnceLock = std::sync::OnceLock::new(); -/// Must be called by the daemon before any Omikron connection is attempted. -/// It keeps identity material independent from the working directory. +/* + * Keeps identity and pinned Omikron key files independent from the process + * working directory, so restarts use the same trusted material. + */ pub fn configure_identity_path(path: PathBuf) { + let key_path = path.parent().map(|parent| parent.join("omikron.mpkb")); let _ = IDENTITY_PATH.set(path); + if let Some(key_path) = key_path { + let _ = OMIKRON_PUBLIC_KEY_PATH.set(key_path); + } } fn identity_path() -> &'static Path { IDENTITY_PATH @@ -51,7 +58,51 @@ fn identity_path() -> &'static Path { .map(PathBuf::as_path) .unwrap_or_else(|| Path::new(IOTA_KEYRING_PATH)) } -const OMIKRON_PUBLIC_KEY_PATH: &str = "omikron.mpkb"; +fn omikron_public_key_path() -> &'static Path { + OMIKRON_PUBLIC_KEY_PATH + .get() + .map(PathBuf::as_path) + .unwrap_or_else(|| Path::new("omikron.mpkb")) +} + +fn save_keyring(keyring: &Keyring, path: &Path) -> Result<(), String> { + let temporary = serialization_path(path)?; + mtp::files::save_keyring_raw(keyring, &temporary) + .map_err(|error| format!("serialize keyring: {error}"))?; + let bytes = + std::fs::read(&temporary).map_err(|error| format!("read serialized keyring: {error}")); + let _ = std::fs::remove_file(&temporary); + let bytes = bytes?; + iota_util::atomic_file::replace_private(path, &bytes, 3) + .map_err(|error| format!("write {}: {error}", path.display())) +} + +fn save_omikron_public_key(key: &PublicKeyBundle, path: &Path) -> Result<(), String> { + let temporary = serialization_path(path)?; + mtp::files::save_public_key_bundle(key, &temporary) + .map_err(|error| format!("serialize Omikron public key: {error}"))?; + let bytes = std::fs::read(&temporary) + .map_err(|error| format!("read serialized Omikron public key: {error}")); + let _ = std::fs::remove_file(&temporary); + let bytes = bytes?; + iota_util::atomic_file::replace(path, &bytes, 3) + .map_err(|error| format!("write {}: {error}", path.display())) +} + +fn serialization_path(path: &Path) -> Result { + let parent = path + .parent() + .ok_or_else(|| format!("{} has no parent directory", path.display()))?; + let name = path + .file_name() + .ok_or_else(|| format!("{} has no file name", path.display()))?; + Ok(parent.join(format!( + ".{}.serialize-{}", + name.to_string_lossy(), + Uuid::new_v4() + ))) +} + const RECONNECT_DELAY: Duration = Duration::from_secs(5); const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); @@ -434,7 +485,7 @@ impl OmikronConnection { if let Some(parent) = path.parent() { let _ = std::fs::create_dir_all(parent); } - if let Err(e) = mtp::files::save_keyring_raw(&keyring, path) { + if let Err(e) = save_keyring(&keyring, path) { log!("Failed to persist {}: {}", path.display(), e); } @@ -469,21 +520,29 @@ impl OmikronConnection { let port: u16 = port_str .parse() .map_err(|_| format!("Invalid OMIKRON_PORT: {}", port_str))?; - let public_key = mtp::files::load_public_key_bundle(OMIKRON_PUBLIC_KEY_PATH) + let key_path = omikron_public_key_path(); + let public_key = mtp::files::load_public_key_bundle(key_path) .map_err(|e| { format!( - "Failed to load Omikron public key bundle from {}: {}. Obtain {} from the Omikron operator and place it in the working directory.", - OMIKRON_PUBLIC_KEY_PATH, e, OMIKRON_PUBLIC_KEY_PATH + "Failed to load Omikron public key bundle from {}: {}. Obtain {} from the Omikron operator and place it at that path.", + key_path.display(), e, key_path.display() ) })?; return Ok((host, port, public_key)); } - let cached_key = mtp::files::load_public_key_bundle(OMIKRON_PUBLIC_KEY_PATH).ok(); + let key_path = omikron_public_key_path(); + let cached_key = mtp::files::load_public_key_bundle(key_path).ok(); let cached_host_port = { let conf = CONFIG.load(); match (&conf.omikron_host, conf.omikron_port) { - (Some(host), Some(port)) => Some((host.clone(), port)), + (Some(host), Some(port)) if !host.trim().is_empty() && port != 0 => { + Some((host.clone(), port)) + } + (Some(_), Some(_)) => { + log!("Ignoring invalid cached Omikron endpoint in Iota configuration"); + None + } _ => None, } }; @@ -504,21 +563,35 @@ impl OmikronConnection { let (host, port, public_key) = if let Some(endpoint) = discovered { match &cached_key { - Some(cached) if cached.as_bytes() != endpoint.public_key.as_bytes() => { - log!( - "Fetched Omikron public key differs from the cached {} - keeping the \ - cached key. Delete {} manually if this is an expected key rotation.", - OMIKRON_PUBLIC_KEY_PATH, - OMIKRON_PUBLIC_KEY_PATH - ); - (endpoint.host, endpoint.port, cached.clone()) + Some(cached) => { + let keys_match = + match (cached.try_as_bytes(), endpoint.public_key.try_as_bytes()) { + (Ok(cached_bytes), Ok(discovered_bytes)) => { + cached_bytes == discovered_bytes + } + _ => false, + }; + if !keys_match { + log!( + "Fetched Omikron public key differs from the cached {} - keeping the \ + cached key. Delete {} manually if this is an expected key rotation.", + key_path.display(), + key_path.display() + ); + if let Some((cached_host, cached_port)) = &cached_host_port { + (cached_host.clone(), *cached_port, cached.clone()) + } else { + return Err(format!( + "Omega returned an Omikron key that differs from {} and no validated cached endpoint is available", + key_path.display() + )); + } + } else { + (endpoint.host, endpoint.port, cached.clone()) + } } - Some(cached) => (endpoint.host, endpoint.port, cached.clone()), None => { - if let Err(e) = mtp::files::save_public_key_bundle( - &endpoint.public_key, - OMIKRON_PUBLIC_KEY_PATH, - ) { + if let Err(e) = save_omikron_public_key(&endpoint.public_key, key_path) { log!("Failed to cache Omikron public key: {}", e); } (endpoint.host, endpoint.port, endpoint.public_key) @@ -1885,7 +1958,7 @@ impl OmikronConnection { )) })?; } - mtp::files::save_keyring_raw(&keyring, path).map_err(|error| { + save_keyring(&keyring, path).map_err(|error| { OmikronError::Internal(format!( "could not save new identity {}: {error}", path.display() @@ -2052,3 +2125,25 @@ impl OmikronClient for OmikronConnection { Self::is_connected(self).await } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn durable_keyring_save_preserves_mtp_format() { + let directory = std::env::temp_dir().join(format!("iota-keyring-test-{}", Uuid::new_v4())); + std::fs::create_dir_all(&directory).unwrap(); + let path = directory.join(IOTA_KEYRING_PATH); + let keyring = crypto_helper::generate_keyring(); + + save_keyring(&keyring, &path).unwrap(); + + let loaded = mtp::files::load_keyring_raw(&path).unwrap(); + assert_eq!( + keyring.try_to_bytes().unwrap(), + loaded.try_to_bytes().unwrap() + ); + std::fs::remove_dir_all(directory).unwrap(); + } +} diff --git a/other-iota/Cargo.toml b/other-iota/Cargo.toml index a6933c3..1fb5337 100644 --- a/other-iota/Cargo.toml +++ b/other-iota/Cargo.toml @@ -4,57 +4,3 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } -iota-logger = { path = "../iota-logger" } -iota-util = { path = "../iota-util" } -iota-storage = { path = "../iota-storage" } -iota-state = { path = "../iota-state" } -iota-auth = { path = "../iota-auth" } - -actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" -chrono = "0.4.43" -crossterm = "*" -dashmap = "6.1.0" -futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } -json = "*" -lazy_static = "1.5.0" -once_cell = "1.21.3" -open = "5.3.3" -pnet = "0.35.0" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" -rustls = { version = "0.23.37", features = ["aws-lc-rs"] } -rustls-pemfile = "2.2.0" -serde_json = "1.0.149" -sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" -tokio = { version = "1.50.0", features = ["full"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" -uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -warp = "*" -x448 = { version = "*" } -zip = "6.0.0" diff --git a/web-server/Cargo.toml b/web-server/Cargo.toml index ad49fba..c07501f 100644 --- a/web-server/Cargo.toml +++ b/web-server/Cargo.toml @@ -9,6 +9,5 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = ["web-s bytes = "1" http = "1" iota-logger = { path = "../iota-logger" } -iota-util = { path = "../iota-util" } tokio = { version = "1.50.0", features = ["full"] } tokio-util = { version = "0.7", features = ["rt"] } diff --git a/web-ui/Cargo.toml b/web-ui/Cargo.toml index 988aac1..f6f6ff8 100644 --- a/web-ui/Cargo.toml +++ b/web-ui/Cargo.toml @@ -4,57 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] -omikron-connector = { path = "../omikron-connector" } iota-storage = { path = "../iota-storage" } iota-state = { path = "../iota-state" } iota-util = { path = "../iota-util" } iota-logger = { path = "../iota-logger" } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" -aes-gcm = "0.10.3" -async-trait = "0.1.89" -base64 = "0.22.1" -chrono = "0.4.43" -crossterm = "*" -dashmap = "6.1.0" -futures = "*" -futures-util = "*" -hex = "*" -hkdf = "0.12.4" -hyper = { version = "1.8.1", features = [ - "capi", - "client", - "full", - "http1", - "http2", - "nightly", - "server", -] } -hyper-util = { version = "*" } -json = "*" -lazy_static = "1.5.0" -once_cell = "1.21.3" -open = "5.3.3" -pnet = "0.35.0" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -ratatui = "0.30.0" -reqwest = "0.13.2" -rusqlite = "0.40.0" rustls = { version = "0.23.37", features = ["aws-lc-rs"] } rustls-pemfile = "2.2.0" serde_json = "1.0.149" -sha2 = "0.11.0" -strum = "0.28.0" -strum_macros = "0.28.0" -sysinfo = "0.39.0" tokio = { version = "1.50.0", features = ["full"] } -tokio-tungstenite = { version = "*", features = ["native-tls"] } -tungstenite = "*" -uuid = { version = "*", features = ["v4"] } -walkdir = "2.5.0" -warp = "*" -x448 = { version = "*" } -zip = "6.0.0" diff --git a/web-ui/src/api.rs b/web-ui/src/api.rs index c5c3dab..0243a61 100755 --- a/web-ui/src/api.rs +++ b/web-ui/src/api.rs @@ -128,7 +128,10 @@ async fn users_remove( return forbidden(); } - let uuid = payload.get("uuid").and_then(|v| v.as_i64()).unwrap_or(0); + let uuid = match user_id(&payload) { + Ok(uuid) => uuid, + Err(response) => return response, + }; iota_storage::users::user_manager::remove_user(uuid); iota_storage::users::user_manager::save_users(); @@ -194,7 +197,14 @@ fn success() -> HttpResponse { } fn error() -> HttpResponse { - HttpResponse::Ok().json(json!({ "type": "error" })) + HttpResponse::BadRequest().json(json!({ "type": "error" })) +} + +fn user_id(payload: &Value) -> Result { + payload + .get("uuid") + .and_then(Value::as_i64) + .ok_or_else(error) } fn is_allowed(addr: SocketAddr, ssl: bool) -> bool { @@ -208,3 +218,20 @@ fn is_allowed_req(req: &HttpRequest, ssl: bool) -> bool { false } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_response_is_bad_request() { + assert_eq!(error().status(), actix_web::http::StatusCode::BAD_REQUEST); + } + + #[test] + fn user_id_rejects_missing_or_non_integer_uuid() { + assert!(user_id(&json!({})).is_err()); + assert!(user_id(&json!({ "uuid": "0" })).is_err()); + assert_eq!(user_id(&json!({ "uuid": 0 })).ok(), Some(0)); + } +}