diff --git a/Cargo.lock b/Cargo.lock index 705b64d..c53e77a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,16 +44,16 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.13.2" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53200bd1513e569e6e644181c922cec072c121a27db5b38d45e88e630c369366" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-tls", "actix-utils", - "base64 0.22.1", + "base64", "bitflags 2.13.1", "brotli", "bytes", @@ -119,9 +119,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716aae056e2f869b7b5cfd8a08fcf98890f8455bec61d69c7dff5d8576f9d2b" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", @@ -129,7 +129,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.10", "tokio", "tracing", ] @@ -211,7 +211,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.6.5", "time", "tracing", "url", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -341,9 +341,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] @@ -504,9 +504,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.18.0" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", @@ -532,12 +532,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - [[package]] name = "base64ct" version = "1.8.3" @@ -678,9 +672,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -818,7 +812,7 @@ dependencies = [ "actix-web-actors", "aes-gcm", "async-trait", - "base64 0.22.1", + "base64", "chrono", "crossterm", "dashmap", @@ -1133,22 +1127,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "curve25519-dalek" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "curve25519-dalek-derive", - "digest 0.11.3", - "fiat-crypto 0.3.0", - "rustc_version", - "subtle", - "zeroize", -] - [[package]] name = "curve25519-dalek-derive" version = "0.1.1" @@ -1162,9 +1140,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.24.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -1172,26 +1150,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.24.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 3.0.3", + "syn 2.0.119", ] [[package]] name = "darling_macro" -version = "0.24.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 3.0.3", + "syn 2.0.119", ] [[package]] @@ -1210,9 +1188,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deflate64" @@ -1233,6 +1211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", + "pem-rfc7468", "zeroize", ] @@ -1243,7 +1222,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", - "pem-rfc7468", "zeroize", ] @@ -1370,45 +1348,20 @@ dependencies = [ "signature 2.2.0", ] -[[package]] -name = "ed25519" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" -dependencies = [ - "pkcs8 0.11.0", - "signature 3.0.0", -] - [[package]] name = "ed25519-dalek" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", + "curve25519-dalek", + "ed25519", "serde", "sha2 0.10.9", "subtle", "zeroize", ] -[[package]] -name = "ed25519-dalek" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" -dependencies = [ - "curve25519-dalek 5.0.0", - "ed25519 3.0.0", - "serde", - "sha2 0.11.0", - "signature 3.0.0", - "subtle", - "zeroize", -] - [[package]] name = "ed448-goldilocks" version = "0.7.2" @@ -1482,6 +1435,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastbloom" version = "0.17.0" @@ -1512,12 +1471,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - [[package]] name = "filedescriptor" version = "0.8.3" @@ -1531,9 +1484,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "finl_unicode" @@ -1782,7 +1735,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.5.0", + "http 1.4.2", "indexmap", "slab", "tokio", @@ -1799,7 +1752,7 @@ dependencies = [ "bytes", "fastrand", "futures-util", - "http 1.5.0", + "http 1.4.2", "pin-project-lite", "tokio", ] @@ -1840,7 +1793,7 @@ dependencies = [ "futures-util", "h3", "h3-datagram", - "http 1.5.0", + "http 1.4.2", "pin-project-lite", "tokio", "tracing", @@ -1889,10 +1842,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "headers-core", - "http 1.5.0", + "http 1.4.2", "httpdate", "mime", "sha1 0.10.7", @@ -1904,7 +1857,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.5.0", + "http 1.4.2", ] [[package]] @@ -1974,9 +1927,9 @@ dependencies = [ [[package]] name = "http" -version = "1.5.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1989,7 +1942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.5.0", + "http 1.4.2", ] [[package]] @@ -2000,7 +1953,7 @@ checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.5.0", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -2019,9 +1972,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "ctutils", "typenum", @@ -2038,7 +1991,7 @@ dependencies = [ "futures-channel", "futures-core", "h2 0.4.15", - "http 1.5.0", + "http 1.4.2", "http-body", "httparse", "httpdate", @@ -2055,7 +2008,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.5.0", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -2070,18 +2023,18 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", - "http 1.5.0", + "http 1.4.2", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -2273,15 +2226,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.13" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.119", ] [[package]] @@ -2296,7 +2249,6 @@ dependencies = [ "iota-paths", "iota-process-manager", "iota-terms", - "iota-util", "serde_json", "serde_yaml", "tokio", @@ -2309,7 +2261,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "async-trait", - "base64 0.22.1", + "base64", "chrono", "crossterm", "dashmap", @@ -2359,7 +2311,7 @@ dependencies = [ "actix-web-actors", "aes-gcm", "async-trait", - "base64 0.22.1", + "base64", "chrono", "crossterm", "dashmap", @@ -2551,7 +2503,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "arc-swap", - "base64 0.22.1", + "base64", "hex", "hkdf 0.12.4", "iota-logger", @@ -2596,8 +2548,8 @@ version = "0.1.0" dependencies = [ "aes-gcm", "anyhow", - "base64 0.22.1", - "ed25519-dalek 2.2.0", + "base64", + "ed25519-dalek", "hex", "hkdf 0.12.4", "iota-logger", @@ -2626,7 +2578,7 @@ dependencies = [ name = "iota-util" version = "0.1.0" dependencies = [ - "base64 0.22.1", + "base64", "hex", "iota-paths", "mtp", @@ -2640,9 +2592,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "ipnetwork" @@ -2791,9 +2743,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -2847,9 +2799,9 @@ dependencies = [ [[package]] name = "line-clipping" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ "bitflags 2.13.1", ] @@ -2906,9 +2858,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" dependencies = [ "hashbrown 0.17.1", ] @@ -3060,7 +3012,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-client", "mtp-codec", @@ -3076,7 +3028,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", @@ -3089,9 +3041,9 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ - "base64 0.23.1", + "base64", "byteorder", "mtp-common", "mtp-crypto", @@ -3102,7 +3054,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "quinn", "rustls", @@ -3113,11 +3065,11 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ - "base64 0.23.1", + "base64", "chacha20poly1305", - "ed25519-dalek 3.0.0", + "ed25519-dalek", "getrandom 0.4.3", "hkdf 0.13.0", "ml-dsa", @@ -3127,7 +3079,7 @@ dependencies = [ "rustls", "serde", "sha2 0.11.0", - "thiserror 2.0.19", + "thiserror 1.0.69", "tokio", "zeroize", ] @@ -3135,24 +3087,24 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-crypto", "rand 0.10.2", - "thiserror 2.0.19", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand 0.8.7", "tokio", "tracing", "wtransport", @@ -3161,7 +3113,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "mtp-codec", @@ -3179,7 +3131,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "serde", "serde_yaml", @@ -3188,14 +3140,14 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b" +source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "bytes", "h3", "h3-quinn", "h3-webtransport", - "http 1.5.0", + "http 1.4.2", "http-body-util", "hyper", "hyper-util", @@ -3400,7 +3352,7 @@ name = "omikron-connector" version = "0.1.0" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "dashmap", "hex", "iota-connection", @@ -3508,7 +3460,7 @@ dependencies = [ "actix-web-actors", "aes-gcm", "async-trait", - "base64 0.22.1", + "base64", "chrono", "crossterm", "dashmap", @@ -3553,21 +3505,21 @@ dependencies = [ [[package]] name = "palette" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ "approx", + "fast-srgb8", "libm", "palette_derive", - "palette_math", ] [[package]] name = "palette_derive" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ "by_address", "proc-macro2", @@ -3575,15 +3527,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "palette_math" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12" -dependencies = [ - "libm", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -3623,15 +3566,15 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] [[package]] name = "pem-rfc7468" -version = "1.0.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] @@ -3960,7 +3903,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.5", "thiserror 2.0.19", "tokio", "tracing", @@ -4001,7 +3944,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.5", "tracing", "windows-sys 0.61.2", ] @@ -4239,9 +4182,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.18" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -4266,12 +4209,12 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "h2 0.4.15", - "http 1.5.0", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -4669,7 +4612,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.0", "sponge-cursor", ] @@ -4769,6 +4712,16 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.5" @@ -5005,7 +4958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bitflags 2.13.1", "fancy-regex", "filedescriptor", @@ -5082,9 +5035,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.55" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", "libc", @@ -5149,20 +5102,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.119", ] [[package]] @@ -5249,7 +5202,7 @@ dependencies = [ "bitflags 2.13.1", "bytes", "futures-util", - "http 1.5.0", + "http 1.4.2", "http-body", "pin-project-lite", "tower", @@ -5316,7 +5269,7 @@ checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" dependencies = [ "bytes", "data-encoding", - "http 1.5.0", + "http 1.4.2", "httparse", "log", "native-tls", @@ -5501,7 +5454,7 @@ dependencies = [ "bytes", "futures-util", "headers", - "http 1.5.0", + "http 1.4.2", "http-body", "http-body-util", "log", @@ -5594,7 +5547,7 @@ name = "web-server" version = "0.1.0" dependencies = [ "bytes", - "http 1.5.0", + "http 1.4.2", "iota-logger", "iota-util", "mtp", @@ -5630,7 +5583,7 @@ dependencies = [ "actix-web-actors", "aes-gcm", "async-trait", - "base64 0.22.1", + "base64", "chrono", "crossterm", "dashmap", @@ -6014,7 +5967,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2", + "socket2 0.6.5", "thiserror 2.0.19", "time", "tokio", @@ -6042,7 +5995,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.3", + "curve25519-dalek", "rand_core 0.6.4", "serde", "zeroize", @@ -6113,18 +6066,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -6234,9 +6187,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.7" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" diff --git a/client/src/client_connection.rs b/client/src/client_connection.rs index 512cc31..cda5cf8 100644 --- a/client/src/client_connection.rs +++ b/client/src/client_connection.rs @@ -407,12 +407,6 @@ impl ClientConnection { return; } - if cv.is_type(CommunicationType::MessageGet) { - self.send_message(&message_handlers::handle_message_get(&cv)) - .await; - return; - } - if cv.is_type(CommunicationType::GetChats) { self.send_message(&message_handlers::handle_get_chats(&cv)) .await; diff --git a/iota-cli/src/ipc_client.rs b/iota-cli/src/ipc_client.rs index ca912fc..ad0796f 100644 --- a/iota-cli/src/ipc_client.rs +++ b/iota-cli/src/ipc_client.rs @@ -483,9 +483,6 @@ impl IpcClient { ResponsePayload::UserRemoved { user_id } => { format!("Removed user {}", user_id) } - ResponsePayload::UserDataPurged { user_id } => { - format!("Purged hosted data for {}", user_id) - } ResponsePayload::Acknowledged { message } => message.clone(), ResponsePayload::DaemonStatus(status) => status.formatted.clone(), ResponsePayload::Config(config) => config.yaml.clone(), diff --git a/iota-cli/src/screens/users.rs b/iota-cli/src/screens/users.rs index 93d4b13..fab710c 100644 --- a/iota-cli/src/screens/users.rs +++ b/iota-cli/src/screens/users.rs @@ -27,9 +27,6 @@ use std::{ pub struct UserEntry { pub user_id: i64, pub username: String, - pub state: iota_ipc::LocalUserState, - pub data_present: bool, - pub credential_present: bool, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -149,9 +146,7 @@ impl UsersScreen { let user = &self.users[*user_index]; ( *user_index, - format!("{:>6} {} {}{}", user.user_id, user.username, - match user.state { iota_ipc::LocalUserState::Managed => "managed", iota_ipc::LocalUserState::Released => "released" }, - if user.data_present { "" } else { ", data purged" }), + format!("{:>6} {}", user.user_id, user.username), ) }) .collect(); @@ -216,7 +211,7 @@ impl UsersScreen { f, buttons_area[2], ActionButton { - label: "Release", + label: "Remove", intent: ButtonIntent::Destructive, focused: self.focus == Focus::RemoveButton, enabled: !self.loading && !self.pending && !self.users.is_empty(), @@ -240,7 +235,7 @@ impl UsersScreen { return InteractionResult::AppTask { task: Box::pin(async move { let result = match ipc.send_request(iota_ipc::LocalRequest::CreateUser { username: name }).await { - Ok(iota_ipc::ResponseResult::Ok(iota_ipc::ResponsePayload::UserCreated { user_id, username })) => Ok(UserEntry { user_id, username, state: iota_ipc::LocalUserState::Managed, data_present: true, credential_present: true }), + Ok(iota_ipc::ResponseResult::Ok(iota_ipc::ResponsePayload::UserCreated { user_id, username })) => Ok(UserEntry { user_id, username }), Ok(iota_ipc::ResponseResult::Error(error)) => Err(format!("Cannot create user: {error}")), Ok(_) => Err("Daemon returned an unexpected response while creating the user.".into()), Err(error) => Err(format!("Cannot create user: {error}")), @@ -254,14 +249,14 @@ impl UsersScreen { let ipc = self.ipc.clone(); let id = user.user_id; self.pending = true; - self.message = Some(format!("Releasing {}…", user.username)); + self.message = Some(format!("Removing {}…", user.username)); return InteractionResult::AppTask { task: Box::pin(async move { - let result = match ipc.send_request(iota_ipc::LocalRequest::ReleaseUser { user_id: id }).await { - Ok(iota_ipc::ResponseResult::Ok(iota_ipc::ResponsePayload::Acknowledged { .. })) => Ok(()), - Ok(iota_ipc::ResponseResult::Error(error)) => Err(format!("Cannot release user: {error}")), - Ok(_) => Err("Daemon returned an unexpected response while releasing the user.".into()), - Err(error) => Err(format!("Cannot release user: {error}")), + let result = match ipc.send_request(iota_ipc::LocalRequest::RemoveUser { user_id: id }).await { + Ok(iota_ipc::ResponseResult::Ok(iota_ipc::ResponsePayload::UserRemoved { .. })) => Ok(()), + Ok(iota_ipc::ResponseResult::Error(error)) => Err(format!("Cannot remove user: {error}")), + Ok(_) => Err("Daemon returned an unexpected response while removing the user.".into()), + Err(error) => Err(format!("Cannot remove user: {error}")), }; UiEvent::App(AppEvent::UserRemoved { user_id: id, @@ -533,11 +528,10 @@ impl Screen for UsersScreen { match result { Ok(()) => { self.pending_dialog = None; - if let Some(user) = self.users.iter_mut().find(|user| user.user_id == user_id) { - user.state = iota_ipc::LocalUserState::Released; - user.credential_present = false; - } - self.message = Some(format!("Released user {user_id}; hosted data retained.")); + self.users.retain(|user| user.user_id != user_id); + self.focused_index = + self.focused_index.min(self.users.len().saturating_sub(1)); + self.message = Some(format!("Removed user {user_id}.")); } Err(error) => { self.dialog = self.pending_dialog.take(); diff --git a/iota-cli/src/ui.rs b/iota-cli/src/ui.rs index dad4c80..b06dab1 100644 --- a/iota-cli/src/ui.rs +++ b/iota-cli/src/ui.rs @@ -602,9 +602,6 @@ impl UI { .map(|u| UserEntry { user_id: u.user_id, username: u.username, - state: u.state, - data_present: u.data_present, - credential_present: u.credential_present, }) .collect()) } diff --git a/iota-connection/src/message_handlers.rs b/iota-connection/src/message_handlers.rs index dc1d4dc..73244d7 100644 --- a/iota-connection/src/message_handlers.rs +++ b/iota-connection/src/message_handlers.rs @@ -102,11 +102,11 @@ pub fn handle_message_delete(cv: &CommunicationValue) -> CommunicationValue { } } -fn stored_message_fields( +fn stored_message_value( message: &chat_files::StoredMessage, storage_owner: i64, partner_id: i64, -) -> Vec<(DataType, DataValue)> { +) -> DataValue { let mut fields = vec![ ( DataType::MessageId, @@ -162,15 +162,7 @@ fn stored_message_fields( .collect(); fields.push((DataType::Reactions, DataValue::Array(reactions))); } - fields -} - -fn stored_message_value( - message: &chat_files::StoredMessage, - storage_owner: i64, - partner_id: i64, -) -> DataValue { - typed_container(stored_message_fields(message, storage_owner, partner_id)) + typed_container(fields) } pub fn handle_get_chat_secret(cv: &CommunicationValue) -> CommunicationValue { @@ -528,28 +520,6 @@ pub fn handle_messages_get(cv: &CommunicationValue) -> CommunicationValue { .add_typed_default(DataType::Messages, DataValue::Array(msg_array)) } -pub fn handle_message_get(cv: &CommunicationValue) -> CommunicationValue { - let Some(send_time) = data_i64(cv, DataType::SendTime) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - let partner_id = data_i64(cv, DataType::ChatPartnerId); - let owner = cv.get_sender() as i64; - - let message = match chat_files::get_message(owner, send_time, partner_id) { - Ok(Some(message)) => message, - Ok(None) => return error_response(cv, CommunicationType::ErrorNotFound), - Err(_) => return error_response(cv, CommunicationType::ErrorInvalidData), - }; - - let mut response = CommunicationValue::new(CommunicationType::MessageGet) - .with_id(cv.get_id()) - .with_receiver(cv.get_sender()); - for (data_type, value) in stored_message_fields(&message, owner, message.external_user) { - response = response.add_typed_default(data_type, value); - } - response -} - pub fn handle_get_chats(cv: &CommunicationValue) -> CommunicationValue { let user_id = cv.get_sender(); let users = chats_util::get_users(user_id as i64); diff --git a/iota-daemon-lib/src/command_router.rs b/iota-daemon-lib/src/command_router.rs index 74f5f96..58e627d 100644 --- a/iota-daemon-lib/src/command_router.rs +++ b/iota-daemon-lib/src/command_router.rs @@ -9,9 +9,8 @@ use iota_ipc::{ use iota_logger::{log, log_command}; use iota_storage::users::user_manager; use iota_storage::util::config_util::{self}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::codec::{CommunicationType, CommunicationValue}; use std::sync::{Arc, Mutex}; -use std::time::Duration; use crate::daemon_state::{ShutdownReason, StartupPhase}; @@ -52,10 +51,7 @@ impl CommandRouter { } let needs_omikron = matches!( request, - LocalRequest::CreateUser { .. } - | LocalRequest::AttachUserFromTu { .. } - | LocalRequest::ReleaseUser { .. } - | LocalRequest::CompleteDeleteUser { .. } + LocalRequest::CreateUser { .. } | LocalRequest::RemoveUser { .. } ); if needs_omikron && !self.services.omikron.is_connected().await { return ResponseResult::Error( @@ -96,15 +92,11 @@ impl CommandRouter { ResponseResult::Ok(ResponsePayload::Tasks(tasks)) } LocalRequest::ListUsers => { - let users: Vec = user_manager::get_residency() + let users: Vec = user_manager::get_users() .into_iter() .map(|user| UserSummary { - credential_present: user.state == user_manager::LocalUserState::Managed - && user_manager::get_user(user.user_id).is_some_and(|profile| iota_util::file_util::read_user_credential_with_legacy(user.user_id, &profile.username).ok().flatten().is_some()), user_id: user.user_id, username: user.username, - state: match user.state { user_manager::LocalUserState::Managed => iota_ipc::LocalUserState::Managed, user_manager::LocalUserState::Released => iota_ipc::LocalUserState::Released }, - data_present: user.data_present, }) .collect(); ResponseResult::Ok(ResponsePayload::Users(users)) @@ -145,61 +137,18 @@ impl CommandRouter { } } } - LocalRequest::PurgeUserData { user_id } => match user_manager::purge_user_data(user_id) { - Ok(()) => ResponseResult::Ok(ResponsePayload::UserDataPurged { user_id }), - Err(error) => { - log!("User data purge failed for {user_id}: {error}"); - ResponseResult::Error(IpcErrorCode::StorageFailure) - } - }, - LocalRequest::AttachUserFromTu { credential } => { - match omikron_connector::user_ops::attach_user_from_tu(self.services.omikron.as_ref(), &credential.0).await { - Ok(user) => ResponseResult::Ok(ResponsePayload::Acknowledged { message: format!("Added {} ({}) to this Iota", user.username, user.user_id) }), - Err(error) => { - log!("Credential attach failed: {error:?}"); - ResponseResult::Error(IpcErrorCode::Unauthorized) - } - } - } - LocalRequest::CompleteDeleteUser { user_id, credential } => { - let contents = match credential { - Some(value) => Ok(value.0), - None => user_manager::get_user(user_id) - .ok_or(()) - .and_then(|user| iota_util::file_util::read_user_credential_with_legacy(user_id, &user.username).map_err(|_| ())) - .and_then(|value| value.ok_or(())), + LocalRequest::RemoveUser { user_id } => { + let user = match user_manager::get_user(user_id) { + Some(user) => user, + None => return ResponseResult::Error(IpcErrorCode::NotFound), }; - let Ok(contents) = contents else { return ResponseResult::Error(IpcErrorCode::Unauthorized); }; - match omikron_connector::user_ops::complete_delete_user_with_tu(self.services.omikron.as_ref(), &contents, user_id).await { - Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged { message: format!("Deleted Tensamin account {user_id}") }), - Err(error) => { - log!("Credential deletion failed for {user_id}: {error:?}"); - ResponseResult::Error(IpcErrorCode::Unauthorized) - } - } - } - LocalRequest::RemoveUser { .. } => ResponseResult::Error(IpcErrorCode::InvalidRequest), - LocalRequest::ReleaseUser { user_id } => { - if user_manager::get_user(user_id).is_none() { - return ResponseResult::Error(IpcErrorCode::NotFound); - } - let request = CommunicationValue::new(CommunicationType::ReleaseUserFromIota) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); - match self.services.omikron.await_response(&request, Duration::from_secs(20)).await { - Ok(response) if response.is_type(CommunicationType::Success) => match user_manager::release_user(user_id) { - Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged { - message: format!("Released user {user_id}; hosted data was retained"), - }), - Err(error) => { - log!("Remote release succeeded but local cleanup failed for {user_id}: {error}"); - ResponseResult::Error(IpcErrorCode::StorageFailure) - } - }, - Ok(response) if response.is_type(CommunicationType::ErrorNotAuthenticated) => ResponseResult::Error(IpcErrorCode::Unauthorized), - Ok(_) => ResponseResult::Error(IpcErrorCode::Conflict), - Err(omikron_connector::OmikronError::Timeout(_)) => ResponseResult::Error(IpcErrorCode::Timeout), - Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable), + let message = CommunicationValue::new(CommunicationType::DeleteUser) + .with_sender(user.user_id as u64); + if let Err(_e) = self.services.omikron.send_message(&message).await { + return ResponseResult::Error(IpcErrorCode::OmikronUnavailable); } + user_manager::remove_user(user.user_id); + ResponseResult::Ok(ResponsePayload::UserRemoved { user_id }) } LocalRequest::ReconnectOmikron => match self.services.omikron.reconnect().await { Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged { @@ -293,22 +242,23 @@ impl CommandRouter { ResponseResult::Ok(ResponsePayload::Components(components)) } LocalRequest::GetUser { user_id } => match user_manager::get_user(user_id) { - Some(user) => { - let credential_present = iota_util::file_util::read_user_credential_with_legacy(user_id, &user.username).ok().flatten().is_some(); - ResponseResult::Ok(ResponsePayload::UserDetail(UserDetailResponse { + Some(user) => ResponseResult::Ok(ResponsePayload::UserDetail(UserDetailResponse { user_id: user.user_id, username: user.username, display_name: user.display_name, created_at: user.created_at, trusted_apps: user.trusted_apps.keys().cloned().collect(), - state: iota_ipc::LocalUserState::Managed, - data_present: user_manager::get_residency().iter().find(|entry| entry.user_id == user_id).is_none_or(|entry| entry.data_present), - credential_present, - })) - }, + })), None => ResponseResult::Error(IpcErrorCode::NotFound), }, - LocalRequest::ImportUser { .. } => ResponseResult::Error(IpcErrorCode::InvalidRequest), + LocalRequest::ImportUser { username } => { + match user_manager::load_from_tu(&username).await { + Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged { + message: format!("Imported user {username}"), + }), + Err(()) => ResponseResult::Error(IpcErrorCode::StorageFailure), + } + } LocalRequest::GetLogs { limit } => { let entries = if let Ok(buf) = self.log_buffer.lock() { buf.recent(limit) diff --git a/iota-daemon/src/main.rs b/iota-daemon/src/main.rs index 76eeaf0..47a0a51 100644 --- a/iota-daemon/src/main.rs +++ b/iota-daemon/src/main.rs @@ -156,7 +156,6 @@ async fn main() -> ExitCode { } }; let omikron_health = omikron.clone(); - let omikron_reconcile = omikron.clone(); let services = DaemonServices::new(omikron); let health_runtime = runtime.clone(); runtime @@ -210,22 +209,6 @@ async fn main() -> ExitCode { Ok(()) }) .await; - runtime - .tasks - .spawn_tracked("user-lifecycle-reconciliation", async move { - let mut states = omikron_reconcile.connection_state(); - loop { - if matches!(*states.borrow(), omikron_connector::omikron_connection::ConnectionState::Connected { .. }) { - omikron_connector::user_ops::reconcile_managed_users(omikron_reconcile.as_ref()).await; - } - tokio::select! { - changed = states.changed() => if changed.is_err() { break }, - _ = tokio::time::sleep(Duration::from_secs(30)) => {}, - } - } - Ok(()) - }) - .await; let ipc_server = match IpcServer::bind( socket.clone(), runtime.clone(), diff --git a/iota-ipc/src/lib.rs b/iota-ipc/src/lib.rs index bd3d102..0ff6284 100644 --- a/iota-ipc/src/lib.rs +++ b/iota-ipc/src/lib.rs @@ -8,7 +8,7 @@ pub use protocol::{ ExitIntent, HealthStatus, HelloAck, IpcErrorCode, LifecycleEvent, LifecyclePhase, LocalRequest, LogEntriesResponse, LogEntry, MetricSample, OmikronStatusResponse, RequestEnvelope, ResponseEnvelope, ResponsePayload, ResponseResult, StartupPhase, StateSnapshot, StatusResponse, - SecretString, SupervisorKind, TaskSummary, UpdateStatusResponse, UserDetailResponse, UserSummary, LocalUserState, + SupervisorKind, TaskSummary, UpdateStatusResponse, UserDetailResponse, UserSummary, }; pub use transport::{read_msg, write_msg}; diff --git a/iota-ipc/src/protocol.rs b/iota-ipc/src/protocol.rs index 2e2be1a..d31bee4 100644 --- a/iota-ipc/src/protocol.rs +++ b/iota-ipc/src/protocol.rs @@ -1,17 +1,5 @@ use serde::{Deserialize, Serialize}; -/// IPC credentials are supplied by the interactive CLI, never a daemon-side -/// path lookup. Debug is deliberately redacted because command routing logs -/// the request value. -#[derive(Clone, Deserialize, Serialize)] -pub struct SecretString(pub String); - -impl std::fmt::Debug for SecretString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("") - } -} - // --------------------------------------------------------------------------- // Client → Daemon // --------------------------------------------------------------------------- @@ -48,21 +36,6 @@ pub enum LocalRequest { CreateUser { username: String, }, - AttachUserFromTu { - credential: SecretString, - }, - PurgeUserData { - user_id: i64, - }, - ReleaseUser { - user_id: i64, - }, - CompleteDeleteUser { - user_id: i64, - credential: Option, - }, - /// Retained only to return an actionable deprecation error to old IPC - /// clients. It must never select lifecycle semantics implicitly. RemoveUser { user_id: i64, }, @@ -164,9 +137,7 @@ pub enum ResponsePayload { Tasks(Vec), Users(Vec), UserCreated { user_id: i64, username: String }, - /// Retained only for wire compatibility. New lifecycle code never emits it. UserRemoved { user_id: i64 }, - UserDataPurged { user_id: i64 }, Acknowledged { message: String }, DaemonStatus(DaemonStatusResponse), Config(ConfigResponse), @@ -203,9 +174,6 @@ pub struct UserDetailResponse { pub display_name: Option, pub created_at: i64, pub trusted_apps: Vec, - pub state: LocalUserState, - pub data_present: bool, - pub credential_present: bool, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -240,16 +208,6 @@ pub struct TaskSummary { pub struct UserSummary { pub user_id: i64, pub username: String, - pub state: LocalUserState, - pub data_present: bool, - pub credential_present: bool, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum LocalUserState { - Managed, - Released, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/iota-storage/src/users/user_manager.rs b/iota-storage/src/users/user_manager.rs index ccd3c9e..af842e5 100644 --- a/iota-storage/src/users/user_manager.rs +++ b/iota-storage/src/users/user_manager.rs @@ -1,26 +1,10 @@ use crate::users::user_profile::UserProfile; use crate::util::db; -use iota_util::file_util::{delete_user_directory, load_file, remove_user_credential, save_file}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use iota_util::crypto_helper::{self, hex_hash, keyring_from_base64, public_key_bundle_to_base64}; +use iota_util::file_util::{load_file, save_file}; +use rand_core::{OsRng, RngCore}; use rusqlite::params; -use std::time::{SystemTime, UNIX_EPOCH}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum LocalUserState { - Managed, - Released, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct UserResidency { - pub user_id: i64, - pub username: String, - pub state: LocalUserState, - pub data_present: bool, -} - -fn now_millis() -> i64 { - SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as i64 -} pub fn add_user(user: UserProfile) { if let Err(e) = try_add_user(user) { @@ -61,12 +45,6 @@ pub fn try_add_user(user: UserProfile) -> Result<(), crate::storage_error::Stora params![user.user_id, app_id, app_secret], )?; } - conn.execute( - r#"INSERT INTO user_residency (user_id, username, lifecycle_state, data_state, updated_at) - VALUES (?1, ?2, 'managed', COALESCE((SELECT data_state FROM user_residency WHERE user_id = ?1), 'present'), ?3) - ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, lifecycle_state = 'managed', updated_at = excluded.updated_at"#, - params![user.user_id, user.username, now_millis()], - )?; Ok(()) }) } @@ -227,92 +205,42 @@ pub fn remove_user(user_id: i64) { } } -/// Remove only local management authority. Hosted content is intentionally -/// retained and is indexed as released for a later purge operation. -pub fn release_user(user_id: i64) -> Result<(), crate::storage_error::StorageError> { - let username = get_user(user_id).map(|user| user.username).ok_or_else(|| { - crate::storage_error::StorageError::Other("managed user was not found".into()) - })?; - db::with_db(|conn| { - let tx = conn.unchecked_transaction()?; - tx.execute("DELETE FROM trusted_apps WHERE user_id = ?1", params![user_id])?; - tx.execute("DELETE FROM users WHERE user_id = ?1", params![user_id])?; - tx.execute( - r#"INSERT INTO user_residency (user_id, username, lifecycle_state, data_state, updated_at) - VALUES (?1, ?2, 'released', COALESCE((SELECT data_state FROM user_residency WHERE user_id = ?1), 'present'), ?3) - ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, lifecycle_state = 'released', updated_at = excluded.updated_at"#, - params![user_id, username, now_millis()], - )?; - tx.commit()?; - Ok(()) - })?; - remove_user_credential(user_id).map_err(|error| crate::storage_error::StorageError::Other(error.to_string())) -} - -/// Authoritative hosted-data erasure used by local purge and future Omega -/// erasure delivery. Management metadata and credentials are left intact. -pub fn purge_user_data(user_id: i64) -> Result<(), crate::storage_error::StorageError> { - db::with_db(|conn| { - let tx = conn.unchecked_transaction()?; - tx.execute("DELETE FROM message_edits WHERE message_id IN (SELECT id FROM messages WHERE storage_owner = ?1)", params![user_id])?; - tx.execute("DELETE FROM reactions WHERE message_id IN (SELECT id FROM messages WHERE storage_owner = ?1)", params![user_id])?; - tx.execute("DELETE FROM messages WHERE storage_owner = ?1", params![user_id])?; - tx.execute("DELETE FROM contacts WHERE storage_owner = ?1", params![user_id])?; - tx.execute("DELETE FROM communities WHERE storage_owner = ?1", params![user_id])?; - tx.execute("DELETE FROM settings WHERE user_id = ?1", params![user_id])?; - tx.execute("DELETE FROM sync_events WHERE user_id = ?1", params![user_id])?; - tx.execute("DELETE FROM sync_heads WHERE user_id = ?1", params![user_id])?; - tx.execute("DELETE FROM client_sync_state WHERE user_id = ?1", params![user_id])?; - tx.execute("DELETE FROM trusted_apps WHERE user_id = ?1", params![user_id])?; - tx.execute( - "UPDATE user_residency SET data_state = 'empty', updated_at = ?2 WHERE user_id = ?1", - params![user_id, now_millis()], - )?; - tx.commit()?; - Ok(()) - })?; - crate::util::e2ee_storage::purge_user(user_id) - .map_err(crate::storage_error::StorageError::Other)?; - delete_user_directory(user_id).map_err(|error| crate::storage_error::StorageError::Other(error.to_string())) -} - -/// Complete local erasure is idempotent and is the target for a durable -/// Omega-hosted erasure request after account deletion. -pub fn erase_user_locally(user_id: i64) -> Result<(), crate::storage_error::StorageError> { - purge_user_data(user_id)?; - db::with_db(|conn| { - conn.execute("DELETE FROM trusted_apps WHERE user_id = ?1", params![user_id])?; - conn.execute("DELETE FROM users WHERE user_id = ?1", params![user_id])?; - conn.execute("DELETE FROM user_residency WHERE user_id = ?1", params![user_id])?; - Ok(()) - })?; - remove_user_credential(user_id).map_err(|error| crate::storage_error::StorageError::Other(error.to_string())) -} - -pub fn get_residency() -> Vec { - db::with_db(|conn| { - let mut stmt = conn.prepare("SELECT user_id, username, lifecycle_state, data_state FROM user_residency ORDER BY username")?; - let rows = stmt.query_map([], |row| { - let lifecycle: String = row.get(2)?; - Ok(UserResidency { - user_id: row.get(0)?, username: row.get(1)?, - state: if lifecycle == "managed" { LocalUserState::Managed } else { LocalUserState::Released }, - data_present: row.get::<_, String>(3)? == "present", - }) - })?; - rows.collect::, _>>().map_err(Into::into) - }).unwrap_or_default() -} - pub fn clear() { if let Err(e) = db::with_db(|conn| { - conn.execute_batch("DELETE FROM trusted_apps; DELETE FROM users; DELETE FROM user_residency;")?; + conn.execute_batch("DELETE FROM trusted_apps; DELETE FROM users;")?; Ok(()) }) { eprintln!("Failed to clear users: {}", e); } } +#[allow(dead_code)] +pub async fn load_from_tu(username: &str) -> Result<(), ()> { + let file_content = load_file("", &format!("{}.tu", username)); + let segments = file_content.split("::").collect::>(); + let (uuid_str, _omega_host) = segments[0].split_once('@').unwrap_or((segments[0], "")); + let uuid = uuid_str.parse::().unwrap_or(0); + let b64_private_key = segments[1]; + + let keyring = keyring_from_base64(b64_private_key).unwrap(); + let pub_key_bundle = keyring.public_key_bundle(); + let keyring_b64 = crypto_helper::keyring_to_base64(&keyring); + + let mut bytes = [0u8; 192]; + OsRng.fill_bytes(&mut bytes); + let reset_token = STANDARD.encode(&bytes); + + let user_profile = UserProfile::new( + uuid, + username.to_string(), + Some(username.to_string()), + public_key_bundle_to_base64(&pub_key_bundle), + hex_hash(&keyring_b64), + reset_token, + ); + add_user(user_profile); + Ok(()) +} pub fn save_users() { // No-op: users are auto-saved via SQLite. diff --git a/iota-storage/src/users/user_profile.rs b/iota-storage/src/users/user_profile.rs index 5f2abd5..2ecb9cc 100644 --- a/iota-storage/src/users/user_profile.rs +++ b/iota-storage/src/users/user_profile.rs @@ -1,7 +1,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use base64::{Engine as _, engine::general_purpose}; -use iota_util::file_util::{read_user_credential_with_legacy, used_dir_space}; +use iota_util::file_util::{has_file, load_file, used_dir_space}; use json::{JsonValue, object}; use rand::Rng; use rand::rngs::OsRng; @@ -55,11 +55,9 @@ impl UserProfile { if let Some(d) = &self.display_name { obj["display_name"] = d.clone().into(); } - // Frontend consumers must never receive private credential material. - obj["has_tu"] = read_user_credential_with_legacy(self.user_id, &self.username) - .map(|credential| credential.is_some()) - .unwrap_or(false) - .into(); + if has_file("", &format!("{}.tu", self.username.clone())) { + obj["tu"] = load_file("", &format!("{}.tu", self.username.clone())).into(); + } obj } diff --git a/iota-storage/src/util/chat_files.rs b/iota-storage/src/util/chat_files.rs index 024c302..da05aad 100644 --- a/iota-storage/src/util/chat_files.rs +++ b/iota-storage/src/util/chat_files.rs @@ -644,62 +644,6 @@ pub fn get_messages( } } -pub fn get_message( - storage_owner: i64, - message_time: i64, - external_user: Option, -) -> Result, StorageError> { - db::with_db(|conn| { - let mut stmt = conn.prepare( - r#" - SELECT id, message_time, content, sent_by_self, message_state, height, - reply_to, edited_count, external_user - FROM messages - WHERE storage_owner = ?1 - AND message_time = ?2 - AND deleted_by_external = 0 - AND (?3 IS NULL OR external_user = ?3) - ORDER BY id DESC - "#, - )?; - - let rows = stmt.query_map(params![storage_owner, message_time, external_user], |row| { - Ok(StoredMessage { - id: row.get(0)?, - message_time: row.get(1)?, - content: row.get(2)?, - sent_by_self: row.get::<_, i64>(3)? != 0, - message_state: row.get(4)?, - height: row.get(5).unwrap_or(0), - reply_to: row.get(6).ok().flatten(), - edited: row.get::<_, i64>(7).unwrap_or(0) > 0, - external_user: row.get(8)?, - reactions: Vec::new(), - }) - })?; - - let messages: Vec = rows.collect::>()?; - if messages.is_empty() { - return Ok(None); - } - if external_user.is_none() - && messages - .iter() - .map(|message| message.external_user) - .collect::>() - .len() - > 1 - { - return Ok(None); - } - - let mut message = messages.into_iter().next().expect("checked non-empty"); - let reaction_map = load_reactions(conn, &[message.id]); - message.reactions = reaction_map.get(&message.id).cloned().unwrap_or_default(); - Ok(Some(message)) - }) -} - pub fn get_messages_by_ids(storage_owner: i64, ids: &[i64]) -> Vec { if ids.is_empty() { return Vec::new(); diff --git a/iota-storage/src/util/db.rs b/iota-storage/src/util/db.rs index 82968e7..1a4ca7e 100644 --- a/iota-storage/src/util/db.rs +++ b/iota-storage/src/util/db.rs @@ -251,21 +251,6 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> { )?; } - if current_version < 7 { - conn.execute_batch( - r#" - CREATE TABLE IF NOT EXISTS user_residency ( - user_id INTEGER PRIMARY KEY, - username TEXT NOT NULL, - lifecycle_state TEXT NOT NULL CHECK (lifecycle_state IN ('managed', 'released')), - data_state TEXT NOT NULL CHECK (data_state IN ('present', 'empty')), - updated_at INTEGER NOT NULL - ); - PRAGMA user_version = 7; - "#, - )?; - } - Ok(()) } @@ -335,7 +320,7 @@ mod tests { run_migrations_on_connection(&conn)?; let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?; - assert_eq!(version, 7); + assert_eq!(version, 6); for column in ["height", "reply_to", "edited_count", "deleted_by_external"] { let mut statement = conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?; @@ -352,8 +337,8 @@ mod tests { run_migrations_on_connection(&conn)?; run_migrations_on_connection(&conn)?; let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?; - assert_eq!(version, 7); - for table in ["sync_heads", "sync_events", "client_sync_state", "user_residency"] { + assert_eq!(version, 6); + for table in ["sync_heads", "sync_events", "client_sync_state"] { let exists: i64 = conn.query_row( "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1", [table], diff --git a/iota-storage/src/util/e2ee_storage.rs b/iota-storage/src/util/e2ee_storage.rs index 6b13018..9d628d4 100644 --- a/iota-storage/src/util/e2ee_storage.rs +++ b/iota-storage/src/util/e2ee_storage.rs @@ -188,23 +188,6 @@ pub fn delete_pending_chat_secret_forward( }) } -/// Erase every E2EE record owned by, or queued for, a user. The operation is -/// intentionally idempotent so it can be retried after an interrupted remote -/// erasure request. -pub fn purge_user(user_id: i64) -> Result<(), StorageError> { - let user_id = user_id.to_string(); - db::with_conn(&E2EE_DB, |conn| { - let tx = conn.unchecked_transaction()?; - tx.execute("DELETE FROM chat_secrets WHERE user_id = ?1", params![user_id])?; - tx.execute( - "DELETE FROM pending_chat_secret_forwards WHERE recipient_user_id = ?1 OR sender_user_id = ?1", - params![user_id], - )?; - tx.commit()?; - Ok(()) - }) -} - pub fn get_chat_secret(query: ChatSecretQuery) -> Result, StorageError> { if query.user_id.is_empty() || query.chat_id.is_empty() { return Ok(None); diff --git a/iota-util/src/file_util.rs b/iota-util/src/file_util.rs index 8412b10..1e1cad4 100755 --- a/iota-util/src/file_util.rs +++ b/iota-util/src/file_util.rs @@ -33,70 +33,11 @@ fn delete_dir_recursive(directory: &Path) -> bool { } #[allow(dead_code)] -pub fn delete_user_directory(user_id: i64) -> io::Result<()> { +pub fn delete_user_directory(user_id: i64) { let user_dir = Path::new(&get_directory()) .join("users") .join(user_id.to_string()); - if !user_dir.exists() { - return Ok(()); - } - fs::remove_dir_all(user_dir) -} - -pub fn credential_path(user_id: i64) -> PathBuf { - storage_directory().join("credentials").join(format!("{user_id}.tu")) -} - -pub fn read_user_credential(user_id: i64) -> io::Result> { - let path = credential_path(user_id); - match fs::read_to_string(path) { - Ok(value) => Ok(Some(value)), - Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None), - Err(error) => Err(error), - } -} - -/// Resolve a credential by immutable account id. A valid legacy -/// `.tu` is migrated atomically the first time it is encountered. -pub fn read_user_credential_with_legacy(user_id: i64, username: &str) -> io::Result> { - if let Some(credential) = read_user_credential(user_id)? { - return Ok(Some(credential)); - } - let legacy = storage_file("", format!("{username}.tu"))?; - let credential = match fs::read_to_string(&legacy) { - Ok(value) => value, - Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None), - Err(error) => return Err(error), - }; - let parsed = crate::tu::TuCredential::parse(&credential) - .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; - if parsed.user_id != user_id { - return Err(io::Error::new(io::ErrorKind::InvalidData, "legacy credential user id mismatch")); - } - write_user_credential(user_id, &parsed.to_canonical_string())?; - fs::remove_file(legacy)?; - Ok(Some(parsed.to_canonical_string())) -} - -pub fn write_user_credential(user_id: i64, credential: &str) -> io::Result<()> { - let path = credential_path(user_id); - let parent = path.parent().expect("credential path has parent"); - fs::create_dir_all(parent)?; - let temporary = parent.join(format!(".{user_id}.tu.tmp")); - fs::write(&temporary, credential)?; - if let Err(error) = fs::rename(&temporary, &path) { - let _ = fs::remove_file(&temporary); - return Err(error); - } - Ok(()) -} - -pub fn remove_user_credential(user_id: i64) -> io::Result<()> { - match fs::remove_file(credential_path(user_id)) { - Ok(()) => Ok(()), - Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()), - Err(error) => Err(error), - } + let _ = delete_dir_recursive(&user_dir); } pub fn load_file_buf(path: &str, name: &str) -> io::Result> { diff --git a/iota-util/src/lib.rs b/iota-util/src/lib.rs index 5d32aef..2277b2b 100644 --- a/iota-util/src/lib.rs +++ b/iota-util/src/lib.rs @@ -1,4 +1,3 @@ pub mod crypto_helper; pub mod crypto_util; pub mod file_util; -pub mod tu; diff --git a/iota-util/src/tu.rs b/iota-util/src/tu.rs deleted file mode 100644 index e1a8334..0000000 --- a/iota-util/src/tu.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! Strict parsing and storage-independent handling of user credentials. -//! -//! A `.tu` file is deliberately identified by the account id embedded in its -//! contents. Its filename is presentation data owned by the CLI, never an -//! account authority. - -use crate::crypto_helper::{keyring_from_base64, keyring_to_base64}; -use mtp::crypto::{Keyring, PublicKeyBundle}; -use std::fmt; - -pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TuError { - InvalidFormat, - InvalidUserId, - InvalidKeyring, -} - -impl fmt::Display for TuError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::InvalidFormat => "invalid .tu credential format", - Self::InvalidUserId => "invalid .tu user id", - Self::InvalidKeyring => "invalid .tu keyring", - }) - } -} - -impl std::error::Error for TuError {} - -pub struct TuCredential { - pub user_id: i64, - pub omega_host: String, - pub keyring: Keyring, -} - -impl fmt::Debug for TuCredential { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TuCredential") - .field("user_id", &self.user_id) - .field("omega_host", &self.omega_host) - .field("keyring", &"") - .finish() - } -} - -impl TuCredential { - pub fn parse(input: &str) -> Result { - let (identity, encoded_keyring) = input.trim().split_once("::").ok_or(TuError::InvalidFormat)?; - if encoded_keyring.is_empty() || encoded_keyring.contains("::") { - return Err(TuError::InvalidFormat); - } - let (user_id, omega_host) = identity.split_once('@').ok_or(TuError::InvalidFormat)?; - if omega_host.trim().is_empty() || omega_host.contains('@') { - return Err(TuError::InvalidFormat); - } - let user_id = user_id.parse::().map_err(|_| TuError::InvalidUserId)?; - if !(1..=MAX_PROTOCOL_ID).contains(&user_id) { - return Err(TuError::InvalidUserId); - } - let keyring = keyring_from_base64(encoded_keyring).ok_or(TuError::InvalidKeyring)?; - Ok(Self { user_id, omega_host: omega_host.trim().to_owned(), keyring }) - } - - pub fn public_key_bundle(&self) -> PublicKeyBundle { - self.keyring.public_key_bundle() - } - - pub fn to_canonical_string(&self) -> String { - format!("{}@{}::{}", self.user_id, self.omega_host, keyring_to_base64(&self.keyring)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::crypto_helper::generate_keyring; - - #[test] - fn round_trip_is_canonical() { - let credential = TuCredential { user_id: 42, omega_host: "omega.example:443".into(), keyring: generate_keyring() }; - let parsed = TuCredential::parse(&credential.to_canonical_string()).unwrap(); - assert_eq!(parsed.user_id, 42); - assert_eq!(parsed.omega_host, "omega.example:443"); - assert_eq!(parsed.to_canonical_string(), credential.to_canonical_string()); - } - - #[test] - fn rejects_malformed_credentials() { - for value in ["", "1@omega", "@omega::abc", "0@omega::abc", "281474976710656@omega::abc", "1@::abc", "1@omega::abc::def"] { - assert!(TuCredential::parse(value).is_err(), "{value}"); - } - } -} diff --git a/iota/Cargo.toml b/iota/Cargo.toml index b05cfe8..3c38d65 100644 --- a/iota/Cargo.toml +++ b/iota/Cargo.toml @@ -11,7 +11,6 @@ iota-core = { path = "../iota-core" } iota-process-manager = { path = "../iota-process-manager" } 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" diff --git a/iota/src/cli_args.rs b/iota/src/cli_args.rs index b1447a0..48c8ead 100644 --- a/iota/src/cli_args.rs +++ b/iota/src/cli_args.rs @@ -1,7 +1,6 @@ use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum, error::ErrorKind}; use iota_cli::theme::{CliOutputFormat, ThemeName, UiConfig}; use iota_terms::TermsType; -use std::path::PathBuf; #[derive(Debug)] pub struct CliInvocation { @@ -116,33 +115,15 @@ enum UsersAction { user_id: i64, }, Add { - username: Option, - #[arg(long, value_name = "PATH")] - tu: Option, + username: String, }, - Release { + Remove { user_id: i64, #[arg(long)] yes: bool, }, - Data { - #[command(subcommand)] - action: UserDataAction, - }, - CompleteDelete { - user_id: i64, - #[arg(long, value_name = "PATH")] - tu: Option, - #[arg(long)] - yes: bool, - }, -} -#[derive(Subcommand, Debug)] -enum UserDataAction { - Purge { - user_id: i64, - #[arg(long)] - yes: bool, + Import { + username: String, }, } #[derive(Args, Debug)] @@ -283,21 +264,14 @@ pub enum Command { user_id: i64, }, UsersAdd { - username: Option, - tu: Option, + username: String, }, - UsersRelease { + UsersRemove { user_id: i64, confirmed: bool, }, - UsersPurgeData { - user_id: i64, - confirmed: bool, - }, - UsersCompleteDelete { - user_id: i64, - tu: Option, - confirmed: bool, + UsersImport { + username: String, }, OmikronReconnect, IdentityRotate { @@ -393,29 +367,12 @@ impl CliInvocation { Some(CliCommand::Users(users)) => match users.action { UsersAction::List => Command::UsersList, UsersAction::Show { user_id } => Command::UsersShow { user_id }, - UsersAction::Add { username, tu } => { - if username.is_some() == tu.is_some() { - return Err( - "users add requires exactly one of or --tu ".into(), - ); - } - Command::UsersAdd { username, tu } - } - UsersAction::Release { user_id, yes } => Command::UsersRelease { + UsersAction::Add { username } => Command::UsersAdd { username }, + UsersAction::Remove { user_id, yes } => Command::UsersRemove { user_id, confirmed: resolve_confirmed(yes), }, - UsersAction::Data { - action: UserDataAction::Purge { user_id, yes }, - } => Command::UsersPurgeData { - user_id, - confirmed: resolve_confirmed(yes), - }, - UsersAction::CompleteDelete { user_id, tu, yes } => Command::UsersCompleteDelete { - user_id, - tu, - confirmed: resolve_confirmed(yes), - }, + UsersAction::Import { username } => Command::UsersImport { username }, }, Some(CliCommand::Omikron(omikron)) => match omikron.action { OmikronAction::Reconnect => Command::OmikronReconnect, @@ -488,7 +445,6 @@ impl CliInvocation { } } - #[allow(unused)] pub fn help_text() -> String { Cli::command().render_long_help().to_string() } @@ -572,7 +528,7 @@ mod tests { #[test] fn command_schema_drives_help_and_completion_paths() { let paths = CliInvocation::command_paths(); - assert!(paths.contains(&"users release".to_owned())); + assert!(paths.contains(&"users remove".to_owned())); assert!(paths.contains(&"daemon install".to_owned())); let help = CliInvocation::help_text(); assert!(help.contains("users")); @@ -602,7 +558,12 @@ mod tests { #[test] fn parses_unconfirmed_destructive_commands_explicitly() { let invocation = CliInvocation::parse(["daemon".into(), "stop".into()]).unwrap(); - assert_eq!(invocation.command, Command::DaemonStop { confirmed: true }); + assert_eq!( + invocation.command, + Command::DaemonStop { + confirmed: true + } + ); } #[test] @@ -612,8 +573,7 @@ mod tests { assert_eq!( invocation.command, Command::UsersAdd { - username: Some("alice".into()), - tu: None, + username: "alice".into() } ); } @@ -637,24 +597,26 @@ mod tests { } #[test] - fn rejects_ambiguous_users_remove() { - let error = - CliInvocation::parse(["users".into(), "remove".into(), "42".into()]).unwrap_err(); - assert!(error.contains("remove")); + fn parses_users_remove_without_confirmation() { + let invocation = + CliInvocation::parse(["users".into(), "remove".into(), "42".into()]).unwrap(); + assert_eq!( + invocation.command, + Command::UsersRemove { + user_id: 42, + confirmed: true, + } + ); } #[test] - fn parses_users_release_with_confirmation() { - let invocation = CliInvocation::parse([ - "users".into(), - "release".into(), - "42".into(), - "--yes".into(), - ]) - .unwrap(); + fn parses_users_remove_with_confirmation() { + let invocation = + CliInvocation::parse(["users".into(), "remove".into(), "42".into(), "--yes".into()]) + .unwrap(); assert_eq!( invocation.command, - Command::UsersRelease { + Command::UsersRemove { user_id: 42, confirmed: true, } diff --git a/iota/src/main.rs b/iota/src/main.rs index e05b63f..192f1c8 100644 --- a/iota/src/main.rs +++ b/iota/src/main.rs @@ -403,9 +403,7 @@ fn print_help() { println!(" users list List all users"); println!(" users show Show user details"); println!(" users add Create a new user"); - println!(" users add --tu Add an existing account credential"); - println!(" users data purge Purge hosted data (requires --yes)"); - println!(" users release Release this Iota (requires --yes)"); + println!(" users remove Remove a user (requires --yes)"); println!(" omikron status Show Omikron connection status"); println!(" omikron reconnect Reconnect to Omikron"); println!(" identity rotate Rotate identity keys (requires --yes)"); @@ -453,7 +451,7 @@ fn print_help() { println!(" iota status Show daemon status"); println!(" iota users list --output=json List users in JSON format"); println!(" iota users add alice Create a user named 'alice'"); - println!(" iota users data purge 42 --yes Purge hosted data"); + println!(" iota users remove 42 --yes Remove user 42"); println!(" iota config get --output=yaml Show config in YAML format"); println!(" iota logs --limit 50 Show last 50 log entries"); println!(" iota completions bash Generate bash completions"); @@ -566,40 +564,12 @@ async fn run_command( Command::Tasks => LocalRequest::ListTasks, Command::UsersList => LocalRequest::ListUsers, Command::UsersShow { user_id } => LocalRequest::GetUser { user_id }, - Command::UsersAdd { username: Some(username), tu: None } => LocalRequest::CreateUser { username }, - Command::UsersAdd { username: None, tu: Some(path) } => { - let contents = std::fs::read_to_string(&path) - .map_err(|error| StartupError::InvalidCommand(format!("Cannot read {}: {error}", path.display())))?; - iota_util::tu::TuCredential::parse(&contents) - .map_err(|error| StartupError::InvalidCommand(format!("Invalid credential {}: {error}", path.display())))?; - LocalRequest::AttachUserFromTu { credential: iota_ipc::SecretString(contents) } - } - Command::UsersAdd { .. } => { - return Err(StartupError::InvalidCommand( - "users add requires exactly one of or --tu ".into(), - )); - } - Command::UsersRelease { + Command::UsersAdd { username } => LocalRequest::CreateUser { username }, + Command::UsersRemove { user_id, confirmed: true, - } => LocalRequest::ReleaseUser { user_id }, - Command::UsersPurgeData { user_id, confirmed: true } => LocalRequest::PurgeUserData { user_id }, - Command::UsersCompleteDelete { user_id, tu, confirmed: true } => { - let credential = match tu { - Some(path) => { - let contents = std::fs::read_to_string(&path) - .map_err(|error| StartupError::InvalidCommand(format!("Cannot read {}: {error}", path.display())))?; - let parsed = iota_util::tu::TuCredential::parse(&contents) - .map_err(|error| StartupError::InvalidCommand(format!("Invalid credential {}: {error}", path.display())))?; - if parsed.user_id != user_id { - return Err(StartupError::InvalidCommand("credential user ID does not match complete-delete target".into())); - } - Some(iota_ipc::SecretString(contents)) - } - None => None, - }; - LocalRequest::CompleteDeleteUser { user_id, credential } - } + } => LocalRequest::RemoveUser { user_id }, + Command::UsersImport { username } => LocalRequest::ImportUser { username }, Command::OmikronReconnect => LocalRequest::ReconnectOmikron, Command::IdentityRotate { confirmed: true } => LocalRequest::RotateIotaIdentity, Command::RegenerateKeys { confirmed: true } => LocalRequest::RotateIotaIdentity, @@ -618,11 +588,9 @@ async fn run_command( Command::Logs { limit } => LocalRequest::GetLogs { limit }, Command::UpdateCheck => LocalRequest::CheckUpdate, Command::CommunityList => LocalRequest::ListCommunities, - Command::UsersRelease { + Command::UsersRemove { confirmed: false, .. } - | Command::UsersPurgeData { confirmed: false, .. } - | Command::UsersCompleteDelete { confirmed: false, .. } | Command::IdentityRotate { confirmed: false } | Command::RegenerateKeys { confirmed: false } | Command::DaemonRestart { confirmed: false } @@ -721,9 +689,6 @@ async fn run_command( user_id ); } - ResponsePayload::UserDataPurged { user_id } => { - println!("{} hosted data for {}. Account remains managed by this Iota.", cli_color::success(&color, "Purged"), user_id); - } ResponsePayload::Acknowledged { message } => { println!("{}", message); } @@ -970,9 +935,6 @@ fn render_table(payload: &ResponsePayload) { ResponsePayload::UserRemoved { user_id } => { println!("Removed user {}", user_id); } - ResponsePayload::UserDataPurged { user_id } => { - println!("Purged hosted data for {}", user_id); - } ResponsePayload::Acknowledged { message } => { println!("{}", message); } diff --git a/omikron-connector/src/omikron_connection.rs b/omikron-connector/src/omikron_connection.rs index a555398..53da661 100755 --- a/omikron-connector/src/omikron_connection.rs +++ b/omikron-connector/src/omikron_connection.rs @@ -884,7 +884,6 @@ impl OmikronConnection { dispatch!(MessageReactionLive, handle_message_reaction_live); dispatch!(MessageDeleteLive, handle_message_delete_live); dispatch!(MessageOtherIota, handle_message_other_iota); - dispatch!(MessageGet, handle_message_get); dispatch!(MessagesGet, handle_messages_get); dispatch!(GetChats, handle_get_chats); dispatch!(AddConversation, handle_add_conversation); @@ -896,41 +895,12 @@ impl OmikronConnection { dispatch!(SettingsSave, handle_settings_save); dispatch!(SettingsLoad, handle_settings_load); dispatch!(SettingsList, handle_settings_list); - dispatch!(EraseHostedUserData, handle_erase_hosted_user_data); } // ------------------------------------------------------------------------- // Message Handlers // ------------------------------------------------------------------------- - /// Omega-authorized account cleanup. The storage operation is idempotent; - /// acknowledgement is therefore safe to retry after a reconnect. - async fn handle_erase_hosted_user_data(self: Arc, cv: &CommunicationValue) { - let Some(user_id) = cv - .get_data(DataType::UserId) - .as_signed_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - - if iota_storage::users::user_manager::erase_user_locally(user_id).is_err() { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInternal)) - .await; - return; - } - - let acknowledgement = CommunicationValue::new(CommunicationType::EraseHostedUserDataAck) - .with_id(cv.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); - let _ = self.send_message(&acknowledgement).await; - } - async fn handle_set_chat_secret(self: Arc, cv: &CommunicationValue) { let sender_id = cv.get_sender().to_string(); let recipients = match chat_secret_recipients(cv) { @@ -1477,24 +1447,6 @@ impl OmikronConnection { let height = cv.get_data(DataType::Height).as_number().unwrap_or(0) as i64; let reply_to = cv.get_data(DataType::ReplyId).as_number().map(|n| n as i64); - if let Some(reply_to) = reply_to { - match chat_files::get_message(sender_id as i64, reply_to, Some(receiver_id)) { - Ok(Some(_)) => {} - Ok(None) => { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorNotFound)) - .await; - return; - } - Err(_) => { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - } - } - } - let is_local = iota_storage::users::user_manager::get_user(receiver_id).is_some(); if is_local { @@ -1749,12 +1701,6 @@ impl OmikronConnection { .await; } - async fn handle_message_get(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_message_get(cv)) - .await; - } - async fn handle_get_chats(self: Arc, cv: &CommunicationValue) { let _ = self .send_message(&message_handlers::handle_get_chats(cv)) diff --git a/omikron-connector/src/user_ops.rs b/omikron-connector/src/user_ops.rs index 241fd46..fde043f 100644 --- a/omikron-connector/src/user_ops.rs +++ b/omikron-connector/src/user_ops.rs @@ -2,12 +2,9 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; use iota_logger::{PrintType, log, log_cv, log_t}; use iota_storage::users::user_manager::try_add_user; use iota_storage::users::user_profile::UserProfile; -use iota_storage::util::config_util::CONFIG; use iota_util::crypto_helper::{self, hex_hash, public_key_bundle_to_base64}; -use iota_util::file_util::write_user_credential; -use iota_util::tu::TuCredential; +use iota_util::file_util::try_save_file; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme}; use rand_core::{OsRng, RngCore}; use std::time::Duration; @@ -23,133 +20,6 @@ pub enum CreateUserError { LocalPersistence(String), } -#[derive(Debug)] -pub enum LifecycleUserError { - InvalidCredential(String), - OmegaHostMismatch, - RemoteRejected, - Transport(crate::OmikronError), - LocalPersistence(String), -} - -impl From for LifecycleUserError { - fn from(value: crate::OmikronError) -> Self { Self::Transport(value) } -} - -fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> Vec { - let mut payload = Vec::with_capacity(domain.len() + 24); - payload.extend_from_slice(domain); - payload.extend_from_slice(&user_id.to_be_bytes()); - payload.extend_from_slice(&iota_id.to_be_bytes()); - payload.extend_from_slice(&nonce.to_be_bytes()); - payload -} - -fn configured_iota_id() -> Result { - CONFIG.load().iota_id - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - .ok_or_else(|| LifecycleUserError::InvalidCredential("Iota identity is not registered".into())) -} - -fn sign_lifecycle_payload(credential: &TuCredential, payload: &[u8]) -> Result<(Vec, Vec), LifecycleUserError> { - let classical = Ed25519Signer::new(&credential.keyring.sig_cl_secret_key) - .map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))? - .sign(payload) - .map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))?; - let pq = MlDsaSigner::new(&credential.keyring.sig_pq_secret_key, &credential.keyring.sig_pq_public_key) - .map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))? - .sign(payload) - .map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))?; - Ok((classical, pq)) -} - -async fn inspect_credential_account( - connection: &dyn OmikronClient, - credential: &TuCredential, -) -> Result<(String, String), LifecycleUserError> { - if credential.omega_host != omega_discovery::omega_host() { - return Err(LifecycleUserError::OmegaHostMismatch); - } - let request = CommunicationValue::new(CommunicationType::GetUserData) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(credential.user_id.into())); - let response = connection.await_response(&request, Duration::from_secs(20)).await?; - if !response.is_type(CommunicationType::GetUserData) { - return Err(LifecycleUserError::RemoteRejected); - } - let username = response.get_data(DataType::Username).as_str().map(str::to_owned) - .ok_or(LifecycleUserError::RemoteRejected)?; - let public_key = response.get_data(DataType::PublicKey).as_str().map(str::to_owned) - .ok_or(LifecycleUserError::RemoteRejected)?; - if public_key != public_key_bundle_to_base64(&credential.public_key_bundle()) { - return Err(LifecycleUserError::RemoteRejected); - } - Ok((username, public_key)) -} - -async fn credential_proof( - connection: &dyn OmikronClient, - credential: &TuCredential, - begin: CommunicationType, - challenge: CommunicationType, - complete: CommunicationType, - domain: &[u8], -) -> Result<(), LifecycleUserError> { - let iota_id = configured_iota_id()?; - let begin_request = CommunicationValue::new(begin) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(credential.user_id.into())); - let challenge_response = connection.await_response(&begin_request, Duration::from_secs(20)).await?; - if !challenge_response.is_type(challenge) { - return Err(LifecycleUserError::RemoteRejected); - } - let nonce = challenge_response.get_data(DataType::ServerNonce).as_signed_number() - .and_then(|value| u64::try_from(value).ok()) - .ok_or(LifecycleUserError::RemoteRejected)?; - let (signature, pq_signature) = sign_lifecycle_payload(credential, &lifecycle_payload(domain, credential.user_id, iota_id, nonce))?; - let complete_request = CommunicationValue::new(complete) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(credential.user_id.into())) - .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())) - .add_typed_default(DataType::Signature, DataValue::Bytes(signature)) - .add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature)); - let response = connection.await_response(&complete_request, Duration::from_secs(20)).await?; - if response.is_type(CommunicationType::Success) { Ok(()) } else { Err(LifecycleUserError::RemoteRejected) } -} - -/// Attach or migrate an existing account. Local state is written only after -/// Omega has accepted the credential proof and changed its assignment. -pub async fn attach_user_from_tu(connection: &dyn OmikronClient, contents: &str) -> Result { - let credential = TuCredential::parse(contents).map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))?; - let (username, public_key) = inspect_credential_account(connection, &credential).await?; - credential_proof(connection, &credential, CommunicationType::AttachUserBegin, CommunicationType::AttachUserChallenge, CommunicationType::AttachUserComplete, b"tensamin:user-attach:v1\0").await?; - let profile = UserProfile::new(credential.user_id, username, None, public_key, hex_hash(contents), String::new()); - write_user_credential(profile.user_id, &credential.to_canonical_string()) - .map_err(|error| LifecycleUserError::LocalPersistence(error.to_string()))?; - try_add_user(profile.clone()).map_err(|error| LifecycleUserError::LocalPersistence(error.to_string()))?; - Ok(profile) -} - -pub async fn complete_delete_user_with_tu(connection: &dyn OmikronClient, contents: &str, expected_user_id: i64) -> Result<(), LifecycleUserError> { - let credential = TuCredential::parse(contents).map_err(|error| LifecycleUserError::InvalidCredential(error.to_string()))?; - if credential.user_id != expected_user_id { return Err(LifecycleUserError::InvalidCredential("credential user ID does not match deletion target".into())); } - inspect_credential_account(connection, &credential).await?; - credential_proof(connection, &credential, CommunicationType::DeleteUserCredentialBegin, CommunicationType::DeleteUserCredentialChallenge, CommunicationType::DeleteUserCredentialComplete, b"tensamin:user-delete:v1\0").await -} - -/// Repair local management state after a release or migration committed in -/// Omega but local cleanup was interrupted. Hosted data is retained. -pub async fn reconcile_managed_users(connection: &dyn OmikronClient) { - let Ok(local_iota_id) = configured_iota_id() else { return; }; - for user in iota_storage::users::user_manager::get_users() { - let request = CommunicationValue::new(CommunicationType::GetUserData) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user.user_id.into())); - let Ok(response) = connection.await_response(&request, Duration::from_secs(10)).await else { continue; }; - let remote_iota_id = response.get_data(DataType::IotaId).as_signed_number().and_then(|value| i64::try_from(value).ok()); - if remote_iota_id != Some(local_iota_id) { - let _ = iota_storage::users::user_manager::release_user(user.user_id); - } - } -} - fn valid_username(username: &str) -> bool { !username.is_empty() && username.chars().count() <= 15 @@ -266,9 +136,15 @@ pub async fn create_user( } } log!("Created User"); - write_user_credential( - user_id, - &format!("{}@{}::{}", user_id, omega_discovery::omega_host(), keyring_b64), + try_save_file( + "", + &format!("{}.tu", username), + &format!( + "{}@{}::{}", + user_id, + omega_discovery::omega_host(), + keyring_b64 + ), ) .map_err(|error| CreateUserError::LocalPersistence(error.to_string()))?;