From 3ecd1fd71833d038c0876559ce39012beee24764 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Mon, 8 Dec 2025 19:43:23 +0200 Subject: [PATCH 1/5] Add working implement 2FA Signed-off-by: Cristian Andrei --- Cargo.lock | 1847 +++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + src/auth.rs | 133 ++++ src/main.rs | 1 + src/run.rs | 156 ++--- 5 files changed, 2017 insertions(+), 124 deletions(-) create mode 100644 src/auth.rs diff --git a/Cargo.lock b/Cargo.lock index 8d2d0a9e..e1dede57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,15 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -362,6 +371,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -464,7 +491,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ - "darling", + "darling 0.20.10", "ident_case", "prettyplease", "proc-macro2", @@ -515,6 +542,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "0.1.10" @@ -533,6 +566,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -616,6 +663,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -625,6 +682,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -641,6 +704,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -741,6 +814,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -761,14 +846,51 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -785,17 +907,53 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.90", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.90", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -803,6 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -823,16 +982,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -934,6 +1179,22 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flume" version = "0.11.0" @@ -958,6 +1219,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fuse3" version = "0.8.1" @@ -1077,6 +1347,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1098,6 +1369,36 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -1108,6 +1409,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -1171,71 +1478,377 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] [[package]] -name = "indexmap" -version = "2.7.0" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "equivalent", - "hashbrown", + "bytes", + "itoa", ] [[package]] -name = "inout" -version = "0.1.3" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "block-padding", - "generic-array", + "bytes", + "http 0.2.12", + "pin-project-lite", ] [[package]] -name = "instant" -version = "0.1.13" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "cfg-if 1.0.0", + "bytes", + "http 1.4.0", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", ] [[package]] -name = "is-terminal" -version = "0.4.13" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "itertools" -version = "0.10.5" +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.35", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.4", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ @@ -1248,6 +1861,28 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if 1.0.0", + "combine", + "jni-sys", + "log", + "thiserror 1.0.64", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.72" @@ -1276,6 +1911,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1283,6 +1921,12 @@ version = "0.2.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linux-keyutils" version = "0.2.4" @@ -1305,6 +1949,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "lock_api" version = "0.4.12" @@ -1327,7 +1977,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1363,6 +2013,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1393,6 +2049,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nix" version = "0.26.4" @@ -1452,6 +2114,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1515,6 +2193,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http 0.2.12", + "rand", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.64", + "url", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.6.0", + "objc2", ] [[package]] @@ -1549,6 +2273,47 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools", + "log", + "oauth2", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.64", + "url", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1565,6 +2330,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1605,6 +2394,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1628,6 +2432,27 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "plotters" version = "0.3.7" @@ -1687,6 +2512,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1712,6 +2546,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1731,6 +2574,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.35", + "socket2 0.5.7", + "thiserror 2.0.6", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.35", + "rustls-pki-types", + "slab", + "thiserror 2.0.6", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -1799,6 +2694,26 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "regex" version = "1.11.0" @@ -1851,7 +2766,7 @@ dependencies = [ "argon2", "async-trait", "atomic-write-file", - "base64", + "base64 0.22.1", "bincode", "blake3", "bon", @@ -1867,9 +2782,11 @@ dependencies = [ "lru", "num-format", "okaywal", + "openidconnect", "rand", "rand_chacha", "rand_core", + "reqwest 0.12.12", "retainer", "ring", "rpassword", @@ -1887,6 +2804,92 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "tracing-test", + "url", + "webbrowser", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.35", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.11", + "windows-registry", ] [[package]] @@ -1901,6 +2904,16 @@ dependencies = [ "rand", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.12" @@ -1926,6 +2939,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.2" @@ -1942,6 +2975,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1978,6 +3017,81 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -1999,12 +3113,60 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secret-service" version = "3.1.0" @@ -2031,7 +3193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -2055,18 +3217,38 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2075,14 +3257,35 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", ] [[package]] @@ -2096,6 +3299,49 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2154,6 +3400,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -2198,6 +3454,22 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2257,6 +3529,53 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.13.0" @@ -2351,6 +3670,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2361,6 +3690,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -2390,6 +3734,26 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.35", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.16" @@ -2401,6 +3765,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -2413,11 +3790,38 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.7.0", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.40" @@ -2523,6 +3927,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -2552,6 +3962,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2586,6 +4014,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2618,6 +4055,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -2657,6 +4106,56 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "6.0.3" @@ -2700,6 +4199,104 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2727,6 +4324,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2758,6 +4370,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2770,6 +4388,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2782,6 +4406,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2800,6 +4430,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2812,6 +4448,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2824,6 +4466,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2836,6 +4484,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2857,12 +4511,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + [[package]] name = "winsafe" version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "xdg-home" version = "1.3.0" @@ -2873,6 +4543,29 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zbus" version = "3.15.2" @@ -2960,12 +4653,66 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 8484ba24..2fb59e72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,10 @@ subtle = "2.6.1" bon = "3.3.0" shush-rs = "0.1.10" criterion = { version = "0.5.1", features = ["html_reports"] } +openidconnect = { version = "3.5", features = ["reqwest"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } +url = "2.5" +webbrowser = "1.0" [target.'cfg(target_os = "linux")'.dependencies] fuse3 = { version = "0.8.1", features = ["tokio-runtime", "unprivileged"] } diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 00000000..e26eb37b --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,133 @@ +use anyhow::{Context, Result}; +use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; +use openidconnect::reqwest::async_http_client; +// UPDATED IMPORT: Added OAuth2TokenResponse based on compiler hint +use openidconnect::{ + AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, + RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, +}; +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpListener; +use url::Url; +use webbrowser; + +/// Authenticates with Google. +/// +/// If `stored_refresh_token` is provided, it tries to use it to silently refresh the session. +/// If that fails or no token is provided, it triggers the full browser flow. +/// +/// Returns the valid Refresh Token string to be saved. +pub async fn authenticate_google( + client_id: String, + client_secret: String, + stored_refresh_token: Option +) -> Result { + + // 1. Setup Client + let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; + + let provider_metadata = CoreProviderMetadata::discover_async(google_issuer, async_http_client) + .await + .context("Failed to discover Google OIDC configuration")?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + ) + .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); + + // 2. Try Silent Auth (Refresh Token) if available + if let Some(token_str) = stored_refresh_token { + println!("Validating cached Google session..."); + let refresh_token = RefreshToken::new(token_str.clone()); + + let response = client + .exchange_refresh_token(&refresh_token) + .request_async(async_http_client) + .await; + + match response { + Ok(_) => { + println!("Google session is valid. Skipping browser."); + return Ok(token_str); // Return the existing token as it is still valid + }, + Err(_) => { + println!("Cached session expired or invalid. Re-authenticating..."); + // Fall through to browser login + } + } + } + + // 3. Browser Login Flow + println!("Initiating Google 2FA..."); + + let (authorize_url, csrf_state, _nonce) = client + .authorize_url( + AuthenticationFlow::::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + // CRITICAL: Request offline access to get a Refresh Token + .add_extra_param("access_type", "offline") + // Force consent to ensure we get a refresh token every time we do the full flow + .add_extra_param("prompt", "consent") + .url(); + + println!("Opening browser for authentication..."); + if webbrowser::open(authorize_url.as_str()).is_err() { + println!("Please open this URL in your browser:\n{}", authorize_url); + } + + let listener = TcpListener::bind("127.0.0.1:8080")?; + + if let Some(stream) = listener.incoming().next() { + let mut stream = stream?; + let code; + let state; + + { + let mut reader = BufReader::new(&stream); + let mut request_line = String::new(); + reader.read_line(&mut request_line)?; + + let redirect_url = request_line.split_whitespace().nth(1).unwrap_or("/"); + let url = Url::parse(&("http://localhost".to_string() + redirect_url))?; + + let code_pair = url.query_pairs().find(|(key, _)| key == "code"); + let state_pair = url.query_pairs().find(|(key, _)| key == "state"); + + if let (Some((_, c)), Some((_, s))) = (code_pair, state_pair) { + code = c.into_owned(); + state = s.into_owned(); + } else { + return Err(anyhow::anyhow!("Failed to retrieve auth code")); + } + } + + let response = "HTTP/1.1 200 OK\r\n\r\nGoogle Auth Successful! You can close this window."; + stream.write_all(response.as_bytes())?; + + if state != *csrf_state.secret() { + return Err(anyhow::anyhow!("CSRF state mismatch")); + } + + let token_response = client + .exchange_code(openidconnect::AuthorizationCode::new(code)) + .request_async(async_http_client) + .await + .context("Failed to exchange auth code for token")?; + + println!("Google Authentication successful."); + + // Extract the Refresh Token to save it + let refresh_token = token_response.refresh_token() + .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token (check access_type=offline)"))?; + + Ok(refresh_token.secret().clone()) + } else { + Err(anyhow::anyhow!("Failed to receive connection from browser")) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d8986e0d..0360d5ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Result; mod keyring; +mod auth; #[cfg(target_os = "linux")] mod run; diff --git a/src/run.rs b/src/run.rs index 8a71ff10..47f4a44a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -268,49 +268,73 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // when running from IDE we can't read from stdin with rpassword, get it from env var - let mut password = SecretString::from_str( - env::var("RENCFS_PASSWORD") - .unwrap_or_else(|_| String::new()) - .as_str(), - ) - .unwrap(); - if password.expose_secret().is_empty() { - // read password from stdin - print!("Enter password: "); + // --- SECURE CREDENTIAL LOADING --- + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + + // Sanitize: Remove whitespace AND strictly remove surrounding quotes if present + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + // ------------------------------------------------------------- + // PHASE 1: GOOGLE AUTH (Token Caching) + // ------------------------------------------------------------- + // We check for a stored REFRESH TOKEN for Google, not the master password. + // If the token exists and works, we skip the browser. + + if !google_client_id.is_empty() { + // Try to load Google Refresh Token from Keyring + // FIX: expose_secret() and convert to String, because authenticate_google expects Option + let stored_token = keyring::get("google_refresh_token") + .ok() + .map(|s| s.expose_secret().to_string()); + + // Authenticate (Silent Refresh OR Browser Flow) + let valid_refresh_token = crate::auth::authenticate_google( + google_client_id.clone(), + google_client_secret.clone(), + stored_token + ).await?; + + // Save the valid token back to keyring (in case it was new) + // FIX: Wrap String in SecretString before saving + let secret_token = SecretString::new(Box::new(valid_refresh_token)); + let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { + warn!("Failed to save Google token to keyring: {}", e); + }); + } + + // ------------------------------------------------------------- + // PHASE 2: MASTER PASSWORD (Always Manual) + // ------------------------------------------------------------- + // We do NOT check keyring for this. We always ask the user. + + print!("Enter password: "); + io::stdout().flush().unwrap(); + let p = SecretString::new(Box::new(read_password()?)); + + if !PathBuf::new().join(data_dir.clone()).is_dir() + || fs::read_dir(&data_dir) + .await + .unwrap() + .next_entry() + .await + .unwrap() + .is_none() + { + // first run / empty dir logic: confirm password + print!("Confirm password: "); io::stdout().flush().unwrap(); - password = SecretString::new(Box::new(read_password()?)); - - if !PathBuf::new().join(data_dir.clone()).is_dir() - || fs::read_dir(&data_dir) - .await - .unwrap() - .next_entry() - .await - .unwrap() - .is_none() - { - // first run, ask to confirm password - print!("Confirm password: "); - io::stdout().flush().unwrap(); - let confirm_password = SecretString::new(Box::new(read_password()?)); - if password.expose_secret() != confirm_password.expose_secret() { - error!("Passwords do not match"); - return Err(ExitStatusError::Failure(1).into()); - } + let confirm_password = SecretString::new(Box::new(read_password()?)); + if p.expose_secret() != confirm_password.expose_secret() { + error!("Passwords do not match"); + return Err(ExitStatusError::Failure(1).into()); } } - // save password in keyring - info!("Save password in keyring"); - let res = keyring::save(&password, "password").map_err(|err| { - warn!(err = %err); - }); - if res.is_err() { - // maybe we don't have a security manager, keep it in mem - unsafe { - warn!("Cannot save password in keyring, keep it in memory"); - PASS = Some(password.clone()); - } + + // Keep password in memory for the mount lifecycle + unsafe { + PASS = Some(p.clone()); } if matches.get_flag("umount-on-start") { @@ -320,50 +344,40 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { }); } - struct PasswordProviderImpl {} - #[allow(clippy::items_after_statements)] - #[allow(static_mut_refs)] + struct PasswordProviderImpl { + pass: SecretString, + } impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - unsafe { - if PASS.is_some() { - info!("Get password from memory"); - PASS.clone() - } else { - info!("Get password from keyring"); - keyring::get("password") - .map_err(|err| { - error!(err = %err, "cannot get password from keyring"); - err - }) - .ok() - } - } + Some(self.pass.clone()) } } + let mount_point = mount::create_mount_point( Path::new(&mountpoint), Path::new(&data_dir), - Box::new(PasswordProviderImpl {}), + Box::new(PasswordProviderImpl { pass: p }), cipher, matches.get_flag("allow-root"), matches.get_flag("allow-other"), matches.get_flag("read-only"), ); + let mount_handle = mount_point.mount().await.map_err(|err| { error!(err = %err); ExitStatusError::Failure(1) })?; let mount_handle = Arc::new(Mutex::new(Some(Some(mount_handle)))); let mount_handle_clone = mount_handle.clone(); - // cleanup on process kill + set_handler(move || { - // can't use tracing methods here as guard cannot be dropper to flush content before we exit eprintln!("Received signal to exit"); let mut status: Option = None; - remove_pass(); + + // Cleanup memory on exit + remove_pass(); + eprintln!("Unmounting {mountpoint}"); - // create new tokio runtime let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -406,18 +420,12 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } #[allow(static_mut_refs)] +#[allow(dead_code)] fn remove_pass() { unsafe { - if PASS.is_none() { - info!("Delete password from keyring"); - keyring::remove("password") - .map_err(|err| { - error!(err = %err); - }) - .ok(); - } else { - info!("Remove password from memory"); - PASS = None; - } + // We only clear memory now. We do NOT touch the keyring + // because we never saved the password there in the first place. + info!("Remove password from memory"); + PASS = None; } -} +} \ No newline at end of file From 63e8699e44b05382af97e96208053f71bdcbd8c0 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Tue, 9 Dec 2025 13:39:27 +0200 Subject: [PATCH 2/5] Refine implementation Signed-off-by: Cristian Andrei --- src/run.rs | 137 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/src/run.rs b/src/run.rs index 47f4a44a..bdab47d9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,6 +16,7 @@ use tokio::{fs, task}; use tracing::{error, info, warn, Level}; use crate::keyring; +// use crate::auth; // Accessed via crate::auth::... below use rencfs::crypto::Cipher; use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; @@ -268,73 +269,77 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // --- SECURE CREDENTIAL LOADING --- + // retrieve credentials let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - // Sanitize: Remove whitespace AND strictly remove surrounding quotes if present + // refine credentials let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); - // ------------------------------------------------------------- - // PHASE 1: GOOGLE AUTH (Token Caching) - // ------------------------------------------------------------- - // We check for a stored REFRESH TOKEN for Google, not the master password. - // If the token exists and works, we skip the browser. - if !google_client_id.is_empty() { - // Try to load Google Refresh Token from Keyring - // FIX: expose_secret() and convert to String, because authenticate_google expects Option + // try to load token from keyring let stored_token = keyring::get("google_refresh_token") .ok() .map(|s| s.expose_secret().to_string()); - // Authenticate (Silent Refresh OR Browser Flow) + // authenticate with Google let valid_refresh_token = crate::auth::authenticate_google( google_client_id.clone(), google_client_secret.clone(), stored_token ).await?; - // Save the valid token back to keyring (in case it was new) - // FIX: Wrap String in SecretString before saving + // save the valid token back to keyring let secret_token = SecretString::new(Box::new(valid_refresh_token)); let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { warn!("Failed to save Google token to keyring: {}", e); }); } - // ------------------------------------------------------------- - // PHASE 2: MASTER PASSWORD (Always Manual) - // ------------------------------------------------------------- - // We do NOT check keyring for this. We always ask the user. - - print!("Enter password: "); - io::stdout().flush().unwrap(); - let p = SecretString::new(Box::new(read_password()?)); - - if !PathBuf::new().join(data_dir.clone()).is_dir() - || fs::read_dir(&data_dir) - .await - .unwrap() - .next_entry() - .await - .unwrap() - .is_none() - { - // first run / empty dir logic: confirm password - print!("Confirm password: "); + // when running from IDE we can't read from stdin with rpassword, get it from env var + let mut password = SecretString::from_str( + env::var("RENCFS_PASSWORD") + .unwrap_or_else(|_| String::new()) + .as_str(), + ) + .unwrap(); + if password.expose_secret().is_empty() { + // read password from stdin + print!("Enter password: "); io::stdout().flush().unwrap(); - let confirm_password = SecretString::new(Box::new(read_password()?)); - if p.expose_secret() != confirm_password.expose_secret() { - error!("Passwords do not match"); - return Err(ExitStatusError::Failure(1).into()); + password = SecretString::new(Box::new(read_password()?)); + + if !PathBuf::new().join(data_dir.clone()).is_dir() + || fs::read_dir(&data_dir) + .await + .unwrap() + .next_entry() + .await + .unwrap() + .is_none() + { + // first run, ask to confirm password + print!("Confirm password: "); + io::stdout().flush().unwrap(); + let confirm_password = SecretString::new(Box::new(read_password()?)); + if password.expose_secret() != confirm_password.expose_secret() { + error!("Passwords do not match"); + return Err(ExitStatusError::Failure(1).into()); + } } } - - // Keep password in memory for the mount lifecycle - unsafe { - PASS = Some(p.clone()); + // save password in keyring + info!("Save password in keyring"); + let res = keyring::save(&password, "password").map_err(|err| { + warn!(err = %err); + }); + if res.is_err() { + // maybe we don't have a security manager, keep it in mem + unsafe { + warn!("Cannot save password in keyring, keep it in memory"); + PASS = Some(password.clone()); + } } if matches.get_flag("umount-on-start") { @@ -344,40 +349,50 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { }); } - struct PasswordProviderImpl { - pass: SecretString, - } + struct PasswordProviderImpl {} + #[allow(clippy::items_after_statements)] + #[allow(static_mut_refs)] impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - Some(self.pass.clone()) + unsafe { + if PASS.is_some() { + info!("Get password from memory"); + PASS.clone() + } else { + info!("Get password from keyring"); + keyring::get("password") + .map_err(|err| { + error!(err = %err, "cannot get password from keyring"); + err + }) + .ok() + } + } } } - let mount_point = mount::create_mount_point( Path::new(&mountpoint), Path::new(&data_dir), - Box::new(PasswordProviderImpl { pass: p }), + Box::new(PasswordProviderImpl {}), cipher, matches.get_flag("allow-root"), matches.get_flag("allow-other"), matches.get_flag("read-only"), ); - let mount_handle = mount_point.mount().await.map_err(|err| { error!(err = %err); ExitStatusError::Failure(1) })?; let mount_handle = Arc::new(Mutex::new(Some(Some(mount_handle)))); let mount_handle_clone = mount_handle.clone(); - + // cleanup on process kill set_handler(move || { + // can't use tracing methods here as guard cannot be dropper to flush content before we exit eprintln!("Received signal to exit"); let mut status: Option = None; - - // Cleanup memory on exit - remove_pass(); - + remove_pass(); eprintln!("Unmounting {mountpoint}"); + // create new tokio runtime let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -420,12 +435,18 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } #[allow(static_mut_refs)] -#[allow(dead_code)] fn remove_pass() { unsafe { - // We only clear memory now. We do NOT touch the keyring - // because we never saved the password there in the first place. - info!("Remove password from memory"); - PASS = None; + if PASS.is_none() { + info!("Delete password from keyring"); + keyring::remove("password") + .map_err(|err| { + error!(err = %err); + }) + .ok(); + } else { + info!("Remove password from memory"); + PASS = None; + } } } \ No newline at end of file From 425bc7ed43eed9a03eb8236eed2bf51ad8c1c560 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 17:04:54 +0200 Subject: [PATCH 3/5] Add email recognition --- src/auth.rs | 79 +++++++++++++++++++++++++++-------------------------- src/run.rs | 21 +++++++++----- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index e26eb37b..1ebff062 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,10 +1,11 @@ +// src/auth.rs use anyhow::{Context, Result}; use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; use openidconnect::reqwest::async_http_client; -// UPDATED IMPORT: Added OAuth2TokenResponse based on compiler hint +// UPDATED IMPORT: Added TokenResponse (for id_token() method) use openidconnect::{ AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, - RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, + RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, TokenResponse, }; use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; @@ -13,15 +14,18 @@ use webbrowser; /// Authenticates with Google. /// -/// If `stored_refresh_token` is provided, it tries to use it to silently refresh the session. -/// If that fails or no token is provided, it triggers the full browser flow. +/// Triggers a browser flow to authenticate the user. +/// Enforces strict user presence (Passkey/2FA) via `max_age=0`. /// -/// Returns the valid Refresh Token string to be saved. +/// If `expected_sub` is provided, it verifies that the authenticated user's +/// Subject ID matches the expected one. +/// +/// Returns a tuple: (Refresh Token, Subject ID) pub async fn authenticate_google( client_id: String, client_secret: String, - stored_refresh_token: Option -) -> Result { + expected_sub: Option +) -> Result<(String, String)> { // 1. Setup Client let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; @@ -37,32 +41,11 @@ pub async fn authenticate_google( ) .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); - // 2. Try Silent Auth (Refresh Token) if available - if let Some(token_str) = stored_refresh_token { - println!("Validating cached Google session..."); - let refresh_token = RefreshToken::new(token_str.clone()); - - let response = client - .exchange_refresh_token(&refresh_token) - .request_async(async_http_client) - .await; - - match response { - Ok(_) => { - println!("Google session is valid. Skipping browser."); - return Ok(token_str); // Return the existing token as it is still valid - }, - Err(_) => { - println!("Cached session expired or invalid. Re-authenticating..."); - // Fall through to browser login - } - } - } - - // 3. Browser Login Flow - println!("Initiating Google 2FA..."); + // 2. Browser Login Flow + println!("Initiating Google 2FA (Passkey Check)..."); - let (authorize_url, csrf_state, _nonce) = client + // We keep the `nonce` to verify the ID token later + let (authorize_url, csrf_state, nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, CsrfToken::new_random, @@ -70,10 +53,9 @@ pub async fn authenticate_google( ) .add_scope(Scope::new("email".to_string())) .add_scope(Scope::new("profile".to_string())) - // CRITICAL: Request offline access to get a Refresh Token .add_extra_param("access_type", "offline") - // Force consent to ensure we get a refresh token every time we do the full flow - .add_extra_param("prompt", "consent") + .add_extra_param("prompt", "login") // Force login UI + .add_extra_param("max_age", "0") // Force fresh authentication (Passkey) .url(); println!("Opening browser for authentication..."); @@ -120,13 +102,32 @@ pub async fn authenticate_google( .await .context("Failed to exchange auth code for token")?; - println!("Google Authentication successful."); + // 3. Verify ID Token and Check Subject + let id_token = token_response.id_token() + .ok_or_else(|| anyhow::anyhow!("Google did not return an ID token"))?; + + let claims = id_token.claims(&client.id_token_verifier(), &nonce) + .context("Failed to verify ID token claims")?; + + let subject = claims.subject().to_string(); + + if let Some(expected) = &expected_sub { + if *expected != subject { + return Err(anyhow::anyhow!( + "Security Alert: Authenticated user ({}) does not match the owner ({})!", + subject, expected + )); + } + } else { + println!("First run detected. Binding this file system to user ID: {}", subject); + } + + println!("Google Authentication and Identity Verification successful."); - // Extract the Refresh Token to save it let refresh_token = token_response.refresh_token() - .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token (check access_type=offline)"))?; + .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token"))?; - Ok(refresh_token.secret().clone()) + Ok((refresh_token.secret().clone(), subject)) } else { Err(anyhow::anyhow!("Failed to receive connection from browser")) } diff --git a/src/run.rs b/src/run.rs index bdab47d9..1b957f20 100644 --- a/src/run.rs +++ b/src/run.rs @@ -273,24 +273,31 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - // refine credentials let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); if !google_client_id.is_empty() { - // try to load token from keyring - let stored_token = keyring::get("google_refresh_token") + // Try to load the known Owner ID (sub) from keyring + let stored_sub = keyring::get("google_owner_sub") .ok() .map(|s| s.expose_secret().to_string()); - // authenticate with Google - let valid_refresh_token = crate::auth::authenticate_google( + // Authenticate with Google (Passkey + Identity Check) + let (valid_refresh_token, subject) = crate::auth::authenticate_google( google_client_id.clone(), google_client_secret.clone(), - stored_token + stored_sub.clone() // Pass the expected sub (or None if first run) ).await?; - // save the valid token back to keyring + // If this was the first run (no stored sub), save the verified sub as the owner + if stored_sub.is_none() { + let secret_sub = SecretString::new(Box::new(subject)); + let _ = keyring::save(&secret_sub, "google_owner_sub").map_err(|e| { + warn!("Failed to save Google Owner ID to keyring: {}", e); + }); + } + + // Save the valid refresh token back to keyring let secret_token = SecretString::new(Box::new(valid_refresh_token)); let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { warn!("Failed to save Google token to keyring: {}", e); From 0617404e273b086d04b55a83700c2eb8a7e66bf7 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 18:52:18 +0200 Subject: [PATCH 4/5] Add implementation for new param Signed-off-by: Cristian Andrei --- src/run.rs | 66 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/run.rs b/src/run.rs index 1b957f20..2eb3f33a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -153,6 +153,14 @@ fn get_cli_args() -> ArgMatches { .requires("data-dir") .help("If we should try to umount the mountpoint before starting the FUSE server. This can be useful when the previous run crashed or was forced kll and the mountpoint is still mounted."), ) + .arg( + Arg::new("init-2fa") + .long("init-2fa") + .action(ArgAction::SetTrue) + .requires("mount-point") + .requires("data-dir") + .help("Initialize Google 2FA for this filesystem. Required only for the first run to bind the user."), + ) .arg( Arg::new("allow-root") .long("allow-root") @@ -269,39 +277,47 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // retrieve credentials - let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); - let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + // 1. Check Keyring for existing 2FA registration + // We attempt to fetch the ID first. If it exists, we MUST enforce 2FA. + let stored_sub = match keyring::get("google_owner_sub") { + Ok(s) => Some(s.expose_secret().to_string()), + Err(_) => None, // Entry not found or keyring unavailable + }; - let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); - let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + let init_2fa = matches.get_flag("init-2fa"); - if !google_client_id.is_empty() { - // Try to load the known Owner ID (sub) from keyring - let stored_sub = keyring::get("google_owner_sub") - .ok() - .map(|s| s.expose_secret().to_string()); - - // Authenticate with Google (Passkey + Identity Check) - let (valid_refresh_token, subject) = crate::auth::authenticate_google( - google_client_id.clone(), - google_client_secret.clone(), - stored_sub.clone() // Pass the expected sub (or None if first run) + // 2. Decide if we need to Authenticate + if stored_sub.is_some() || init_2fa { + // Retrieve credentials + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + // 3. Fail if credentials are missing but 2FA is required + if google_client_id.is_empty() || google_client_secret.is_empty() { + return Err(anyhow::anyhow!( + "CRITICAL: This filesystem (or your user profile) is bound to Google 2FA, but GOOGLE_CLIENT_ID/SECRET are missing.\n\ + You must provide these environment variables to authenticate." + )); + } + + // 4. Authenticate + let (_valid_refresh_token, subject) = crate::auth::authenticate_google( + google_client_id, + google_client_secret, + stored_sub.clone() ).await?; - // If this was the first run (no stored sub), save the verified sub as the owner + // 5. Persist the ID if this was the first run if stored_sub.is_none() { let secret_sub = SecretString::new(Box::new(subject)); - let _ = keyring::save(&secret_sub, "google_owner_sub").map_err(|e| { + if let Err(e) = keyring::save(&secret_sub, "google_owner_sub") { warn!("Failed to save Google Owner ID to keyring: {}", e); - }); + } else { + info!("2FA Identity saved. Future mounts will strictly require Google Auth."); + } } - - // Save the valid refresh token back to keyring - let secret_token = SecretString::new(Box::new(valid_refresh_token)); - let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { - warn!("Failed to save Google token to keyring: {}", e); - }); } // when running from IDE we can't read from stdin with rpassword, get it from env var From 62a681ff16703609477c12d4991fa978fe590922 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 19:58:16 +0200 Subject: [PATCH 5/5] Add encrypted storage of Subject ID Signed-off-by: Cristian Andrei --- src/auth.rs | 18 ++++----- src/encryptedfs.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/run.rs | 93 ++++++++++++++++++++++------------------------ 3 files changed, 131 insertions(+), 59 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 1ebff062..ab420e89 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -15,10 +15,9 @@ use webbrowser; /// Authenticates with Google. /// /// Triggers a browser flow to authenticate the user. -/// Enforces strict user presence (Passkey/2FA) via `max_age=0`. /// /// If `expected_sub` is provided, it verifies that the authenticated user's -/// Subject ID matches the expected one. +/// subject ID matches the expected one. /// /// Returns a tuple: (Refresh Token, Subject ID) pub async fn authenticate_google( @@ -27,7 +26,6 @@ pub async fn authenticate_google( expected_sub: Option ) -> Result<(String, String)> { - // 1. Setup Client let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; let provider_metadata = CoreProviderMetadata::discover_async(google_issuer, async_http_client) @@ -41,10 +39,9 @@ pub async fn authenticate_google( ) .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); - // 2. Browser Login Flow println!("Initiating Google 2FA (Passkey Check)..."); - // We keep the `nonce` to verify the ID token later + // keep the `nonce` to verify the ID token later let (authorize_url, csrf_state, nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, @@ -102,7 +99,7 @@ pub async fn authenticate_google( .await .context("Failed to exchange auth code for token")?; - // 3. Verify ID Token and Check Subject + // verify ID token and check subject let id_token = token_response.id_token() .ok_or_else(|| anyhow::anyhow!("Google did not return an ID token"))?; @@ -119,15 +116,16 @@ pub async fn authenticate_google( )); } } else { - println!("First run detected. Binding this file system to user ID: {}", subject); + println!("Authenticated as user ID: {}", subject); } println!("Google Authentication and Identity Verification successful."); - let refresh_token = token_response.refresh_token() - .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token"))?; + let refresh_token = token_response.refresh_token() + .map(|t| t.secret().clone()) + .unwrap_or_else(String::new); - Ok((refresh_token.secret().clone(), subject)) + Ok((refresh_token, subject)) } else { Err(anyhow::anyhow!("Failed to receive connection from browser")) } diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 7c2da5a4..9c5d8545 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -41,6 +41,7 @@ pub(crate) const CONTENTS_DIR: &str = "contents"; pub(crate) const SECURITY_DIR: &str = "security"; pub(crate) const KEY_ENC_FILENAME: &str = "key.enc"; pub(crate) const KEY_SALT_FILENAME: &str = "key.salt"; +pub(crate) const IDENTITY_FILENAME: &str = "identity.enc"; pub(crate) const LS_DIR: &str = "ls"; pub(crate) const HASH_DIR: &str = "hash"; @@ -640,6 +641,84 @@ impl EncryptedFs { Ok(arc) } + // checks if the filesystem at the given path is bound to a specific identity. + pub fn is_identity_bound(data_dir: &Path) -> bool { + data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME).exists() + } + + // decrypts and returns the bound identity (subject ID) if it exists. + pub fn get_bound_identity( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + ) -> FsResult> { + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + if !identity_path.exists() { + return Ok(None); + } + + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); + let stored_sub: String = bincode::deserialize_from(reader) + .map_err(|_| FsError::Other("Failed to decrypt identity. Wrong password?"))?; + + Ok(Some(stored_sub)) + } + + // Verifies the identity of the user against the bound Google Account ID (sub). + // If no identity is bound, it encrypts and stores the provided `subject` to bind the vault. + pub async fn verify_or_bind_identity( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + subject: &str, + ) -> FsResult<()> { + // Ensure the directory structure exists + ensure_structure_created(&data_dir.to_path_buf()).await?; + + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + + // Derive/Load the master key using the provided password + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + + if identity_path.exists() { + info!("Identity file found. Verifying 2FA Identity..."); + + // Decrypt the stored ID + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); + let stored_sub: String = bincode::deserialize_from(reader) + .map_err(|e| { + error!("Failed to deserialize identity: {}", e); + FsError::Other("Failed to read identity file. Password might be wrong or file corrupted.") + })?; + + if stored_sub != subject { + error!("CRITICAL: Identity mismatch! Vault is bound to user '{}', but current user is '{}'", stored_sub, subject); + return Err(FsError::Other("Access Denied: The Google Account used for 2FA does not match the vault owner.")); + } + info!("Identity verified successfully."); + } else { + info!("No identity file found. Binding vault to Google Account ID: {}", subject); + + // Encrypt and save the ID + crypto::atomic_serialize_encrypt_into( + &identity_path, + &subject.to_string(), + cipher, + &key + )?; + info!("Vault successfully bound to user."); + } + + Ok(()) + } + pub fn exists(&self, ino: u64) -> bool { self.ino_file(ino).is_file() } diff --git a/src/run.rs b/src/run.rs index 2eb3f33a..d2e7dbd3 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,7 +16,7 @@ use tokio::{fs, task}; use tracing::{error, info, warn, Level}; use crate::keyring; -// use crate::auth; // Accessed via crate::auth::... below + use rencfs::crypto::Cipher; use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; @@ -24,6 +24,7 @@ use rencfs::{log, mount}; static mut PASS: Option = None; + #[derive(Debug, Error)] enum ExitStatusError { #[error("exit with status {0}")] @@ -53,6 +54,7 @@ pub(super) async fn run() -> Result<()> { }) }) .await; + match res { Ok(Ok(Ok(()))) => Ok(()), Ok(Ok(Err(err))) => { @@ -276,63 +278,21 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .to_string(); let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - - // 1. Check Keyring for existing 2FA registration - // We attempt to fetch the ID first. If it exists, we MUST enforce 2FA. - let stored_sub = match keyring::get("google_owner_sub") { - Ok(s) => Some(s.expose_secret().to_string()), - Err(_) => None, // Entry not found or keyring unavailable - }; - - let init_2fa = matches.get_flag("init-2fa"); - - // 2. Decide if we need to Authenticate - if stored_sub.is_some() || init_2fa { - // Retrieve credentials - let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); - let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); - let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); - - // 3. Fail if credentials are missing but 2FA is required - if google_client_id.is_empty() || google_client_secret.is_empty() { - return Err(anyhow::anyhow!( - "CRITICAL: This filesystem (or your user profile) is bound to Google 2FA, but GOOGLE_CLIENT_ID/SECRET are missing.\n\ - You must provide these environment variables to authenticate." - )); - } - - // 4. Authenticate - let (_valid_refresh_token, subject) = crate::auth::authenticate_google( - google_client_id, - google_client_secret, - stored_sub.clone() - ).await?; - - // 5. Persist the ID if this was the first run - if stored_sub.is_none() { - let secret_sub = SecretString::new(Box::new(subject)); - if let Err(e) = keyring::save(&secret_sub, "google_owner_sub") { - warn!("Failed to save Google Owner ID to keyring: {}", e); - } else { - info!("2FA Identity saved. Future mounts will strictly require Google Auth."); - } - } - } - - // when running from IDE we can't read from stdin with rpassword, get it from env var + let data_path = Path::new(&data_dir); + let mut password = SecretString::from_str( env::var("RENCFS_PASSWORD") .unwrap_or_else(|_| String::new()) .as_str(), ) .unwrap(); + if password.expose_secret().is_empty() { - // read password from stdin print!("Enter password: "); io::stdout().flush().unwrap(); password = SecretString::new(Box::new(read_password()?)); + // confirm password logic for new directories if !PathBuf::new().join(data_dir.clone()).is_dir() || fs::read_dir(&data_dir) .await @@ -342,7 +302,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .unwrap() .is_none() { - // first run, ask to confirm password print!("Confirm password: "); io::stdout().flush().unwrap(); let confirm_password = SecretString::new(Box::new(read_password()?)); @@ -352,19 +311,55 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } } + // save password in keyring info!("Save password in keyring"); let res = keyring::save(&password, "password").map_err(|err| { warn!(err = %err); }); if res.is_err() { - // maybe we don't have a security manager, keep it in mem unsafe { warn!("Cannot save password in keyring, keep it in memory"); PASS = Some(password.clone()); } } + // check 2FA Requirement + let is_bound = EncryptedFs::is_identity_bound(data_path); + let init_2fa = matches.get_flag("init-2fa"); + + if is_bound || init_2fa { + info!("2FA is required. Checking identity..."); + + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + if google_client_id.is_empty() || google_client_secret.is_empty() { + return Err(anyhow::anyhow!("CRITICAL: GOOGLE_CLIENT_ID/SECRET missing.")); + } + + // determine expected User + let expected_sub = if is_bound { + EncryptedFs::get_bound_identity(data_path, &password, cipher)? + } else { + None + }; + + // authenticate + let (_refresh_token, subject) = crate::auth::authenticate_google( + google_client_id, + google_client_secret, + expected_sub + ).await?; + + // bind if this was a new initialization + if !is_bound { + EncryptedFs::verify_or_bind_identity(data_path, &password, cipher, &subject).await?; + } + } + if matches.get_flag("umount-on-start") { let _ = mount::umount(mountpoint.as_str()).map_err(|err| { warn!("Cannot umount, maybe it was not mounted: {err}");