Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6be977f3f | |||
|
7dc98ef29b |
|||
|
326ebf3b37 |
23 changed files with 1045 additions and 269 deletions
321
Cargo.lock
generated
321
Cargo.lock
generated
|
|
@ -44,16 +44,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "actix-http"
|
||||
version = "3.13.1"
|
||||
version = "3.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2"
|
||||
checksum = "53200bd1513e569e6e644181c922cec072c121a27db5b38d45e88e630c369366"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-tls",
|
||||
"actix-utils",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.13.1",
|
||||
"brotli",
|
||||
"bytes",
|
||||
|
|
@ -119,9 +119,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "actix-server"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502"
|
||||
checksum = "3716aae056e2f869b7b5cfd8a08fcf98890f8455bec61d69c7dff5d8576f9d2b"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
|
|
@ -129,7 +129,7 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-util",
|
||||
"mio",
|
||||
"socket2 0.5.10",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
|
@ -211,7 +211,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
|
|
@ -290,9 +290,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -320,9 +320,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -483,9 +483,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.3"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
|
||||
checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"untrusted 0.7.1",
|
||||
|
|
@ -494,9 +494,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
|
||||
checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
|
|
@ -511,6 +511,12 @@ 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"
|
||||
|
|
@ -651,9 +657,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
|
||||
checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
|
|
@ -780,7 +786,7 @@ dependencies = [
|
|||
"actix-web-actors",
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
|
|
@ -1089,6 +1095,22 @@ 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"
|
||||
|
|
@ -1102,9 +1124,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
|
|
@ -1112,26 +1134,26 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||
checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.119",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1150,9 +1172,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
|
|
@ -1173,7 +1195,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid 0.9.6",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -1184,6 +1205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
||||
dependencies = [
|
||||
"const-oid 0.10.2",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -1310,20 +1332,45 @@ 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",
|
||||
"ed25519",
|
||||
"curve25519-dalek 4.1.3",
|
||||
"ed25519 2.2.3",
|
||||
"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"
|
||||
|
|
@ -1397,12 +1444,6 @@ 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"
|
||||
|
|
@ -1433,6 +1474,12 @@ 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"
|
||||
|
|
@ -1446,9 +1493,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
||||
|
||||
[[package]]
|
||||
name = "finl_unicode"
|
||||
|
|
@ -1698,7 +1745,7 @@ dependencies = [
|
|||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
|
@ -1715,7 +1762,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"fastrand",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
|
@ -1756,7 +1803,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"h3",
|
||||
"h3-datagram",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -1805,10 +1852,10 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1 0.10.7",
|
||||
|
|
@ -1820,7 +1867,7 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||
dependencies = [
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1881,9 +1928,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
||||
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
|
|
@ -1896,7 +1943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1907,7 +1954,7 @@ checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
|
@ -1926,9 +1973,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
||||
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||
dependencies = [
|
||||
"ctutils",
|
||||
"typenum",
|
||||
|
|
@ -1945,7 +1992,7 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2 0.4.15",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
|
|
@ -1962,7 +2009,7 @@ version = "0.27.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
|
|
@ -1977,18 +2024,18 @@ version = "0.1.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
|
|
@ -2171,15 +2218,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
|
||||
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2194,6 +2241,7 @@ dependencies = [
|
|||
"iota-paths",
|
||||
"iota-process-manager",
|
||||
"iota-terms",
|
||||
"iota-util",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tokio",
|
||||
|
|
@ -2206,7 +2254,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
|
|
@ -2256,7 +2304,7 @@ dependencies = [
|
|||
"actix-web-actors",
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
|
|
@ -2448,7 +2496,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"aes-gcm",
|
||||
"arc-swap",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"iota-logger",
|
||||
|
|
@ -2493,8 +2541,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"ed25519-dalek",
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek 2.2.0",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"iota-logger",
|
||||
|
|
@ -2523,7 +2571,7 @@ dependencies = [
|
|||
name = "iota-util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"hex",
|
||||
"iota-paths",
|
||||
"mtp",
|
||||
|
|
@ -2537,9 +2585,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
|
|
@ -2688,9 +2736,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
|
||||
checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
|
|
@ -2744,9 +2792,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "line-clipping"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
|
||||
checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
]
|
||||
|
|
@ -2803,9 +2851,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
|
||||
checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
|
||||
dependencies = [
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
|
@ -2957,7 +3005,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"mtp-client",
|
||||
"mtp-codec",
|
||||
|
|
@ -2973,7 +3021,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-client"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
|
|
@ -2986,9 +3034,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.23.1",
|
||||
"byteorder",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
|
|
@ -2999,7 +3047,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"quinn",
|
||||
"rustls",
|
||||
|
|
@ -3010,11 +3058,11 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-crypto"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.23.1",
|
||||
"chacha20poly1305",
|
||||
"ed25519-dalek",
|
||||
"ed25519-dalek 3.0.0",
|
||||
"getrandom 0.4.3",
|
||||
"hkdf",
|
||||
"ml-dsa",
|
||||
|
|
@ -3024,7 +3072,7 @@ dependencies = [
|
|||
"rustls",
|
||||
"serde",
|
||||
"sha2 0.11.0",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -3032,24 +3080,24 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-files"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"mtp-crypto",
|
||||
"rand 0.10.2",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.19",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-host"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-transport",
|
||||
"rand 0.8.7",
|
||||
"rand 0.10.2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
|
|
@ -3058,7 +3106,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec",
|
||||
|
|
@ -3076,7 +3124,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-type-map"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
|
|
@ -3085,14 +3133,14 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-webserver"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"h3",
|
||||
"h3-quinn",
|
||||
"h3-webtransport",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
|
|
@ -3297,7 +3345,7 @@ name = "omikron-connector"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"dashmap",
|
||||
"hex",
|
||||
"iota-connection",
|
||||
|
|
@ -3405,7 +3453,7 @@ dependencies = [
|
|||
"actix-web-actors",
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
|
|
@ -3450,21 +3498,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
|
||||
checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"fast-srgb8",
|
||||
"libm",
|
||||
"palette_derive",
|
||||
"palette_math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette_derive"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
|
||||
checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be"
|
||||
dependencies = [
|
||||
"by_address",
|
||||
"proc-macro2",
|
||||
|
|
@ -3472,6 +3520,15 @@ 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"
|
||||
|
|
@ -3511,15 +3568,15 @@ version = "3.0.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
|
@ -3849,7 +3906,7 @@ dependencies = [
|
|||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -3890,7 +3947,7 @@ dependencies = [
|
|||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
|
@ -4128,9 +4185,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -4155,12 +4212,12 @@ version = "0.13.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"h2 0.4.15",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
|
|
@ -4558,7 +4615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
|
||||
dependencies = [
|
||||
"digest 0.11.3",
|
||||
"keccak 0.2.0",
|
||||
"keccak 0.2.1",
|
||||
"sponge-cursor",
|
||||
]
|
||||
|
||||
|
|
@ -4658,16 +4715,6 @@ 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"
|
||||
|
|
@ -4904,7 +4951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.13.1",
|
||||
"fancy-regex",
|
||||
"filedescriptor",
|
||||
|
|
@ -4981,9 +5028,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.54"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
|
||||
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"libc",
|
||||
|
|
@ -5048,20 +5095,20 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.1"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
|
||||
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5148,7 +5195,7 @@ dependencies = [
|
|||
"bitflags 2.13.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
|
|
@ -5215,7 +5262,7 @@ checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
|
|
@ -5390,7 +5437,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"log",
|
||||
|
|
@ -5483,7 +5530,7 @@ name = "web-server"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.4.2",
|
||||
"http 1.5.0",
|
||||
"iota-logger",
|
||||
"iota-util",
|
||||
"mtp",
|
||||
|
|
@ -5519,7 +5566,7 @@ dependencies = [
|
|||
"actix-web-actors",
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"dashmap",
|
||||
|
|
@ -5903,7 +5950,7 @@ dependencies = [
|
|||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"sha2 0.11.0",
|
||||
"socket2 0.6.5",
|
||||
"socket2",
|
||||
"thiserror 2.0.19",
|
||||
"time",
|
||||
"tokio",
|
||||
|
|
@ -5931,7 +5978,7 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"curve25519-dalek 4.1.3",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"zeroize",
|
||||
|
|
@ -6002,18 +6049,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.55"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
|
||||
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.55"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
|
||||
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -6123,9 +6170,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
|
||||
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
|
|
|
|||
|
|
@ -407,6 +407,12 @@ 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;
|
||||
|
|
|
|||
|
|
@ -483,6 +483,9 @@ 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(),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ 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)]
|
||||
|
|
@ -146,7 +149,9 @@ impl UsersScreen {
|
|||
let user = &self.users[*user_index];
|
||||
(
|
||||
*user_index,
|
||||
format!("{:>6} {}", user.user_id, user.username),
|
||||
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" }),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -211,7 +216,7 @@ impl UsersScreen {
|
|||
f,
|
||||
buttons_area[2],
|
||||
ActionButton {
|
||||
label: "Remove",
|
||||
label: "Release",
|
||||
intent: ButtonIntent::Destructive,
|
||||
focused: self.focus == Focus::RemoveButton,
|
||||
enabled: !self.loading && !self.pending && !self.users.is_empty(),
|
||||
|
|
@ -235,7 +240,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 }),
|
||||
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::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}")),
|
||||
|
|
@ -249,14 +254,14 @@ impl UsersScreen {
|
|||
let ipc = self.ipc.clone();
|
||||
let id = user.user_id;
|
||||
self.pending = true;
|
||||
self.message = Some(format!("Removing {}…", user.username));
|
||||
self.message = Some(format!("Releasing {}…", user.username));
|
||||
return InteractionResult::AppTask {
|
||||
task: Box::pin(async move {
|
||||
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}")),
|
||||
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}")),
|
||||
};
|
||||
UiEvent::App(AppEvent::UserRemoved {
|
||||
user_id: id,
|
||||
|
|
@ -528,10 +533,11 @@ impl Screen for UsersScreen {
|
|||
match result {
|
||||
Ok(()) => {
|
||||
self.pending_dialog = None;
|
||||
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}."));
|
||||
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."));
|
||||
}
|
||||
Err(error) => {
|
||||
self.dialog = self.pending_dialog.take();
|
||||
|
|
|
|||
|
|
@ -602,6 +602,9 @@ 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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,11 +102,11 @@ pub fn handle_message_delete(cv: &CommunicationValue) -> CommunicationValue {
|
|||
}
|
||||
}
|
||||
|
||||
fn stored_message_value(
|
||||
fn stored_message_fields(
|
||||
message: &chat_files::StoredMessage,
|
||||
storage_owner: i64,
|
||||
partner_id: i64,
|
||||
) -> DataValue {
|
||||
) -> Vec<(DataType, DataValue)> {
|
||||
let mut fields = vec![
|
||||
(
|
||||
DataType::MessageId,
|
||||
|
|
@ -162,7 +162,15 @@ fn stored_message_value(
|
|||
.collect();
|
||||
fields.push((DataType::Reactions, DataValue::Array(reactions)));
|
||||
}
|
||||
typed_container(fields)
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn handle_get_chat_secret(cv: &CommunicationValue) -> CommunicationValue {
|
||||
|
|
@ -520,6 +528,28 @@ 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);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ 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};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::daemon_state::{ShutdownReason, StartupPhase};
|
||||
|
||||
|
|
@ -51,7 +52,10 @@ impl CommandRouter {
|
|||
}
|
||||
let needs_omikron = matches!(
|
||||
request,
|
||||
LocalRequest::CreateUser { .. } | LocalRequest::RemoveUser { .. }
|
||||
LocalRequest::CreateUser { .. }
|
||||
| LocalRequest::AttachUserFromTu { .. }
|
||||
| LocalRequest::ReleaseUser { .. }
|
||||
| LocalRequest::CompleteDeleteUser { .. }
|
||||
);
|
||||
if needs_omikron && !self.services.omikron.is_connected().await {
|
||||
return ResponseResult::Error(
|
||||
|
|
@ -92,11 +96,15 @@ impl CommandRouter {
|
|||
ResponseResult::Ok(ResponsePayload::Tasks(tasks))
|
||||
}
|
||||
LocalRequest::ListUsers => {
|
||||
let users: Vec<UserSummary> = user_manager::get_users()
|
||||
let users: Vec<UserSummary> = user_manager::get_residency()
|
||||
.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))
|
||||
|
|
@ -137,18 +145,61 @@ impl CommandRouter {
|
|||
}
|
||||
}
|
||||
}
|
||||
LocalRequest::RemoveUser { user_id } => {
|
||||
let user = match user_manager::get_user(user_id) {
|
||||
Some(user) => user,
|
||||
None => return ResponseResult::Error(IpcErrorCode::NotFound),
|
||||
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(())),
|
||||
};
|
||||
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);
|
||||
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),
|
||||
}
|
||||
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 {
|
||||
|
|
@ -242,23 +293,22 @@ impl CommandRouter {
|
|||
ResponseResult::Ok(ResponsePayload::Components(components))
|
||||
}
|
||||
LocalRequest::GetUser { user_id } => match user_manager::get_user(user_id) {
|
||||
Some(user) => ResponseResult::Ok(ResponsePayload::UserDetail(UserDetailResponse {
|
||||
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 {
|
||||
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 { 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::ImportUser { .. } => ResponseResult::Error(IpcErrorCode::InvalidRequest),
|
||||
LocalRequest::GetLogs { limit } => {
|
||||
let entries = if let Ok(buf) = self.log_buffer.lock() {
|
||||
buf.recent(limit)
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ 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
|
||||
|
|
@ -209,6 +210,22 @@ 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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
SupervisorKind, TaskSummary, UpdateStatusResponse, UserDetailResponse, UserSummary,
|
||||
SecretString, SupervisorKind, TaskSummary, UpdateStatusResponse, UserDetailResponse, UserSummary, LocalUserState,
|
||||
};
|
||||
pub use transport::{read_msg, write_msg};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
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("<redacted>")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Client → Daemon
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -36,6 +48,21 @@ pub enum LocalRequest {
|
|||
CreateUser {
|
||||
username: String,
|
||||
},
|
||||
AttachUserFromTu {
|
||||
credential: SecretString,
|
||||
},
|
||||
PurgeUserData {
|
||||
user_id: i64,
|
||||
},
|
||||
ReleaseUser {
|
||||
user_id: i64,
|
||||
},
|
||||
CompleteDeleteUser {
|
||||
user_id: i64,
|
||||
credential: Option<SecretString>,
|
||||
},
|
||||
/// Retained only to return an actionable deprecation error to old IPC
|
||||
/// clients. It must never select lifecycle semantics implicitly.
|
||||
RemoveUser {
|
||||
user_id: i64,
|
||||
},
|
||||
|
|
@ -137,7 +164,9 @@ pub enum ResponsePayload {
|
|||
Tasks(Vec<TaskSummary>),
|
||||
Users(Vec<UserSummary>),
|
||||
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),
|
||||
|
|
@ -174,6 +203,9 @@ pub struct UserDetailResponse {
|
|||
pub display_name: Option<String>,
|
||||
pub created_at: i64,
|
||||
pub trusted_apps: Vec<String>,
|
||||
pub state: LocalUserState,
|
||||
pub data_present: bool,
|
||||
pub credential_present: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
|
@ -208,6 +240,16 @@ 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)]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
use crate::users::user_profile::UserProfile;
|
||||
use crate::util::db;
|
||||
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 iota_util::file_util::{delete_user_directory, load_file, remove_user_credential, save_file};
|
||||
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) {
|
||||
|
|
@ -45,6 +61,12 @@ 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(())
|
||||
})
|
||||
}
|
||||
|
|
@ -205,42 +227,92 @@ 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<UserResidency> {
|
||||
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::<Result<Vec<_>, _>>().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;")?;
|
||||
conn.execute_batch("DELETE FROM trusted_apps; DELETE FROM users; DELETE FROM user_residency;")?;
|
||||
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::<Vec<&str>>();
|
||||
let (uuid_str, _omega_host) = segments[0].split_once('@').unwrap_or((segments[0], ""));
|
||||
let uuid = uuid_str.parse::<i64>().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.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use iota_util::file_util::{has_file, load_file, used_dir_space};
|
||||
use iota_util::file_util::{read_user_credential_with_legacy, used_dir_space};
|
||||
use json::{JsonValue, object};
|
||||
use rand::Rng;
|
||||
use rand::rngs::OsRng;
|
||||
|
|
@ -55,9 +55,11 @@ impl UserProfile {
|
|||
if let Some(d) = &self.display_name {
|
||||
obj["display_name"] = d.clone().into();
|
||||
}
|
||||
if has_file("", &format!("{}.tu", self.username.clone())) {
|
||||
obj["tu"] = load_file("", &format!("{}.tu", self.username.clone())).into();
|
||||
}
|
||||
// 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();
|
||||
obj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -644,6 +644,62 @@ pub fn get_messages(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_message(
|
||||
storage_owner: i64,
|
||||
message_time: i64,
|
||||
external_user: Option<i64>,
|
||||
) -> Result<Option<StoredMessage>, 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<StoredMessage> = rows.collect::<Result<_, _>>()?;
|
||||
if messages.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
if external_user.is_none()
|
||||
&& messages
|
||||
.iter()
|
||||
.map(|message| message.external_user)
|
||||
.collect::<std::collections::HashSet<_>>()
|
||||
.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<StoredMessage> {
|
||||
if ids.is_empty() {
|
||||
return Vec::new();
|
||||
|
|
|
|||
|
|
@ -251,6 +251,21 @@ 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(())
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +335,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, 6);
|
||||
assert_eq!(version, 7);
|
||||
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")?;
|
||||
|
|
@ -337,8 +352,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, 6);
|
||||
for table in ["sync_heads", "sync_events", "client_sync_state"] {
|
||||
assert_eq!(version, 7);
|
||||
for table in ["sync_heads", "sync_events", "client_sync_state", "user_residency"] {
|
||||
let exists: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
|
||||
[table],
|
||||
|
|
|
|||
|
|
@ -188,6 +188,23 @@ 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<Option<StoredChatSecret>, StorageError> {
|
||||
if query.user_id.is_empty() || query.chat_id.is_empty() {
|
||||
return Ok(None);
|
||||
|
|
|
|||
|
|
@ -33,11 +33,70 @@ fn delete_dir_recursive(directory: &Path) -> bool {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_user_directory(user_id: i64) {
|
||||
pub fn delete_user_directory(user_id: i64) -> io::Result<()> {
|
||||
let user_dir = Path::new(&get_directory())
|
||||
.join("users")
|
||||
.join(user_id.to_string());
|
||||
let _ = delete_dir_recursive(&user_dir);
|
||||
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<Option<String>> {
|
||||
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
|
||||
/// `<username>.tu` is migrated atomically the first time it is encountered.
|
||||
pub fn read_user_credential_with_legacy(user_id: i64, username: &str) -> io::Result<Option<String>> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_file_buf(path: &str, name: &str) -> io::Result<BufReader<File>> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod crypto_helper;
|
||||
pub mod crypto_util;
|
||||
pub mod file_util;
|
||||
pub mod tu;
|
||||
|
|
|
|||
95
iota-util/src/tu.rs
Normal file
95
iota-util/src/tu.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//! 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", &"<redacted>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl TuCredential {
|
||||
pub fn parse(input: &str) -> Result<Self, TuError> {
|
||||
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::<i64>().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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ 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"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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 {
|
||||
|
|
@ -115,15 +116,33 @@ enum UsersAction {
|
|||
user_id: i64,
|
||||
},
|
||||
Add {
|
||||
username: String,
|
||||
username: Option<String>,
|
||||
#[arg(long, value_name = "PATH")]
|
||||
tu: Option<PathBuf>,
|
||||
},
|
||||
Remove {
|
||||
Release {
|
||||
user_id: i64,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
},
|
||||
Import {
|
||||
username: String,
|
||||
Data {
|
||||
#[command(subcommand)]
|
||||
action: UserDataAction,
|
||||
},
|
||||
CompleteDelete {
|
||||
user_id: i64,
|
||||
#[arg(long, value_name = "PATH")]
|
||||
tu: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
},
|
||||
}
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum UserDataAction {
|
||||
Purge {
|
||||
user_id: i64,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
},
|
||||
}
|
||||
#[derive(Args, Debug)]
|
||||
|
|
@ -264,14 +283,21 @@ pub enum Command {
|
|||
user_id: i64,
|
||||
},
|
||||
UsersAdd {
|
||||
username: String,
|
||||
username: Option<String>,
|
||||
tu: Option<PathBuf>,
|
||||
},
|
||||
UsersRemove {
|
||||
UsersRelease {
|
||||
user_id: i64,
|
||||
confirmed: bool,
|
||||
},
|
||||
UsersImport {
|
||||
username: String,
|
||||
UsersPurgeData {
|
||||
user_id: i64,
|
||||
confirmed: bool,
|
||||
},
|
||||
UsersCompleteDelete {
|
||||
user_id: i64,
|
||||
tu: Option<PathBuf>,
|
||||
confirmed: bool,
|
||||
},
|
||||
OmikronReconnect,
|
||||
IdentityRotate {
|
||||
|
|
@ -367,12 +393,29 @@ impl CliInvocation {
|
|||
Some(CliCommand::Users(users)) => match users.action {
|
||||
UsersAction::List => Command::UsersList,
|
||||
UsersAction::Show { user_id } => Command::UsersShow { user_id },
|
||||
UsersAction::Add { username } => Command::UsersAdd { username },
|
||||
UsersAction::Remove { user_id, yes } => Command::UsersRemove {
|
||||
UsersAction::Add { username, tu } => {
|
||||
if username.is_some() == tu.is_some() {
|
||||
return Err(
|
||||
"users add requires exactly one of <username> or --tu <PATH>".into(),
|
||||
);
|
||||
}
|
||||
Command::UsersAdd { username, tu }
|
||||
}
|
||||
UsersAction::Release { user_id, yes } => Command::UsersRelease {
|
||||
user_id,
|
||||
confirmed: resolve_confirmed(yes),
|
||||
},
|
||||
UsersAction::Import { username } => Command::UsersImport { username },
|
||||
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),
|
||||
},
|
||||
},
|
||||
Some(CliCommand::Omikron(omikron)) => match omikron.action {
|
||||
OmikronAction::Reconnect => Command::OmikronReconnect,
|
||||
|
|
@ -445,6 +488,7 @@ impl CliInvocation {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn help_text() -> String {
|
||||
Cli::command().render_long_help().to_string()
|
||||
}
|
||||
|
|
@ -528,7 +572,7 @@ mod tests {
|
|||
#[test]
|
||||
fn command_schema_drives_help_and_completion_paths() {
|
||||
let paths = CliInvocation::command_paths();
|
||||
assert!(paths.contains(&"users remove".to_owned()));
|
||||
assert!(paths.contains(&"users release".to_owned()));
|
||||
assert!(paths.contains(&"daemon install".to_owned()));
|
||||
let help = CliInvocation::help_text();
|
||||
assert!(help.contains("users"));
|
||||
|
|
@ -558,12 +602,7 @@ 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]
|
||||
|
|
@ -573,7 +612,8 @@ mod tests {
|
|||
assert_eq!(
|
||||
invocation.command,
|
||||
Command::UsersAdd {
|
||||
username: "alice".into()
|
||||
username: Some("alice".into()),
|
||||
tu: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -597,26 +637,24 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
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,
|
||||
}
|
||||
);
|
||||
fn rejects_ambiguous_users_remove() {
|
||||
let error =
|
||||
CliInvocation::parse(["users".into(), "remove".into(), "42".into()]).unwrap_err();
|
||||
assert!(error.contains("remove"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_users_remove_with_confirmation() {
|
||||
let invocation =
|
||||
CliInvocation::parse(["users".into(), "remove".into(), "42".into(), "--yes".into()])
|
||||
.unwrap();
|
||||
fn parses_users_release_with_confirmation() {
|
||||
let invocation = CliInvocation::parse([
|
||||
"users".into(),
|
||||
"release".into(),
|
||||
"42".into(),
|
||||
"--yes".into(),
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
invocation.command,
|
||||
Command::UsersRemove {
|
||||
Command::UsersRelease {
|
||||
user_id: 42,
|
||||
confirmed: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,7 +403,9 @@ fn print_help() {
|
|||
println!(" users list List all users");
|
||||
println!(" users show <ID> Show user details");
|
||||
println!(" users add <NAME> Create a new user");
|
||||
println!(" users remove <ID> Remove a user (requires --yes)");
|
||||
println!(" users add --tu <PATH> Add an existing account credential");
|
||||
println!(" users data purge <ID> Purge hosted data (requires --yes)");
|
||||
println!(" users release <ID> Release this Iota (requires --yes)");
|
||||
println!(" omikron status Show Omikron connection status");
|
||||
println!(" omikron reconnect Reconnect to Omikron");
|
||||
println!(" identity rotate Rotate identity keys (requires --yes)");
|
||||
|
|
@ -451,7 +453,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 remove 42 --yes Remove user 42");
|
||||
println!(" iota users data purge 42 --yes Purge hosted data");
|
||||
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");
|
||||
|
|
@ -564,12 +566,40 @@ async fn run_command(
|
|||
Command::Tasks => LocalRequest::ListTasks,
|
||||
Command::UsersList => LocalRequest::ListUsers,
|
||||
Command::UsersShow { user_id } => LocalRequest::GetUser { user_id },
|
||||
Command::UsersAdd { username } => LocalRequest::CreateUser { username },
|
||||
Command::UsersRemove {
|
||||
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 <username> or --tu <PATH>".into(),
|
||||
));
|
||||
}
|
||||
Command::UsersRelease {
|
||||
user_id,
|
||||
confirmed: true,
|
||||
} => LocalRequest::RemoveUser { user_id },
|
||||
Command::UsersImport { username } => LocalRequest::ImportUser { username },
|
||||
} => 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 }
|
||||
}
|
||||
Command::OmikronReconnect => LocalRequest::ReconnectOmikron,
|
||||
Command::IdentityRotate { confirmed: true } => LocalRequest::RotateIotaIdentity,
|
||||
Command::RegenerateKeys { confirmed: true } => LocalRequest::RotateIotaIdentity,
|
||||
|
|
@ -588,9 +618,11 @@ async fn run_command(
|
|||
Command::Logs { limit } => LocalRequest::GetLogs { limit },
|
||||
Command::UpdateCheck => LocalRequest::CheckUpdate,
|
||||
Command::CommunityList => LocalRequest::ListCommunities,
|
||||
Command::UsersRemove {
|
||||
Command::UsersRelease {
|
||||
confirmed: false, ..
|
||||
}
|
||||
| Command::UsersPurgeData { confirmed: false, .. }
|
||||
| Command::UsersCompleteDelete { confirmed: false, .. }
|
||||
| Command::IdentityRotate { confirmed: false }
|
||||
| Command::RegenerateKeys { confirmed: false }
|
||||
| Command::DaemonRestart { confirmed: false }
|
||||
|
|
@ -689,6 +721,9 @@ 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);
|
||||
}
|
||||
|
|
@ -935,6 +970,9 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -884,6 +884,7 @@ 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);
|
||||
|
|
@ -895,12 +896,41 @@ 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<Self>, 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<Self>, cv: &CommunicationValue) {
|
||||
let sender_id = cv.get_sender().to_string();
|
||||
let recipients = match chat_secret_recipients(cv) {
|
||||
|
|
@ -1447,6 +1477,24 @@ 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 {
|
||||
|
|
@ -1701,6 +1749,12 @@ impl OmikronConnection {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn handle_message_get(self: Arc<Self>, cv: &CommunicationValue) {
|
||||
let _ = self
|
||||
.send_message(&message_handlers::handle_message_get(cv))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_get_chats(self: Arc<Self>, cv: &CommunicationValue) {
|
||||
let _ = self
|
||||
.send_message(&message_handlers::handle_get_chats(cv))
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ 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::try_save_file;
|
||||
use iota_util::file_util::write_user_credential;
|
||||
use iota_util::tu::TuCredential;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use mtp::crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme};
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -20,6 +23,133 @@ pub enum CreateUserError {
|
|||
LocalPersistence(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LifecycleUserError {
|
||||
InvalidCredential(String),
|
||||
OmegaHostMismatch,
|
||||
RemoteRejected,
|
||||
Transport(crate::OmikronError),
|
||||
LocalPersistence(String),
|
||||
}
|
||||
|
||||
impl From<crate::OmikronError> 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<u8> {
|
||||
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<i64, LifecycleUserError> {
|
||||
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<u8>, Vec<u8>), 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<UserProfile, LifecycleUserError> {
|
||||
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
|
||||
|
|
@ -136,15 +266,9 @@ pub async fn create_user(
|
|||
}
|
||||
}
|
||||
log!("Created User");
|
||||
try_save_file(
|
||||
"",
|
||||
&format!("{}.tu", username),
|
||||
&format!(
|
||||
"{}@{}::{}",
|
||||
user_id,
|
||||
omega_discovery::omega_host(),
|
||||
keyring_b64
|
||||
),
|
||||
write_user_credential(
|
||||
user_id,
|
||||
&format!("{}@{}::{}", user_id, omega_discovery::omega_host(), keyring_b64),
|
||||
)
|
||||
.map_err(|error| CreateUserError::LocalPersistence(error.to_string()))?;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue