diff --git a/Cargo.lock b/Cargo.lock index 897f5ca..14ca7f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,28 +3,19 @@ version = 4 [[package]] -name = "addr2line" -version = "0.21.0" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "gimli", + "memchr", ] [[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aho-corasick" -version = "1.1.2" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "atomic-waker" @@ -32,32 +23,11 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" -version = "0.21.5" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -67,9 +37,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -82,30 +52,31 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" -version = "1.5.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ - "libc", + "find-msvc-tools", + "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clipboard-win" @@ -133,9 +104,19 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -143,9 +124,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -158,9 +139,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -197,6 +178,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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.117", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -205,9 +197,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -220,18 +212,18 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -246,27 +238,32 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fb" version = "0.2.3" dependencies = [ + "base64", "console", "dirs", "gumdrop", + "keyring", "once_cell", + "open", "openssl", "pest", "pest_derive", + "rand", "regex", "reqwest", "rustyline", "serde", "serde_json", "serde_yaml", + "sha2", "tokio", "tokio-util", "toml", @@ -280,16 +277,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix", + "rustix 0.38.44", "windows-sys 0.48.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -307,50 +316,50 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-task", "pin-project-lite", - "pin-utils", + "slab", ] [[package]] @@ -365,9 +374,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -375,10 +384,17 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.28.0" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] [[package]] name = "gumdrop" @@ -402,9 +418,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -421,41 +437,49 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] [[package]] -name = "hermit-abi" -version = "0.3.3" +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.5" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "http" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -463,9 +487,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", @@ -476,30 +500,48 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.2.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -518,104 +560,282 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", - "tower", "tower-service", "tracing", + "windows-registry", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[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 = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" -version = "0.4.0" +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 = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.1.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "linux-keyutils", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "zeroize", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags 2.11.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.6.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -623,40 +843,30 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "mio" -version = "0.8.11" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ - "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "tempfile", ] @@ -682,37 +892,29 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "object" -version = "0.32.1" +name = "open" +version = "5.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ - "memchr", + "is-wsl", + "libc", + "pathdiff", ] -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -729,29 +931,29 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -768,9 +970,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -778,39 +980,44 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-link", ] +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", - "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -818,53 +1025,32 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ - "once_cell", "pest", "sha2", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -874,72 +1060,127 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "proc-macro2" -version = "1.0.94" +name = "potential_utf" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "unicode-ident", + "zerovec", ] [[package]] -name = "quote" -version = "1.0.40" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", + "zerocopy", ] [[package]] -name = "radix_trie" -version = "0.2.1" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "endian-type", - "nibble_vec", + "proc-macro2", + "syn 2.0.117", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "proc-macro2" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "bitflags 1.3.2", + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror 1.0.50", + "getrandom 0.2.17", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -949,9 +1190,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -960,87 +1201,136 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.0" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b48d98d932f4ee75e541614d32a7f44c889b72bd9c2e04d95edd135989df88" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", - "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "base64", + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "rustyline" version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.11.0", "cfg-if", "clipboard-win", "fd-lock", @@ -1053,24 +1343,24 @@ dependencies = [ "regex", "scopeguard", "unicode-segmentation", - "unicode-width 0.1.11", + "unicode-width 0.1.14", "utf8parse", "winapi", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1081,12 +1371,25 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", - "core-foundation", + "bitflags 2.11.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1094,50 +1397,68 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.190" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -1156,9 +1477,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.27" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", @@ -1169,55 +1490,71 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "str-buf" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1231,9 +1568,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1242,26 +1579,40 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +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.117", +] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 1.3.2", - "core-foundation", + "bitflags 2.11.0", + "core-foundation 0.9.4", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1269,100 +1620,73 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", - "redox_syscall 0.4.1", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "thiserror" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" -dependencies = [ - "thiserror-impl 1.0.50", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", ] [[package]] name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.50" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[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.38.2" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1375,25 +1699,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.8.8" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -1403,85 +1736,108 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" -version = "0.4.13" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tokio", "tower-layer", "tower-service", - "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -1489,38 +1845,23 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" @@ -1528,21 +1869,34 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.4.1" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1551,11 +1905,17 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[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.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -1580,52 +1940,60 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.88" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.88" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bumpalo", - "log", + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.100", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1633,28 +2001,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.100", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -1682,6 +2087,41 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[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.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1691,6 +2131,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1700,6 +2149,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1823,19 +2281,212 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.19" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.50.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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.117", + "synstructure", ] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[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.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 1b90a4a..0c99282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,8 @@ urlencoding = "2.1" pest = "2.7" pest_derive = "2.7" console = "0.15" +keyring = { version = "3.6.3", features = ["apple-native", "linux-native"] } +rand = "0.8" +sha2 = "0.10" +base64 = "0.22" +open = "5" diff --git a/src/args.rs b/src/args.rs index 9da6c6b..36a3f29 100644 --- a/src/args.rs +++ b/src/args.rs @@ -95,6 +95,10 @@ pub struct Args { #[serde(default)] pub no_spinner: bool, + #[options(no_short, help = "Store secrets in file instead of OS keychain")] + #[serde(default)] + pub no_keyring: bool, + #[options(no_short, help = "Update default configuration values")] #[serde(skip_serializing, skip_deserializing)] pub update_defaults: bool, diff --git a/src/auth.rs b/src/auth.rs index 0bcbb8d..1886233 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,136 +1,427 @@ -use serde::Deserialize; +use base64::{engine::general_purpose, Engine as _}; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; use std::fs; use std::time::SystemTime; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::task; use tokio_util::sync::CancellationToken; -use crate::context::{Context, SavedCredentials, ServiceAccountToken}; -use crate::utils::{credentials_path, format_remaining_time, sa_token_path, spin}; +use crate::context::{AuthMethod, CachedToken, Context, SavedCredentials}; +use crate::utils::{credentials_path, format_remaining_time, secrets_path, spin}; use std::io::{self, Write}; -/// Helper to create an authenticated context from saved credentials -async fn create_context_from_credentials( - host: String, - database: String, - format: String, - no_spinner: bool, -) -> Result> { - let creds_path = credentials_path()?; - if !creds_path.exists() { - return Err("No saved credentials found. Run 'fb auth' first.".into()); +const KEYRING_SERVICE: &str = "fb-cli"; +const BROWSER_CLIENT_ID: &str = "f6ktrYgUuMtzmAw3EXHSsvElt7fdk2Xi"; + +// ─── Cached token JSON (stored in keyring / secrets file) ──────────────────── + +#[derive(Serialize, Deserialize)] +struct CachedTokenJson { + token: String, + until: u64, +} + +// ─── OIDC discovery ─────────────────────────────────────────────────────────── + +#[derive(Deserialize)] +struct OidcConfig { + authorization_endpoint: String, + token_endpoint: String, +} + +async fn discover_oidc_config(oauth_env: &str) -> Result> { + // Browser flow uses idp.*.firebolt.io which supports OIDC discovery. + let url = format!( + "https://idp.{}.firebolt.io/.well-known/openid-configuration", + oauth_env + ); + let client = reqwest::Client::new(); + let resp = client.get(&url).send().await?; + if !resp.status().is_success() { + return Err(format!("OIDC discovery failed ({}): {}", resp.status(), resp.text().await?).into()); } + Ok(resp.json::().await?) +} - let saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; +// ─── Keyring / file-based secret storage ───────────────────────────────────── - let temp_args = crate::args::Args { - command: String::new(), - core: false, - host, - database, - format, - extra: vec![], - label: String::new(), - jwt: String::new(), - sa_id: saved_creds.sa_id, - sa_secret: saved_creds.sa_secret, - account_name: saved_creds.account_name, - jwt_from_file: false, - oauth_env: saved_creds.oauth_env, - verbose: false, - concise: true, - hide_pii: false, - no_spinner, - update_defaults: false, - version: false, - help: false, - query: vec![], +fn secrets_file_read() -> HashMap { + if let Ok(path) = secrets_path() { + if let Ok(content) = fs::read_to_string(&path) { + if let Ok(map) = serde_yaml::from_str::>(&content) { + return map; + } + } + } + HashMap::new() +} + +fn store_secret_in_file(key: &str, value: &str) -> Result<(), Box> { + let mut map = secrets_file_read(); + map.insert(key.to_string(), value.to_string()); + fs::write(secrets_path()?, serde_yaml::to_string(&map)?)?; + Ok(()) +} + +fn load_secret_from_file(key: &str) -> Result, Box> { + Ok(secrets_file_read().get(key).cloned()) +} + +fn delete_secret_from_file(key: &str) { + let mut map = secrets_file_read(); + map.remove(key); + if let Ok(path) = secrets_path() { + let _ = fs::write(path, serde_yaml::to_string(&map).unwrap_or_default()); + } +} + +fn keyring_store(key: &str, value: &str, no_keyring: bool) -> Result<(), Box> { + if no_keyring { + return store_secret_in_file(key, value); + } + keyring::Entry::new(KEYRING_SERVICE, key)?.set_password(value)?; + Ok(()) +} + +fn keyring_load(key: &str, no_keyring: bool) -> Result, Box> { + if no_keyring { + return load_secret_from_file(key); + } + match keyring::Entry::new(KEYRING_SERVICE, key)?.get_password() { + Ok(val) => Ok(Some(val)), + Err(keyring::Error::NoEntry) => Ok(None), + Err(e) => Err(e.into()), + } +} + +fn keyring_delete(key: &str, no_keyring: bool) { + if no_keyring { + delete_secret_from_file(key); + return; + } + if let Ok(entry) = keyring::Entry::new(KEYRING_SERVICE, key) { + let _ = entry.delete_credential(); + } +} + +// ─── PKCE browser flow helpers ──────────────────────────────────────────────── + +async fn wait_for_callback( + listener: tokio::net::TcpListener, +) -> Result> { + let result = tokio::time::timeout(std::time::Duration::from_secs(300), async { + let (stream, _) = listener.accept().await?; + let (reader_half, mut writer_half) = tokio::io::split(stream); + let mut reader = BufReader::new(reader_half); + + let mut first_line = String::new(); + reader.read_line(&mut first_line).await?; + + // Drain request headers + loop { + let mut line = String::new(); + let n = reader.read_line(&mut line).await?; + if n == 0 || line == "\r\n" || line == "\n" { + break; + } + } + + let success_html = concat!( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n", + "

Authentication successful!

", + "

You can close this window.

" + ); + writer_half.write_all(success_html.as_bytes()).await?; + let _ = writer_half.shutdown().await; + + // Parse "GET /callback?code=XXX HTTP/1.1" + let code: Result> = first_line + .split_whitespace() + .nth(1) + .and_then(|path| path.split('?').nth(1)) + .and_then(|query| { + query + .split('&') + .find(|p| p.starts_with("code=")) + .map(|p| p[5..].to_string()) + }) + .map(|c| urlencoding::decode(&c).map(|s| s.into_owned()).unwrap_or(c)) + .ok_or_else(|| "No authorization code in callback".into()); + + code + }) + .await; + + match result { + Ok(inner) => inner, + Err(_) => Err("Authentication timed out (5 minutes)".into()), + } +} + +async fn authenticate_browser( + context: &mut Context, + oidc_config: &OidcConfig, +) -> Result<(), Box> { + let no_keyring = context.args.no_keyring; + + // Generate PKCE code_verifier (64 URL-safe chars) + let code_verifier: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(64) + .map(char::from) + .collect(); + + // code_challenge = BASE64URL_NOPAD(SHA256(verifier)) + let hash = Sha256::digest(code_verifier.as_bytes()); + let code_challenge = general_purpose::URL_SAFE_NO_PAD.encode(hash); + + // Try binding to one of several candidate ports for the callback server. + // Using fixed candidates instead of :0 keeps redirect_uri predictable + // across retries and avoids firewall/browser trust issues with ephemeral ports. + const CALLBACK_PORTS: [u16; 5] = [27847, 29156, 31082, 33491, 35724]; + let listener = { + let mut bound = None; + for &p in &CALLBACK_PORTS { + match tokio::net::TcpListener::bind(("127.0.0.1", p)).await { + Ok(l) => { bound = Some(l); break; } + Err(_) => continue, + } + } + bound.ok_or("All callback ports are busy. Free up one of: 27847, 29156, 31082, 33491, 35724")? }; + let port = listener.local_addr()?.port(); + let redirect_uri = format!("http://localhost:{}/callback", port); + + let auth_url = format!( + "{}?response_type=code&client_id={}&redirect_uri={}&scope={}&code_challenge={}&code_challenge_method=S256&audience=https%3A%2F%2Fapi.firebolt.io", + oidc_config.authorization_endpoint, + urlencoding::encode(BROWSER_CLIENT_ID), + urlencoding::encode(&redirect_uri), + urlencoding::encode("offline_access openid"), + urlencoding::encode(&code_challenge), + ); - let mut context = Context::new(temp_args); - context.update_url(); - authenticate_service_account(&mut context).await?; + println!("Opening browser for authentication..."); + println!("If the browser does not open, visit:\n {}", auth_url); + let _ = open::that(&auth_url); - Ok(context) + println!("Waiting for authentication callback..."); + let code = wait_for_callback(listener).await?; + + // Exchange authorization code for tokens + let mut params = HashMap::new(); + params.insert("grant_type", "authorization_code"); + params.insert("client_id", BROWSER_CLIENT_ID); + params.insert("code", code.as_str()); + params.insert("redirect_uri", redirect_uri.as_str()); + params.insert("code_verifier", code_verifier.as_str()); + + let client = reqwest::Client::new(); + let resp = client + .post(&oidc_config.token_endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .form(¶ms) + .send() + .await?; + + if !resp.status().is_success() { + let text = resp.text().await?; + return Err(format!("Token exchange failed: {}", text).into()); + } + + #[derive(Deserialize)] + struct TokenResponse { + access_token: String, + refresh_token: Option, + expires_in: Option, + } + + let token_resp: TokenResponse = resp.json().await?; + let expires_in = token_resp.expires_in.unwrap_or(1800); + let until = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs() + + expires_in; + + let cached = CachedTokenJson { token: token_resp.access_token.clone(), until }; + keyring_store("browser_access_token", &serde_json::to_string(&cached)?, no_keyring)?; + + if let Some(refresh) = &token_resp.refresh_token { + keyring_store("browser_refresh_token", refresh, no_keyring)?; + } + + context.auth_token = Some(CachedToken { token: token_resp.access_token, until }); + Ok(()) } -pub async fn authenticate_service_account(context: &mut Context) -> Result<(), Box> { - if let Some(sa_token) = &context.sa_token { - let valid_until = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(sa_token.until); - if sa_token.sa_id == context.args.sa_id && - sa_token.sa_secret == context.args.sa_secret && - sa_token.oauth_env == context.args.oauth_env && - valid_until > SystemTime::now() { +async fn refresh_browser_token( + context: &mut Context, + oidc_config: &OidcConfig, +) -> Result<(), Box> { + let no_keyring = context.args.no_keyring; + + let refresh_token = keyring_load("browser_refresh_token", no_keyring)? + .ok_or("Session expired. Please run 'fb auth' to log in again.")?; + + let mut params = HashMap::new(); + params.insert("grant_type", "refresh_token"); + params.insert("client_id", BROWSER_CLIENT_ID); + params.insert("refresh_token", refresh_token.as_str()); + + let client = reqwest::Client::new(); + let resp = client + .post(&oidc_config.token_endpoint) + .header("Content-Type", "application/x-www-form-urlencoded") + .form(¶ms) + .send() + .await?; + + if !resp.status().is_success() { + let text = resp.text().await?; + return Err(format!("Token refresh failed: {}", text).into()); + } + + #[derive(Deserialize)] + struct TokenResponse { + access_token: String, + refresh_token: Option, + expires_in: Option, + } + + let token_resp: TokenResponse = resp.json().await?; + let expires_in = token_resp.expires_in.unwrap_or(1800); + let until = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs() + + expires_in; + + let cached = CachedTokenJson { token: token_resp.access_token.clone(), until }; + keyring_store("browser_access_token", &serde_json::to_string(&cached)?, no_keyring)?; + + if let Some(new_refresh) = &token_resp.refresh_token { + keyring_store("browser_refresh_token", new_refresh, no_keyring)?; + } + + context.auth_token = Some(CachedToken { token: token_resp.access_token, until }); + Ok(()) +} + +async fn authenticate_browser_from_keyring( + context: &mut Context, + oauth_env: &str, +) -> Result<(), Box> { + let no_keyring = context.args.no_keyring; + + // Try cached access token first + if let Some(token_json) = keyring_load("browser_access_token", no_keyring)? { + if let Ok(cached) = serde_json::from_str::(&token_json) { + let valid_until = + SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(cached.until); + // Use with 60-second buffer + if valid_until > SystemTime::now() + std::time::Duration::from_secs(60) { + context.auth_token = + Some(CachedToken { token: cached.token, until: cached.until }); + return Ok(()); + } + } + } + + // Token expired or missing — check refresh token before attempting refresh + if keyring_load("browser_refresh_token", no_keyring)?.is_none() { + return Err("Session expired. Please run 'fb auth' to log in again.".into()); + } + + let oidc = discover_oidc_config(oauth_env).await?; + refresh_browser_token(context, &oidc).await +} + +// ─── Service Account flow ───────────────────────────────────────────────────── + +pub async fn authenticate_service_account( + context: &mut Context, +) -> Result<(), Box> { + let no_keyring = context.args.no_keyring; + + // Check in-memory token + if let Some(token) = &context.auth_token { + let valid_until = + SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(token.until); + if valid_until > SystemTime::now() + std::time::Duration::from_secs(60) { return Ok(()); } } - let args = &mut context.args; + let args = &context.args; if args.sa_id.is_empty() { return Err("Missing Service Account ID (--sa-id)".into()); } - if args.sa_secret.is_empty() { - return Err(format!("Missing Service Account Secret (--sa-secret)").into()); + return Err("Missing Service Account Secret (--sa-secret)".into()); } - if args.oauth_env != "staging" && args.oauth_env != "app" { - return Err(format!("OAuth Env = {:?}, which is not \"staging\" or \"app\"", args.oauth_env).into()); + return Err( + format!("OAuth Env = {:?}, which is not \"staging\" or \"app\"", args.oauth_env) + .into(), + ); } - let auth_url = format!("https://id.{}.firebolt.io/oauth/token", args.oauth_env); - if args.verbose { - eprintln!("Getting auth token for SA ID {:?} from {:?}...", args.sa_id, auth_url); - } + let sa_id = args.sa_id.clone(); - let sa_token_path = sa_token_path()?; - if sa_token_path.exists() { - if let Some(sa_token) = serde_yaml::from_str::>(&fs::read_to_string(&sa_token_path)?)? { - let valid_until = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(sa_token.until); - if sa_token.sa_id == args.sa_id && - sa_token.sa_secret == args.sa_secret && - sa_token.oauth_env == args.oauth_env && - valid_until > SystemTime::now() { + // Check keyring cache + if let Some(token_json) = keyring_load("sa_access_token", no_keyring)? { + if let Ok(cached) = serde_json::from_str::(&token_json) { + let valid_until = + SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(cached.until); + if valid_until > SystemTime::now() + std::time::Duration::from_secs(60) { if args.verbose { eprintln!( - "Using cached SA token from {:?}, valid for {:}", - sa_token_path, + "Using cached access token from keyring, valid for {}", format_remaining_time(valid_until, "more".into())? ); } - - args.jwt.clear(); - context.sa_token = Some(sa_token.clone()); - + context.auth_token = + Some(CachedToken { token: cached.token, until: cached.until }); + context.args.jwt.clear(); return Ok(()); } } } - let mut params = std::collections::HashMap::new(); + // Service Account flow uses id.*.firebolt.io directly (no OIDC discovery). + let auth_url = format!( + "https://id.{}.firebolt.io/oauth/token", + context.args.oauth_env + ); + if context.args.verbose { + eprintln!( + "Getting auth token for SA ID {:?} from {:?}...", + sa_id, auth_url + ); + } + + let mut params = HashMap::new(); params.insert("grant_type", "client_credentials"); params.insert("audience", "https://api.firebolt.io"); - params.insert("client_id", &args.sa_id); - params.insert("client_secret", &args.sa_secret); + params.insert("client_id", context.args.sa_id.as_str()); + params.insert("client_secret", context.args.sa_secret.as_str()); + let async_req = reqwest::Client::new() - .post(auth_url) + .post(&auth_url) .header("Content-Type", "application/x-www-form-urlencoded") .form(¶ms); - if args.verbose { - eprintln!("OAuth request: {:?}", async_req); - } - - let valid_until: SystemTime = SystemTime::now() + std::time::Duration::new(1800, 0); + let valid_until = SystemTime::now() + std::time::Duration::new(1800, 0); let async_resp = async_req.send(); let token = CancellationToken::new(); - let maybe_spin = if args.no_spinner || args.concise { + let maybe_spin = if context.args.no_spinner || context.args.concise { None } else { let token_clone = token.clone(); - Some(task::spawn(async { - spin(token_clone).await; - })) + Some(task::spawn(async { spin(token_clone).await })) }; let response = async_resp.await; @@ -146,57 +437,163 @@ pub async fn authenticate_service_account(context: &mut Context) -> Result<(), B access_token: Option<&'a str>, } let resp_text = resp.text().await?; - if args.verbose { + if context.args.verbose { eprintln!("OAuth response: {:?}", resp_text); } let response: AuthResponse = serde_json::from_str(&resp_text)?; - if !response.access_token.is_some() { - return Err(format!("Failed to authenticate: '{:}'", resp_text).into()); + if response.access_token.is_none() { + return Err(format!("Failed to authenticate: '{}'", resp_text).into()); } - let sa_token = ServiceAccountToken { - sa_id: args.sa_id.clone(), - sa_secret: args.sa_secret.clone(), - token: response.access_token.unwrap().to_string(), - until: valid_until.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), - oauth_env: args.oauth_env.clone(), - }; + let token_str = response.access_token.unwrap().to_string(); + let until = valid_until + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs(); - args.jwt.clear(); - context.sa_token = Some(sa_token.clone()); + // Cache in keyring + let cached_json = CachedTokenJson { token: token_str.clone(), until }; + keyring_store("sa_access_token", &serde_json::to_string(&cached_json)?, no_keyring)?; - fs::write(&sa_token_path, serde_yaml::to_string(&sa_token)?)?; - if args.verbose { + if context.args.verbose { eprintln!( - "SA token saved to {:?}, valid for {:}", - sa_token_path, + "SA token cached in keyring, valid for {}", format_remaining_time(valid_until, "".into())? ); } + + context.args.jwt.clear(); + context.auth_token = Some(CachedToken { token: token_str, until }); } Err(error) => { if context.args.verbose { return Err(format!("Failed to authenticate: {:?}", error).into()); } - - return Err(format!("Failed to authenticate: {}", error.to_string()).into()); + return Err(format!("Failed to authenticate: {}", error).into()); } - }; + } - return Ok(()); + Ok(()) } -pub async fn maybe_authenticate(context: &mut Context) -> Result<(), Box> { - let args = &mut context.args; - if !args.sa_id.is_empty() || !args.sa_secret.is_empty() { +// ─── Maybe authenticate (dispatches based on saved auth method) ─────────────── + +pub async fn maybe_authenticate( + context: &mut Context, +) -> Result<(), Box> { + // Explicit SA credentials on CLI always take priority + if !context.args.sa_id.is_empty() { return authenticate_service_account(context).await; } - return Ok(()); + // Check saved credentials for auth method + let creds_path = credentials_path()?; + if !creds_path.exists() { + return Ok(()); + } + + let saved_creds: SavedCredentials = + match serde_yaml::from_str(&fs::read_to_string(&creds_path)?) { + Ok(c) => c, + Err(_) => return Ok(()), // Old format or unreadable — skip + }; + + match &saved_creds.auth_method { + AuthMethod::ServiceAccount { .. } => { + if !context.args.sa_id.is_empty() { + authenticate_service_account(context).await?; + } + } + AuthMethod::Browser => { + authenticate_browser_from_keyring(context, &saved_creds.oauth_env).await?; + } + } + + Ok(()) +} + +// ─── Account listing / selection ───────────────────────────────────────────── + +#[derive(Deserialize)] +struct Account { + name: String, + region: String, +} + +#[derive(Deserialize)] +struct AccountsResponse { + accounts: Vec, +} + +async fn list_accounts( + access_token: &str, + api_endpoint: &str, +) -> Result, Box> { + let url = format!("https://{}/web/v3/myAccounts", api_endpoint); + let client = reqwest::Client::new(); + let resp = client + .get(&url) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .await?; + + if !resp.status().is_success() { + let text = resp.text().await?; + return Err(format!("Failed to list accounts: {}", text).into()); + } + + Ok(resp.json::().await?.accounts) } -/// Discover system engine URL from account name using the gateway API +async fn select_account_interactive( + access_token: &str, + api_endpoint: &str, +) -> Result> { + let accounts = list_accounts(access_token, api_endpoint).await?; + + if accounts.is_empty() { + return Err("No accounts found for this user.".into()); + } + + if accounts.len() == 1 { + println!("Using account: {} ({})", accounts[0].name, accounts[0].region); + return Ok(accounts[0].name.clone()); + } + + println!("\nAvailable accounts:"); + for (i, account) in accounts.iter().enumerate() { + println!(" {}) {} ({})", i + 1, account.name, account.region); + } + println!(); + + loop { + print!("Select account [1]: "); + io::stdout().flush()?; + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + let input = input.trim(); + + let idx: usize = if input.is_empty() { + 1 + } else { + match input.parse() { + Ok(n) => n, + Err(_) => { + eprintln!("Please enter a number between 1 and {}.", accounts.len()); + continue; + } + } + }; + + if idx >= 1 && idx <= accounts.len() { + return Ok(accounts[idx - 1].name.clone()); + } + eprintln!("Please enter a number between 1 and {}.", accounts.len()); + } +} + +// ─── System engine discovery ────────────────────────────────────────────────── + async fn discover_system_engine_url( account_name: &str, access_token: &str, @@ -230,24 +627,189 @@ async fn discover_system_engine_url( engine_url: String, } - let response_data: EngineUrlResponse = response.json().await?; - Ok(response_data.engine_url) + let data: EngineUrlResponse = response.json().await?; + Ok(data.engine_url) +} + +// ─── Create context from saved credentials (used internally) ───────────────── + +pub async fn create_context_from_credentials( + host: String, + database: String, + format: String, + no_spinner: bool, + no_keyring: bool, +) -> Result> { + let creds_path = credentials_path()?; + if !creds_path.exists() { + return Err("No saved credentials found. Run 'fb auth' first.".into()); + } + + let saved_creds: SavedCredentials = + serde_yaml::from_str(&fs::read_to_string(&creds_path)?) + .map_err(|_| "No valid authentication session found. Run 'fb auth' to set up.")?; + + let mut sa_id = String::new(); + let mut sa_secret = String::new(); + + if let AuthMethod::ServiceAccount { sa_id: id } = &saved_creds.auth_method { + sa_id = id.clone(); + if let Some(secret) = keyring_load("sa_secret", no_keyring)? { + sa_secret = secret; + } + } + + let temp_args = crate::args::Args { + command: String::new(), + core: false, + host, + database, + format, + extra: vec![], + label: String::new(), + jwt: String::new(), + sa_id, + sa_secret, + account_name: saved_creds.account_name.clone(), + jwt_from_file: false, + oauth_env: saved_creds.oauth_env.clone(), + verbose: false, + concise: true, + hide_pii: false, + no_spinner, + no_keyring, + update_defaults: false, + version: false, + help: false, + query: vec![], + }; + + let mut context = Context::new(temp_args); + context.saved_creds = Some(saved_creds); + maybe_authenticate(&mut context).await?; + + Ok(context) } -/// Interactive prompt for authentication setup -pub async fn interactive_auth_setup() -> Result<(), Box> { +// ─── Interactive setup ──────────────────────────────────────────────────────── + +pub async fn interactive_auth_setup( + no_keyring: bool, +) -> Result<(), Box> { println!("Welcome to Firebolt CLI authentication setup!\n"); + println!("How would you like to authenticate?"); + println!(" 1) Browser login (recommended)"); + println!(" 2) Service Account (client_id / client_secret)"); + println!(); + print!("Enter choice [1]: "); + io::stdout().flush()?; + + let mut choice = String::new(); + io::stdin().read_line(&mut choice)?; + let choice = choice.trim().to_string(); + + let oauth_env = "app"; + let api_endpoint = "api.app.firebolt.io"; + + if choice == "2" { + setup_service_account(no_keyring, oauth_env, api_endpoint).await + } else { + setup_browser(no_keyring, oauth_env, api_endpoint).await + } +} + +async fn setup_browser( + no_keyring: bool, + oauth_env: &str, + api_endpoint: &str, +) -> Result<(), Box> { + let oidc = discover_oidc_config(oauth_env).await?; + + let temp_args = crate::args::Args { + command: String::new(), + core: false, + host: api_endpoint.to_string(), + database: String::new(), + format: String::new(), + extra: vec![], + label: String::new(), + jwt: String::new(), + sa_id: String::new(), + sa_secret: String::new(), + account_name: String::new(), + jwt_from_file: false, + oauth_env: oauth_env.to_string(), + verbose: false, + concise: false, + hide_pii: false, + no_spinner: true, + no_keyring, + update_defaults: false, + version: false, + help: false, + query: vec![], + }; + + let mut temp_context = Context::new(temp_args); + authenticate_browser(&mut temp_context, &oidc).await?; + + println!("✓ Authentication successful!"); + + let access_token = temp_context + .auth_token + .as_ref() + .map(|t| t.token.clone()) + .ok_or("Failed to obtain access token")?; + + let account_name = select_account_interactive(&access_token, api_endpoint).await?; + temp_context.args.account_name = account_name.clone(); + + println!("Discovering system engine endpoint..."); + let system_engine_url = + discover_system_engine_url(&account_name, &access_token, api_endpoint).await?; + println!("✓ System engine URL: {}", system_engine_url); + + temp_context.args.host = system_engine_url.clone(); + temp_context.update_url(); + + let (final_host, final_database) = + configure_defaults(&mut temp_context, system_engine_url).await; + + let saved_creds = SavedCredentials { + auth_method: AuthMethod::Browser, + oauth_env: oauth_env.to_string(), + account_name, + host: final_host, + database: final_database.clone(), + }; + + let creds_path = credentials_path()?; + fs::write(&creds_path, serde_yaml::to_string(&saved_creds)?)?; + + println!("\nCredentials saved to {:?}", creds_path); + println!("\n✓ Setup complete! You can now run queries:"); + if final_database.is_some() { + println!(" fb \"select 42\""); + } else { + println!(" fb -d \"select 42\""); + } + + Ok(()) +} +async fn setup_service_account( + no_keyring: bool, + oauth_env: &str, + api_endpoint: &str, +) -> Result<(), Box> { print!("Enter Service Account ID: "); io::stdout().flush()?; let mut sa_id = String::new(); io::stdin().read_line(&mut sa_id)?; let sa_id = sa_id.trim().to_string(); - // Use dialoguer for better password input with visual feedback use console::Term; let term = Term::stderr(); - eprint!("Enter Service Account Secret: "); io::stderr().flush()?; @@ -277,24 +839,12 @@ pub async fn interactive_auth_setup() -> Result<(), Box> } let sa_secret = sa_secret.trim().to_string(); - print!("Enter account name (e.g., my_account): "); - io::stdout().flush()?; - let mut account_name = String::new(); - io::stdin().read_line(&mut account_name)?; - let account_name = account_name.trim().to_string(); - - // Always use "app" environment (production) - let oauth_env = "app"; - let api_endpoint = "api.app.firebolt.io"; - - // Step 1: Authenticate to get access token println!("\nAuthenticating..."); - // Create minimal args for authentication let temp_args = crate::args::Args { command: String::new(), core: false, - host: api_endpoint.to_string(), // Temporarily use API endpoint + host: api_endpoint.to_string(), database: String::new(), format: String::new(), extra: vec![], @@ -302,72 +852,99 @@ pub async fn interactive_auth_setup() -> Result<(), Box> jwt: String::new(), sa_id: sa_id.clone(), sa_secret: sa_secret.clone(), - account_name: account_name.clone(), + account_name: String::new(), jwt_from_file: false, oauth_env: oauth_env.to_string(), verbose: false, concise: false, hide_pii: false, no_spinner: true, + no_keyring, update_defaults: false, version: false, help: false, query: vec![], }; - let mut temp_context = crate::context::Context::new(temp_args); + let mut temp_context = Context::new(temp_args); authenticate_service_account(&mut temp_context).await?; - let access_token = if let Some(token) = &temp_context.sa_token { - token.token.clone() - } else { - return Err("Failed to obtain access token".into()); - }; + let access_token = temp_context + .auth_token + .as_ref() + .map(|t| t.token.clone()) + .ok_or("Failed to obtain access token")?; println!("✓ Authentication successful!"); - // Step 2: Discover system engine URL + // Store SA secret in keyring + keyring_store("sa_secret", &sa_secret, no_keyring)?; + + let account_name = select_account_interactive(&access_token, api_endpoint).await?; + temp_context.args.account_name = account_name.clone(); + println!("Discovering system engine endpoint..."); - let system_engine_url = discover_system_engine_url(&account_name, &access_token, api_endpoint).await?; + let system_engine_url = + discover_system_engine_url(&account_name, &access_token, api_endpoint).await?; println!("✓ System engine URL: {}", system_engine_url); - // Update context with system engine URL temp_context.args.host = system_engine_url.clone(); temp_context.update_url(); - // Step 3: Optional database and engine configuration + let (final_host, final_database) = + configure_defaults(&mut temp_context, system_engine_url).await; + + let saved_creds = SavedCredentials { + auth_method: AuthMethod::ServiceAccount { sa_id: sa_id.clone() }, + oauth_env: oauth_env.to_string(), + account_name, + host: final_host, + database: final_database.clone(), + }; + + let creds_path = credentials_path()?; + fs::write(&creds_path, serde_yaml::to_string(&saved_creds)?)?; + + println!("\nCredentials saved to {:?}", creds_path); + println!("SA secret stored in {}", if no_keyring { "~/.firebolt/fb_secrets" } else { "OS keychain" }); + println!("\n✓ Setup complete! You can now run queries:"); + if final_database.is_some() { + println!(" fb \"select 42\""); + } else { + println!(" fb -d \"select 42\""); + } + + Ok(()) +} + +/// Shared helper: prompt for optional database/engine and return final (host, database). +async fn configure_defaults( + temp_context: &mut Context, + system_engine_url: String, +) -> (Option, Option) { println!("\n(Optional) Configure defaults:"); print!("Default database name [press Enter to skip]: "); - io::stdout().flush()?; + let _ = io::stdout().flush(); let mut database_input = String::new(); - io::stdin().read_line(&mut database_input)?; - let database_name = database_input.trim(); + let _ = io::stdin().read_line(&mut database_input); + let database_name = database_input.trim().to_string(); let final_database = if !database_name.is_empty() { - // Validate database exists println!("Validating database '{}'...", database_name); let check_query = format!( "SELECT catalog_name FROM information_schema.catalogs WHERE catalog_name = '{}'", - database_name.replace("'", "''") + database_name.replace('\'', "''") ); - - match execute_query_internal(&mut temp_context, check_query).await { - Ok(response) => { - if response.contains(database_name) { - println!("✓ Database '{}' validated", database_name); - temp_context.args.database = database_name.to_string(); - temp_context.update_url(); - Some(database_name.to_string()) - } else { - eprintln!("⚠ Warning: Database '{}' does not exist.", database_name); - eprintln!(" Skipping database configuration."); - None - } + match execute_query_internal(temp_context, check_query).await { + Ok(response) if response.contains(&database_name) => { + println!("✓ Database '{}' validated", database_name); + temp_context.args.database = database_name.clone(); + temp_context.update_url(); + Some(database_name) } - Err(e) => { - eprintln!("⚠ Warning: Failed to validate database: {}", e); - eprintln!(" Skipping database configuration."); + _ => { + eprintln!("⚠ Warning: could not validate database '{}'. Skipping.", database_name); None } } @@ -376,45 +953,34 @@ pub async fn interactive_auth_setup() -> Result<(), Box> }; print!("Default engine name [press Enter to skip]: "); - io::stdout().flush()?; + let _ = io::stdout().flush(); let mut engine_input = String::new(); - io::stdin().read_line(&mut engine_input)?; - let engine_name = engine_input.trim(); + let _ = io::stdin().read_line(&mut engine_input); + let engine_name = engine_input.trim().to_string(); let final_host = if !engine_name.is_empty() { - // Validate engine exists println!("Validating engine '{}'...", engine_name); let check_query = format!( "SELECT engine_name FROM information_schema.engines WHERE engine_name = '{}'", - engine_name.replace("'", "''") + engine_name.replace('\'', "''") ); - - match execute_query_internal(&mut temp_context, check_query).await { - Ok(response) => { - if response.contains(engine_name) { - // Engine exists, now resolve its endpoint - println!("Resolving engine '{}' endpoint...", engine_name); - let use_engine_query = format!("USE ENGINE {}", engine_name); - match crate::query::query(&mut temp_context, use_engine_query).await { - Ok(_) => { - println!("✓ Engine '{}' configured: {}", engine_name, temp_context.args.host); - Some(temp_context.args.host.clone()) - } - Err(e) => { - eprintln!("⚠ Warning: Failed to resolve engine '{}' endpoint: {}", engine_name, e); - eprintln!(" Continuing with system engine endpoint."); - Some(system_engine_url) - } + match execute_query_internal(temp_context, check_query).await { + Ok(response) if response.contains(&engine_name) => { + println!("Resolving engine '{}' endpoint...", engine_name); + let use_query = format!("USE ENGINE {}", engine_name); + match crate::query::query(temp_context, use_query).await { + Ok(_) => { + println!("✓ Engine '{}' configured: {}", engine_name, temp_context.args.host); + Some(temp_context.args.host.clone()) + } + Err(e) => { + eprintln!("⚠ Warning: failed to resolve engine endpoint: {}. Using system engine.", e); + Some(system_engine_url) } - } else { - eprintln!("⚠ Warning: Engine '{}' does not exist.", engine_name); - eprintln!(" Continuing with system engine endpoint."); - Some(system_engine_url) } } - Err(e) => { - eprintln!("⚠ Warning: Failed to validate engine: {}", e); - eprintln!(" Continuing with system engine endpoint."); + _ => { + eprintln!("⚠ Warning: could not validate engine '{}'. Using system engine.", engine_name); Some(system_engine_url) } } @@ -422,32 +988,11 @@ pub async fn interactive_auth_setup() -> Result<(), Box> Some(system_engine_url) }; - let saved_creds = SavedCredentials { - sa_id, - sa_secret, - oauth_env: oauth_env.to_string(), - account_name, - host: final_host, - database: final_database, - }; - - let creds_path = credentials_path()?; - fs::write(&creds_path, serde_yaml::to_string(&saved_creds)?)?; - - println!("\nCredentials saved to {:?}", creds_path); - println!("\n✓ Setup complete! You can now run queries:"); - if saved_creds.database.is_some() { - println!(" fb \"select 42\""); - println!(" fb \"select * from my_table\""); - } else { - println!(" fb -d \"select 42\""); - println!(" fb -d \"select * from my_table\""); - } - - Ok(()) + (final_host, final_database) } -/// Helper function to execute a query and return the response text (doesn't print to stdout) +// ─── Execute query internally (no stdout, returns response text) ────────────── + async fn execute_query_internal( context: &mut Context, query_text: String, @@ -465,8 +1010,10 @@ async fn execute_query_internal( .header("Firebolt-Protocol-Version", FIREBOLT_PROTOCOL_VERSION) .body(query_text); - if let Some(sa_token) = &context.sa_token { - request = request.header("authorization", format!("Bearer {}", sa_token.token)); + if let Some(token) = context.access_token() { + request = request.header("authorization", format!("Bearer {}", token)); + } else if !context.args.jwt.is_empty() { + request = request.header("authorization", format!("Bearer {}", context.args.jwt)); } let response = request.send().await?; @@ -480,18 +1027,19 @@ async fn execute_query_internal( Ok(response.text().await?) } -/// Set default database in saved credentials (with validation) -pub async fn set_default_database(database_name: String) -> Result<(), Box> { +// ─── Set default database / engine ─────────────────────────────────────────── + +pub async fn set_default_database( + database_name: String, + no_keyring: bool, +) -> Result<(), Box> { let creds_path = credentials_path()?; - let saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; + let saved_creds: SavedCredentials = + serde_yaml::from_str(&fs::read_to_string(&creds_path)?) + .map_err(|_| "No valid authentication session found. Run 'fb auth' to set up.")?; - // Get system engine host for querying information_schema let system_engine_host = if let Some(host) = &saved_creds.host { - if let Some(pos) = host.find("?engine=") { - host[..pos].to_string() - } else { - host.clone() - } + if let Some(pos) = host.find("?engine=") { host[..pos].to_string() } else { host.clone() } } else { return Err("No host configured. Run 'fb auth' to set up credentials.".into()); }; @@ -501,129 +1049,89 @@ pub async fn set_default_database(database_name: String) -> Result<(), Box { - // Check if response contains the database name (simple check - response will have header + data row if exists) - if response.contains(&database_name) { - // Database exists! Save it to credentials - let mut updated_creds = saved_creds; - updated_creds.database = Some(database_name.clone()); - - fs::write(&creds_path, serde_yaml::to_string(&updated_creds)?)?; - println!("✓ Default database set to: {}", database_name); - Ok(()) - } else { - eprintln!("Database '{}' does not exist.", database_name); - eprintln!("Run 'fb show databases' to see available databases."); - Err("Database validation failed".into()) - } + Ok(response) if response.contains(&database_name) => { + let mut updated_creds = saved_creds; + updated_creds.database = Some(database_name.clone()); + fs::write(&creds_path, serde_yaml::to_string(&updated_creds)?)?; + println!("✓ Default database set to: {}", database_name); + Ok(()) } - Err(e) => { - Err(format!("Failed to validate database: {}", e).into()) + _ => { + eprintln!("Database '{}' does not exist.", database_name); + eprintln!("Run 'fb show databases' to see available databases."); + Err("Database validation failed".into()) } } } -/// Set default engine in saved credentials by running USE ENGINE and capturing endpoint -pub async fn set_default_engine(engine_name: String) -> Result<(), Box> { +pub async fn set_default_engine( + engine_name: String, + no_keyring: bool, +) -> Result<(), Box> { let creds_path = credentials_path()?; if !creds_path.exists() { return Err("No saved credentials found. Run 'fb auth' first.".into()); } - let mut saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; + let mut saved_creds: SavedCredentials = + serde_yaml::from_str(&fs::read_to_string(&creds_path)?) + .map_err(|_| "No valid authentication session found. Run 'fb auth' to set up.")?; - // Need to get the system engine URL first let system_engine_host = if let Some(host) = &saved_creds.host { - // If the host contains ?engine=, strip it to get system engine - if let Some(pos) = host.find("?engine=") { - host[..pos].to_string() - } else { - host.clone() - } + if let Some(pos) = host.find("?engine=") { host[..pos].to_string() } else { host.clone() } } else { - // No host saved, need to discover it - println!("Discovering system engine endpoint..."); - - let api_endpoint = "api.app.firebolt.io"; - let temp_context = create_context_from_credentials( - api_endpoint.to_string(), - String::new(), - String::new(), - true, - ) - .await?; - - let access_token = if let Some(token) = &temp_context.sa_token { - token.token.clone() - } else { - return Err("Failed to obtain access token".into()); - }; - - let system_url = - discover_system_engine_url(&saved_creds.account_name, &access_token, api_endpoint) - .await?; - println!("✓ System engine: {}", system_url); - system_url + return Err("No host configured. Run 'fb auth' first.".into()); }; - // Create context with system engine to run USE ENGINE let mut temp_context = create_context_from_credentials( - system_engine_host.clone(), + system_engine_host, saved_creds.database.clone().unwrap_or_default(), String::new(), true, + no_keyring, ) .await?; - // First, validate that the engine exists by querying information_schema.engines println!("Validating engine '{}'...", engine_name); let check_query = format!( "SELECT engine_name FROM information_schema.engines WHERE engine_name = '{}'", - engine_name.replace("'", "''") // Escape single quotes + engine_name.replace('\'', "''") ); match execute_query_internal(&mut temp_context, check_query).await { - Ok(response) => { - // Check if response contains the engine name - if !response.contains(&engine_name) { - eprintln!("Engine '{}' does not exist.", engine_name); - eprintln!("Run 'fb show engines' to see available engines."); - return Err("Engine validation failed".into()); - } - } - Err(e) => { - return Err(format!("Failed to validate engine: {}", e).into()); + Ok(response) if response.contains(&engine_name) => {} + _ => { + eprintln!("Engine '{}' does not exist.", engine_name); + eprintln!("Run 'fb show engines' to see available engines."); + return Err("Engine validation failed".into()); } } - // Engine exists! Now run USE ENGINE to get the endpoint println!("Resolving engine '{}' endpoint...", engine_name); let use_engine_query = format!("USE ENGINE {}", engine_name); crate::query::query(&mut temp_context, use_engine_query).await?; - // The query function will have updated temp_context.args.host with the engine endpoint saved_creds.host = Some(temp_context.args.host.clone()); - fs::write(&creds_path, serde_yaml::to_string(&saved_creds)?)?; println!("✓ Default engine set to: {} ({})", engine_name, temp_context.args.host); Ok(()) } -/// Display current authentication status -pub fn show_auth_status() -> Result<(), Box> { +// ─── Auth status, clear, token ─────────────────────────────────────────────── + +pub fn show_auth_status(no_keyring: bool) -> Result<(), Box> { let creds_path = credentials_path()?; if !creds_path.exists() { println!("No saved credentials found."); @@ -631,37 +1139,49 @@ pub fn show_auth_status() -> Result<(), Box> { return Ok(()); } - let saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; + let saved_creds: SavedCredentials = match serde_yaml::from_str(&fs::read_to_string(&creds_path)?) { + Ok(c) => c, + Err(_) => { + println!("No valid authentication session found."); + println!("Run 'fb auth' to set up authentication."); + return Ok(()); + } + }; - println!("Authenticated as: Service Account"); - println!(" ID: {}", saved_creds.sa_id); + match &saved_creds.auth_method { + AuthMethod::ServiceAccount { sa_id } => { + println!("Authenticated as: Service Account"); + println!(" ID: {}", sa_id); + } + AuthMethod::Browser => { + println!("Authenticated as: Browser login"); + } + } println!(" Account: {}", saved_creds.account_name); println!(" Environment: {}", saved_creds.oauth_env); - // Check if token is cached and valid - if let Ok(sa_token_path) = sa_token_path() { - if sa_token_path.exists() { - if let Ok(content) = fs::read_to_string(&sa_token_path) { - if let Ok(Some(token)) = - serde_yaml::from_str::>(&content) - { - let valid_until = SystemTime::UNIX_EPOCH - + std::time::Duration::from_secs(token.until); - if valid_until > SystemTime::now() { - println!( - " Token valid for: {}", - format_remaining_time(valid_until, "".into())? - ); - } else { - println!(" Token: expired"); - } - } + // Show cached token expiry + let token_key = match &saved_creds.auth_method { + AuthMethod::Browser => "browser_access_token", + AuthMethod::ServiceAccount { .. } => "sa_access_token", + }; + if let Ok(Some(token_json)) = keyring_load(token_key, no_keyring) { + if let Ok(cached) = serde_json::from_str::(&token_json) { + let valid_until = + SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(cached.until); + if valid_until > SystemTime::now() { + println!( + " Token valid for: {}", + format_remaining_time(valid_until, "".into())? + ); + } else { + println!(" Token: expired"); } } } if let Some(host) = &saved_creds.host { - println!(" System engine: {}", host); + println!(" Endpoint: {}", host); } if let Some(database) = &saved_creds.database { println!(" Default database: {}", database); @@ -670,8 +1190,7 @@ pub fn show_auth_status() -> Result<(), Box> { Ok(()) } -/// Clear saved credentials -pub fn clear_auth() -> Result<(), Box> { +pub fn clear_auth(no_keyring: bool) -> Result<(), Box> { let creds_path = credentials_path()?; if creds_path.exists() { fs::remove_file(&creds_path)?; @@ -679,39 +1198,118 @@ pub fn clear_auth() -> Result<(), Box> { } else { println!("No saved credentials to clear."); } + + // Clear keyring / secrets file + keyring_delete("sa_secret", no_keyring); + keyring_delete("sa_access_token", no_keyring); + keyring_delete("browser_access_token", no_keyring); + keyring_delete("browser_refresh_token", no_keyring); + // Legacy keys (migration cleanup) + keyring_delete("access_token", no_keyring); + keyring_delete("refresh_token", no_keyring); + Ok(()) } -/// Load saved credentials into Args -pub fn load_saved_credentials( - args: &mut crate::args::Args, +pub async fn print_access_token( + no_keyring: bool, ) -> Result<(), Box> { let creds_path = credentials_path()?; if !creds_path.exists() { - return Ok(()); // No saved credentials + eprintln!("No saved credentials found. Run 'fb auth' first."); + std::process::exit(1); } - let saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; + let saved_creds: SavedCredentials = match serde_yaml::from_str(&fs::read_to_string(&creds_path)?) { + Ok(c) => c, + Err(_) => { + eprintln!("No valid authentication session found. Run 'fb auth' first."); + std::process::exit(1); + } + }; - // Only apply if not explicitly provided on command line - if args.sa_id.is_empty() { - args.sa_id = saved_creds.sa_id; + // Build a minimal context to use the auth machinery + let host = saved_creds.host.clone().unwrap_or_default(); + let context = create_context_from_credentials( + host, + String::new(), + String::new(), + true, + no_keyring, + ) + .await + .map_err(|e| { + eprintln!("Failed to authenticate: {}", e); + std::process::exit(1); + }) + .unwrap(); + + if let Some(token) = context.access_token() { + print!("{}", token); + return Ok(()); } - if args.sa_secret.is_empty() { - args.sa_secret = saved_creds.sa_secret; + + // For browser mode, the token may need to come from keyring directly + let token_key = match &saved_creds.auth_method { + AuthMethod::Browser => "browser_access_token", + AuthMethod::ServiceAccount { .. } => "sa_access_token", + }; + if let Some(token_json) = keyring_load(token_key, no_keyring)? { + if let Ok(cached) = serde_json::from_str::(&token_json) { + print!("{}", cached.token); + return Ok(()); + } } - // Always use the saved oauth_env - args.oauth_env = saved_creds.oauth_env; - // Apply default host/database if saved + eprintln!("No access token available. Run 'fb auth' first."); + std::process::exit(1); +} + +// ─── Load saved credentials into Args ──────────────────────────────────────── + +pub fn load_saved_credentials( + args: &mut crate::args::Args, +) -> Result<(), Box> { + let creds_path = credentials_path()?; + if !creds_path.exists() { + return Ok(()); + } + + let saved_creds: SavedCredentials = + match serde_yaml::from_str(&fs::read_to_string(&creds_path)?) { + Ok(c) => c, + Err(_) => return Ok(()), // Old format — skip, don't crash + }; + + // Apply saved host/database if not overridden if args.host.is_empty() { - if let Some(host) = saved_creds.host { - args.host = host; + if let Some(host) = &saved_creds.host { + args.host = host.clone(); } } if args.database.is_empty() { - if let Some(database) = saved_creds.database { - args.database = database; + if let Some(database) = &saved_creds.database { + args.database = database.clone(); + } + } + + args.oauth_env = saved_creds.oauth_env.clone(); + args.account_name = saved_creds.account_name.clone(); + + match &saved_creds.auth_method { + AuthMethod::ServiceAccount { sa_id } => { + if args.sa_id.is_empty() { + args.sa_id = sa_id.clone(); + } + // Load SA secret from keyring if not provided on CLI + if args.sa_secret.is_empty() { + if let Ok(Some(secret)) = keyring_load("sa_secret", args.no_keyring) { + args.sa_secret = secret; + } + } + } + AuthMethod::Browser => { + // Nothing extra needed in args for browser mode } } @@ -724,5 +1322,5 @@ pub fn load_saved_credentials( #[cfg(test)] mod tests { - // Add tests for authentication functionality when possible + // Integration tests for authentication require a running Firebolt instance. } diff --git a/src/context.rs b/src/context.rs index c2746da..7874c92 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,34 +1,35 @@ use crate::args::{get_url, Args}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ServiceAccountToken { - pub sa_id: String, - pub sa_secret: String, - pub token: String, - pub until: u64, - #[serde(default = "default_oauth_env")] - pub oauth_env: String, +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "auth_method")] +pub enum AuthMethod { + #[serde(rename = "service_account")] + ServiceAccount { sa_id: String }, + #[serde(rename = "browser")] + Browser, } -fn default_oauth_env() -> String { - "app".to_string() -} - -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SavedCredentials { - pub sa_id: String, - pub sa_secret: String, + pub auth_method: AuthMethod, pub oauth_env: String, pub account_name: String, pub host: Option, pub database: Option, } +#[derive(Clone, Debug)] +pub struct CachedToken { + pub token: String, + pub until: u64, // expiry as unix epoch seconds +} + pub struct Context { pub args: Args, pub url: String, - pub sa_token: Option, + pub auth_token: Option, + pub saved_creds: Option, pub prompt1: Option, pub prompt2: Option, pub prompt3: Option, @@ -37,13 +38,17 @@ pub struct Context { impl Context { pub fn new(args: Args) -> Self { let url = get_url(&args); - Self { args, url, sa_token: None, prompt1: None, prompt2: None, prompt3: None } + Self { args, url, auth_token: None, saved_creds: None, prompt1: None, prompt2: None, prompt3: None } } pub fn update_url(&mut self) { self.url = get_url(&self.args); } + pub fn access_token(&self) -> Option<&str> { + self.auth_token.as_ref().map(|t| t.token.as_str()) + } + pub fn set_prompt1(&mut self, prompt: String) { self.prompt1 = Some(prompt); } @@ -71,6 +76,6 @@ mod tests { assert!(context.url.contains("localhost:8123")); assert!(context.url.contains("database=test_db")); - assert!(context.sa_token.is_none()); + assert!(context.auth_token.is_none()); } } diff --git a/src/main.rs b/src/main.rs index e297f55..ab3463d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ fn print_help() { println!(" fb auth Interactive authentication setup"); println!(" fb auth check Show authentication status"); println!(" fb auth clear Clear saved credentials"); + println!(" fb auth token Print current access token"); println!(); println!("CONFIGURATION:"); println!(" fb use database Set default database"); @@ -57,6 +58,7 @@ fn print_help() { println!(" --verbose Enable verbose output"); println!(" --concise Suppress time statistics"); println!(" --no-spinner Disable spinner"); + println!(" --no-keyring Store secrets in file instead of OS keychain"); println!(" --version Print version"); println!(" --help Show this help message"); println!(); @@ -99,18 +101,18 @@ async fn main() -> Result<(), Box> { // Check for auth subcommands (use positional words instead of flags) if args.query.len() > 1 { match args.query[1].as_str() { - "check" | "status" => return auth::show_auth_status(), - "clear" | "logout" => return auth::clear_auth(), + "check" | "status" => return auth::show_auth_status(args.no_keyring), + "clear" | "logout" => return auth::clear_auth(args.no_keyring), + "token" => return auth::print_access_token(args.no_keyring).await, _ => { eprintln!("Unknown auth subcommand: {}", args.query[1]); - eprintln!("Available: fb auth check, fb auth clear"); + eprintln!("Available: fb auth check, fb auth clear, fb auth token"); std::process::exit(1); } } } - // Interactive mode only - return auth::interactive_auth_setup().await; + return auth::interactive_auth_setup(args.no_keyring).await; } // Handle 'use' subcommand for setting database/engine @@ -124,11 +126,11 @@ async fn main() -> Result<(), Box> { match args.query[1].as_str() { "database" => { let database_name = args.query[2].clone(); - return auth::set_default_database(database_name).await; + return auth::set_default_database(database_name, args.no_keyring).await; } "engine" => { let engine_name = args.query[2].clone(); - return auth::set_default_engine(engine_name).await; + return auth::set_default_engine(engine_name, args.no_keyring).await; } _ => { eprintln!("Unknown use target: {}", args.query[1]); diff --git a/src/query.rs b/src/query.rs index 4b3e0ef..d68b908 100644 --- a/src/query.rs +++ b/src/query.rs @@ -7,7 +7,7 @@ use tokio::{select, signal, task}; use tokio_util::sync::CancellationToken; use crate::args::normalize_extras; -use crate::auth::authenticate_service_account; +use crate::auth::maybe_authenticate; use crate::context::Context; use crate::utils::spin; use crate::FIREBOLT_PROTOCOL_VERSION; @@ -88,9 +88,7 @@ pub async fn query(context: &mut Context, query_text: String) -> Result<(), Box< return Ok(()); } - if !context.args.sa_id.is_empty() || !context.args.sa_secret.is_empty() { - authenticate_service_account(context).await?; - } + maybe_authenticate(context).await?; if context.args.verbose { eprintln!("URL: {}", context.url); @@ -110,11 +108,9 @@ pub async fn query(context: &mut Context, query_text: String) -> Result<(), Box< .header("Firebolt-Protocol-Version", FIREBOLT_PROTOCOL_VERSION) .body(query_text); - if let Some(sa_token) = &context.sa_token { - request = request.header("authorization", format!("Bearer {}", sa_token.token)); - } - - if !context.args.jwt.is_empty() { + if let Some(token) = context.access_token() { + request = request.header("authorization", format!("Bearer {}", token)); + } else if !context.args.jwt.is_empty() { request = request.header("authorization", format!("Bearer {}", context.args.jwt)); } diff --git a/src/show.rs b/src/show.rs index 2687f03..fe63410 100644 --- a/src/show.rs +++ b/src/show.rs @@ -1,5 +1,3 @@ -use crate::auth::authenticate_service_account; -use crate::context::{Context, SavedCredentials}; use crate::utils::credentials_path; use std::fs; @@ -7,15 +5,16 @@ use std::fs; async fn create_query_context( database: Option, format: Option, -) -> Result> { +) -> Result> { let creds_path = credentials_path()?; if !creds_path.exists() { return Err("No saved credentials found. Run 'fb auth' first.".into()); } - let saved_creds: SavedCredentials = serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; + let saved_creds: crate::context::SavedCredentials = + serde_yaml::from_str(&fs::read_to_string(&creds_path)?)?; - // Get system engine host + // Use system engine host (strip any ?engine= query param) let system_engine_host = if let Some(host) = &saved_creds.host { if let Some(pos) = host.find("?engine=") { host[..pos].to_string() @@ -26,37 +25,14 @@ async fn create_query_context( return Err("No host configured. Run 'fb auth' to set up credentials.".into()); }; - let temp_args = crate::args::Args { - command: String::new(), - core: false, - host: system_engine_host, - database: database.unwrap_or_default(), - format: format.unwrap_or_else(|| String::from("PSQL")), - extra: vec![], - label: String::new(), - jwt: String::new(), - sa_id: saved_creds.sa_id.clone(), - sa_secret: saved_creds.sa_secret.clone(), - account_name: saved_creds.account_name.clone(), - jwt_from_file: false, - oauth_env: saved_creds.oauth_env.clone(), - verbose: false, - concise: false, - hide_pii: false, - no_spinner: false, - update_defaults: false, - version: false, - help: false, - query: vec![], - }; - - let mut context = Context::new(temp_args); - context.update_url(); - - // Authenticate - authenticate_service_account(&mut context).await?; - - Ok(context) + crate::auth::create_context_from_credentials( + system_engine_host, + database.unwrap_or_default(), + format.unwrap_or_else(|| String::from("PSQL")), + false, + false, + ) + .await } /// Show available databases diff --git a/src/utils.rs b/src/utils.rs index 5214eef..5c476b6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -27,9 +27,9 @@ pub fn history_path() -> Result> { Ok(init_root_path()?.join("fb_history")) } -// Get sa_token path on disk. -pub fn sa_token_path() -> Result> { - Ok(init_root_path()?.join("fb_sa_token")) +// Get secrets file path on disk (used with --no-keyring). +pub fn secrets_path() -> Result> { + Ok(init_root_path()?.join("fb_secrets")) } // Get credentials storage path on disk. @@ -85,7 +85,7 @@ mod tests { let history = history_path().unwrap(); assert!(history.ends_with("fb_history")); - let sa_token = sa_token_path().unwrap(); - assert!(sa_token.ends_with("fb_sa_token")); + let secrets = secrets_path().unwrap(); + assert!(secrets.ends_with("fb_secrets")); } }