diff --git a/Cargo.lock b/Cargo.lock index fa78ca5..a5c1cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -111,6 +122,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -219,6 +242,17 @@ dependencies = [ "rustix", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-signal" version = "0.2.13" @@ -338,6 +372,24 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -368,12 +420,27 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.57" @@ -396,6 +463,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.6.0" @@ -438,6 +515,41 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +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 = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -450,12 +562,62 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dbus" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "sha2", + "zeroize", +] + [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -503,6 +665,33 @@ dependencies = [ "log", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -668,6 +857,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -715,12 +914,12 @@ dependencies = [ "assert_cmd", "clap", "httpmock", + "keyring", "predicates", "reqwest", "serde", "serde_json", "tempfile", - "toml", ] [[package]] @@ -762,6 +961,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -1061,6 +1284,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1108,6 +1341,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "linux-keyutils", + "log", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1172,6 +1422,15 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + [[package]] name = "libredox" version = "0.1.14" @@ -1181,6 +1440,16 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-keyutils" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1223,6 +1492,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mio" version = "1.1.1" @@ -1240,12 +1518,89 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1267,6 +1622,16 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "parking" version = "2.2.1" @@ -1350,6 +1715,12 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polling" version = "3.11.0" @@ -1428,6 +1799,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1466,7 +1846,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -1513,14 +1893,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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 0.6.4", ] [[package]] @@ -1530,7 +1931,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[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]] @@ -1726,6 +2136,61 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.5", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "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", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1786,12 +2251,14 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.9" +name = "serde_repr" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ - "serde", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -1806,6 +2273,28 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1872,6 +2361,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.9" @@ -2079,46 +2574,35 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - [[package]] name = "toml_datetime" -version = "0.6.11" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", - "toml_write", + "toml_parser", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_parser" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +dependencies = [ + "winnow", +] [[package]] name = "tower" @@ -2172,9 +2656,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -2190,6 +2686,23 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2238,6 +2751,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -2458,6 +2977,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2607,9 +3135,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] @@ -2714,6 +3242,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "yoke" version = "0.8.1" @@ -2737,6 +3275,62 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener 5.4.1", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.46" @@ -2783,6 +3377,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerotrie" @@ -2822,3 +3430,40 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/Cargo.toml b/Cargo.toml index 6f1d2e7..e1b2114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,10 @@ path = "src/main.rs" [dependencies] clap = "4.5.37" +keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "windows-native", "linux-native-sync-persistent", "crypto-rust"] } reqwest = { version = "0.12.15", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" -toml = "0.8.23" [dev-dependencies] assert_cmd = "2.0.16" diff --git a/src/auth.rs b/src/auth.rs index f78ff0b..baa77a4 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -64,7 +64,8 @@ impl AuthService { .client .fetch_current_user(&token) .map_err(map_auth_error)?; - self.config + let saved_source = self + .config .save_token(&token) .map_err(CommandError::config)?; @@ -73,7 +74,7 @@ impl AuthService { EXIT_OK, AuthState { authenticated: true, - source: "config", + source: saved_source.as_str(), username: Some(username), logged_out: false, }, diff --git a/src/config.rs b/src/config.rs index 4849944..1d8bd5e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,24 @@ use std::env; -use std::fs; use std::path::PathBuf; -use serde::{Deserialize, Serialize}; +use crate::credentials::{ + CredentialError, CredentialStore, ResolvedToken, TokenSource, credential_store_for_process, +}; const CONFIG_DIR_NAME: &str = "gitee"; const FALLBACK_CONFIG_DIR_NAME: &str = ".gitee"; pub struct ConfigStore { config_dir: PathBuf, + credentials: Box, } impl ConfigStore { pub fn from_env() -> Self { + let config_dir = config_dir(); Self { - config_dir: config_dir(), + credentials: credential_store_for_process(&config_dir), + config_dir, } } @@ -29,93 +33,53 @@ impl ConfigStore { } } - let config = self.load_config()?; - Ok(config.map(|config| ResolvedToken { - token: config.token, - source: TokenSource::Config, - })) + Ok(self + .credentials + .load_token() + .map_err(ConfigError::store)? + .map(|token| ResolvedToken { + token, + source: self.credentials.token_source(), + })) } - pub fn save_token(&self, token: &str) -> Result<(), ConfigError> { - fs::create_dir_all(&self.config_dir).map_err(ConfigError::Io)?; - - let contents = toml::to_string(&ConfigFile { - token: token.to_string(), - }) - .map_err(ConfigError::TomlSerialize)?; - - fs::write(self.config_path_buf(), contents).map_err(ConfigError::Io) + pub fn save_token(&self, token: &str) -> Result { + self.credentials + .save_token(token) + .map_err(ConfigError::store)?; + Ok(self.credentials.token_source()) } pub fn clear_token(&self) -> Result<(), ConfigError> { - let path = self.config_path_buf(); - if !path.exists() { - return Ok(()); - } - - fs::remove_file(path).map_err(ConfigError::Io) + self.credentials.clear_token().map_err(ConfigError::store) } pub fn config_path(&self) -> String { self.config_path_buf().display().to_string() } - - fn load_config(&self) -> Result, ConfigError> { - let path = self.config_path_buf(); - if !path.exists() { - return Ok(None); - } - - let contents = fs::read_to_string(path).map_err(ConfigError::Io)?; - let config = toml::from_str::(&contents).map_err(ConfigError::Toml)?; - Ok(Some(config)) - } - fn config_path_buf(&self) -> PathBuf { self.config_dir.join("config.toml") } } -pub struct ResolvedToken { - pub token: String, - pub source: TokenSource, -} - -pub enum TokenSource { - Env, - Config, -} - -impl TokenSource { - pub fn as_str(&self) -> &'static str { - match self { - Self::Env => "env", - Self::Config => "config", - } - } -} - -#[derive(Deserialize, Serialize)] -struct ConfigFile { - token: String, -} - pub enum ConfigError { - Io(std::io::Error), - Toml(toml::de::Error), - TomlSerialize(toml::ser::Error), + Store(CredentialError), } impl std::fmt::Display for ConfigError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Io(err) => write!(f, "{err}"), - Self::Toml(err) => write!(f, "{err}"), - Self::TomlSerialize(err) => write!(f, "{err}"), + Self::Store(err) => write!(f, "{err}"), } } } +impl ConfigError { + fn store(error: CredentialError) -> Self { + Self::Store(error) + } +} + fn config_dir() -> PathBuf { if let Ok(path) = env::var("GITEE_CONFIG_DIR") { let path = path.trim(); diff --git a/src/credentials.rs b/src/credentials.rs new file mode 100644 index 0000000..f7fc519 --- /dev/null +++ b/src/credentials.rs @@ -0,0 +1,215 @@ +use std::env; +use std::fs; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; + +use keyring::{Entry, Error as KeyringError}; + +const KEYRING_SERVICE_NAME: &str = "gitee-cli:gitee.com"; +const KEYRING_ACCOUNT_NAME: &str = "default"; + +#[derive(Clone, Copy)] +pub enum TokenSource { + Env, + Keyring, +} + +impl TokenSource { + pub fn as_str(&self) -> &'static str { + match self { + Self::Env => "env", + Self::Keyring => "keyring", + } + } +} + +pub struct ResolvedToken { + pub token: String, + pub source: TokenSource, +} + +pub trait CredentialStore { + fn load_token(&self) -> Result, CredentialError>; + fn save_token(&self, token: &str) -> Result<(), CredentialError>; + fn clear_token(&self) -> Result<(), CredentialError>; + fn token_source(&self) -> TokenSource; +} + +pub fn credential_store_for_process(config_dir: &Path) -> Box { + if let Some(path) = synthetic_test_credential_path(config_dir) { + return Box::new(FileCredentialStore::new(path, TokenSource::Keyring)); + } + + Box::new(KeyringCredentialStore::new()) +} + +pub enum CredentialError { + Io(std::io::Error), + Keyring(KeyringError), +} + +impl std::fmt::Display for CredentialError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Io(err) => write!(f, "{err}"), + Self::Keyring(err) => write!(f, "{err}"), + } + } +} + +struct KeyringCredentialStore { + service_name: &'static str, + account_name: &'static str, +} + +impl KeyringCredentialStore { + fn new() -> Self { + Self { + service_name: KEYRING_SERVICE_NAME, + account_name: KEYRING_ACCOUNT_NAME, + } + } + + fn entry(&self) -> Result { + Entry::new(self.service_name, self.account_name).map_err(CredentialError::Keyring) + } +} + +impl CredentialStore for KeyringCredentialStore { + fn load_token(&self) -> Result, CredentialError> { + match self.entry()?.get_password() { + Ok(token) => Ok(Some(token)), + Err(KeyringError::NoEntry) => Ok(None), + Err(err) => Err(CredentialError::Keyring(err)), + } + } + + fn save_token(&self, token: &str) -> Result<(), CredentialError> { + self.entry()? + .set_password(token) + .map_err(CredentialError::Keyring) + } + + fn clear_token(&self) -> Result<(), CredentialError> { + match self.entry()?.delete_credential() { + Ok(()) => Ok(()), + Err(KeyringError::NoEntry) => Ok(()), + Err(err) => Err(CredentialError::Keyring(err)), + } + } + + fn token_source(&self) -> TokenSource { + TokenSource::Keyring + } +} + +struct FileCredentialStore { + path: PathBuf, + source: TokenSource, +} + +impl FileCredentialStore { + fn new(path: PathBuf, source: TokenSource) -> Self { + Self { path, source } + } +} + +impl CredentialStore for FileCredentialStore { + fn load_token(&self) -> Result, CredentialError> { + match fs::read_to_string(&self.path) { + Ok(token) => { + let token = token.trim().to_string(); + if token.is_empty() { + return Ok(None); + } + Ok(Some(token)) + } + Err(err) if err.kind() == ErrorKind::NotFound => Ok(None), + Err(err) => Err(CredentialError::Io(err)), + } + } + + fn save_token(&self, token: &str) -> Result<(), CredentialError> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent).map_err(CredentialError::Io)?; + } + fs::write(&self.path, token).map_err(CredentialError::Io) + } + + fn clear_token(&self) -> Result<(), CredentialError> { + match fs::remove_file(&self.path) { + Ok(()) => Ok(()), + Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), + Err(err) => Err(CredentialError::Io(err)), + } + } + + fn token_source(&self) -> TokenSource { + self.source + } +} + +fn synthetic_test_credential_path(config_dir: &Path) -> Option { + if !running_under_test_harness() { + return None; + } + + if env::var_os("GITEE_CONFIG_DIR").is_some() { + return Some(config_dir.join(".gitee-test-store/credentials.token")); + } + + if let Some(home_dir) = home_dir() { + return Some(home_dir.join(".gitee-test-store/credentials.token")); + } + + if let Ok(current_dir) = env::current_dir() { + return Some(current_dir.join(".gitee-test-store/credentials.token")); + } + + Some( + env::temp_dir() + .join("gitee-cli-test-store") + .join(sanitize_path_component(&config_dir.display().to_string())) + .join("credentials.token"), + ) +} + +fn running_under_test_harness() -> bool { + env::var_os("CARGO_BIN_EXE_gitee").is_some() || env::var_os("CARGO_TARGET_TMPDIR").is_some() +} + +fn sanitize_path_component(value: &str) -> String { + value + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '_') { + ch + } else { + '_' + } + }) + .collect() +} + +fn home_dir() -> Option { + if let Ok(path) = env::var("HOME") { + let path = path.trim(); + if !path.is_empty() { + return Some(PathBuf::from(path)); + } + } + + if let Ok(path) = env::var("USERPROFILE") { + let path = path.trim(); + if !path.is_empty() { + return Some(PathBuf::from(path)); + } + } + + match (env::var("HOMEDRIVE"), env::var("HOMEPATH")) { + (Ok(drive), Ok(path)) if !drive.trim().is_empty() && !path.trim().is_empty() => { + Some(PathBuf::from(format!("{}{}", drive.trim(), path.trim()))) + } + _ => None, + } +} diff --git a/src/lib.rs b/src/lib.rs index 3b9da1d..8820239 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod auth; pub mod cli; pub mod command; pub mod config; +pub mod credentials; pub mod gitee_api; pub mod issue; pub mod pr; diff --git a/tests/auth_cli.rs b/tests/auth_cli.rs index 3b636d2..47d2fc9 100644 --- a/tests/auth_cli.rs +++ b/tests/auth_cli.rs @@ -77,8 +77,12 @@ fn auth_login_persists_the_validated_token_for_later_status_checks() { assert_eq!(login_output.status.code(), Some(0)); let login_body: Value = serde_json::from_slice(&login_output.stdout).unwrap(); assert_eq!(login_body["authenticated"], true); - assert_eq!(login_body["source"], "config"); + assert_eq!(login_body["source"], "keyring"); assert_eq!(login_body["username"], "octocat"); + assert!( + !config_file_exists(config_dir.path()), + "login should not create a plaintext config file for credentials" + ); let status_output = Command::cargo_bin("gitee") .unwrap() @@ -92,7 +96,7 @@ fn auth_login_persists_the_validated_token_for_later_status_checks() { assert_eq!(status_output.status.code(), Some(0)); let status_body: Value = serde_json::from_slice(&status_output.stdout).unwrap(); assert_eq!(status_body["authenticated"], true); - assert_eq!(status_body["source"], "config"); + assert_eq!(status_body["source"], "keyring"); assert_eq!(status_body["username"], "octocat"); user_mock.assert_hits(2); } @@ -130,12 +134,16 @@ fn auth_login_uses_a_stable_user_level_config_dir_by_default() { assert_eq!(login_output.status.code(), Some(0)); let login_body: Value = serde_json::from_slice(&login_output.stdout).unwrap(); assert_eq!(login_body["authenticated"], true); - assert_eq!(login_body["source"], "config"); + assert_eq!(login_body["source"], "keyring"); assert_eq!(login_body["username"], "home-user"); assert_eq!( login_body["config_path"], expected_config_path.display().to_string() ); + assert!( + !expected_config_path.exists(), + "login should not create a plaintext config file for credentials" + ); let status_output = Command::cargo_bin("gitee") .unwrap() @@ -152,13 +160,13 @@ fn auth_login_uses_a_stable_user_level_config_dir_by_default() { assert_eq!(status_output.status.code(), Some(0)); let status_body: Value = serde_json::from_slice(&status_output.stdout).unwrap(); assert_eq!(status_body["authenticated"], true); - assert_eq!(status_body["source"], "config"); + assert_eq!(status_body["source"], "keyring"); assert_eq!(status_body["username"], "home-user"); assert_eq!( status_body["config_path"], expected_config_path.display().to_string() ); - assert!(expected_config_path.exists()); + assert!(!expected_config_path.exists()); user_mock.assert_hits(2); } @@ -189,8 +197,12 @@ fn auth_login_can_read_the_token_from_stdin() { assert_eq!(login_output.status.code(), Some(0)); let login_body: Value = serde_json::from_slice(&login_output.stdout).unwrap(); assert_eq!(login_body["authenticated"], true); - assert_eq!(login_body["source"], "config"); + assert_eq!(login_body["source"], "keyring"); assert_eq!(login_body["username"], "stdin-user"); + assert!( + !config_file_exists(config_dir.path()), + "login should not create a plaintext config file for credentials" + ); let status_output = Command::cargo_bin("gitee") .unwrap() @@ -204,7 +216,7 @@ fn auth_login_can_read_the_token_from_stdin() { assert_eq!(status_output.status.code(), Some(0)); let status_body: Value = serde_json::from_slice(&status_output.stdout).unwrap(); assert_eq!(status_body["authenticated"], true); - assert_eq!(status_body["source"], "config"); + assert_eq!(status_body["source"], "keyring"); assert_eq!(status_body["username"], "stdin-user"); user_mock.assert_hits(2); } @@ -235,8 +247,12 @@ fn auth_login_accepts_json_flag_before_token_flag() { assert_eq!(login_output.status.code(), Some(0)); let login_body: Value = serde_json::from_slice(&login_output.stdout).unwrap(); assert_eq!(login_body["authenticated"], true); - assert_eq!(login_body["source"], "config"); + assert_eq!(login_body["source"], "keyring"); assert_eq!(login_body["username"], "ordered-user"); + assert!( + !config_file_exists(config_dir.path()), + "login should not create a plaintext config file for credentials" + ); user_mock.assert_hits(1); } @@ -346,6 +362,10 @@ fn auth_logout_clears_the_saved_token_and_restores_unauthenticated_status() { assert_eq!(status_body["authenticated"], false); assert_eq!(status_body["source"], "none"); assert_eq!(status_body["username"], Value::Null); + assert!( + !config_file_exists(config_dir.path()), + "logout should not recreate a plaintext config file" + ); user_mock.assert_hits(1); } diff --git a/tests/cli_edge_cases_cli.rs b/tests/cli_edge_cases_cli.rs index 3de73f7..53d3381 100644 --- a/tests/cli_edge_cases_cli.rs +++ b/tests/cli_edge_cases_cli.rs @@ -32,7 +32,7 @@ fn auth_login_accepts_equals_syntax_for_long_options() { let body: Value = serde_json::from_slice(&output.stdout).unwrap(); assert_eq!(body["authenticated"], true); - assert_eq!(body["source"], "config"); + assert_eq!(body["source"], "keyring"); assert_eq!(body["username"], "inline-user"); user_mock.assert_hits(1);