重构: secret纯密钥逻辑下沉df-storage(方案B避循环依赖,12调用点零改)
This commit is contained in:
310
Cargo.lock
generated
310
Cargo.lock
generated
@@ -97,6 +97,17 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-fs"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
|
||||||
|
dependencies = [
|
||||||
|
"async-lock",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-io"
|
name = "async-io"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
@@ -465,6 +476,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.45"
|
version = "0.4.45"
|
||||||
@@ -848,6 +865,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"df-core",
|
"df-core",
|
||||||
|
"keyring",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2182,10 +2200,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"linux-keyutils",
|
||||||
"log",
|
"log",
|
||||||
|
"secret-service",
|
||||||
"security-framework 2.11.1",
|
"security-framework 2.11.1",
|
||||||
"security-framework 3.7.0",
|
"security-framework 3.7.0",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
|
"zbus 4.4.0",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2264,6 +2285,16 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-keyutils"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -2418,6 +2449,19 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -2428,12 +2472,76 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2948,6 +3056,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "precomputed-hash"
|
name = "precomputed-hash"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -3056,6 +3173,36 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -3408,6 +3555,21 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "secret-service"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"generic-array",
|
||||||
|
"num",
|
||||||
|
"once_cell",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"zbus 4.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
@@ -3643,6 +3805,17 @@ dependencies = [
|
|||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
@@ -3758,6 +3931,12 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -4127,7 +4306,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows",
|
||||||
"zbus",
|
"zbus 5.16.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5728,6 +5907,16 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -5751,6 +5940,44 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||||
|
dependencies = [
|
||||||
|
"async-broadcast",
|
||||||
|
"async-executor",
|
||||||
|
"async-fs",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-process",
|
||||||
|
"async-recursion",
|
||||||
|
"async-task",
|
||||||
|
"async-trait",
|
||||||
|
"blocking",
|
||||||
|
"enumflags2",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"hex",
|
||||||
|
"nix",
|
||||||
|
"ordered-stream",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"sha1",
|
||||||
|
"static_assertions",
|
||||||
|
"tracing",
|
||||||
|
"uds_windows",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
"xdg-home",
|
||||||
|
"zbus_macros 4.4.0",
|
||||||
|
"zbus_names 3.0.0",
|
||||||
|
"zvariant 4.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.16.0"
|
version = "5.16.0"
|
||||||
@@ -5781,9 +6008,22 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"winnow 1.0.3",
|
"winnow 1.0.3",
|
||||||
"zbus_macros",
|
"zbus_macros 5.16.0",
|
||||||
"zbus_names",
|
"zbus_names 4.3.2",
|
||||||
"zvariant",
|
"zvariant 5.12.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_macros"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate 3.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"zvariant_utils 2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5796,9 +6036,20 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
"zbus_names",
|
"zbus_names 4.3.2",
|
||||||
"zvariant",
|
"zvariant 5.12.0",
|
||||||
"zvariant_utils",
|
"zvariant_utils 3.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 4.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5809,7 +6060,7 @@ checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"winnow 1.0.3",
|
"winnow 1.0.3",
|
||||||
"zvariant",
|
"zvariant 5.12.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5898,6 +6149,19 @@ version = "1.0.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
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 4.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.12.0"
|
version = "5.12.0"
|
||||||
@@ -5908,8 +6172,21 @@ dependencies = [
|
|||||||
"enumflags2",
|
"enumflags2",
|
||||||
"serde",
|
"serde",
|
||||||
"winnow 1.0.3",
|
"winnow 1.0.3",
|
||||||
"zvariant_derive",
|
"zvariant_derive 5.12.0",
|
||||||
"zvariant_utils",
|
"zvariant_utils 3.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_derive"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate 3.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"zvariant_utils 2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5922,7 +6199,18 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
"zvariant_utils",
|
"zvariant_utils 3.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -17,3 +17,8 @@ uuid = { version = "1", features = ["v4"] }
|
|||||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
# keyring v3 默认不带任何 platform 后端,必须显式启 feature——
|
||||||
|
# 否则 Entry 走 noop store:set_password 静默成功不持久化,get_password 永远空。
|
||||||
|
# Win+mac 启用原生后端;linux 用 async-persistent 变体避同步阻塞 Secret Service。
|
||||||
|
# 由 df-storage(密钥解析下沉层) + src-tauri(build_provider_for 转发壳)共同引用。
|
||||||
|
keyring = { version = "3", features = ["windows-native", "apple-native", "linux-native-async-persistent"] }
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ anyhow = { workspace = true }
|
|||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
rusqlite = { workspace = true }
|
rusqlite = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
# keyring:FR-S1 密钥解析下沉,纯密钥逻辑(get/set/delete/resolve/ensure/migrate)的唯一源。
|
||||||
|
# build_provider_for 不下沉(依赖 df-ai 会引循环),留 src-tauri 转发壳。
|
||||||
|
keyring = { workspace = true }
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ pub mod crud;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod secret;
|
||||||
|
|||||||
237
crates/df-storage/src/secret.rs
Normal file
237
crates/df-storage/src/secret.rs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
//! FR-S1 api_key 密钥管理 — 真实密钥存 OS keyring,DB `api_key` 列迁移后存空串。
|
||||||
|
//!
|
||||||
|
//! **下沉层(方案 B,2026-06-16)**:原位于 `src-tauri/src/commands/ai/secret.rs`,
|
||||||
|
//! 下沉纯密钥逻辑(get/set/delete/resolve/ensure/migrate + failcount sidecar)到 df-storage,
|
||||||
|
//! 供 df-nodes AiNode 与 src-tauri 转发壳共用(密钥解析唯一源,DRY)。
|
||||||
|
//!
|
||||||
|
//! **不下沉项**:`build_provider_for`(依赖 `df_ai::build_provider`)——df-storage 不依赖
|
||||||
|
//! df-ai,下沉会引 df-storage→df-ai 反向依赖。`build_provider_for` 留 src-tauri 转发壳,
|
||||||
|
//! 内部调本模块 `resolve_provider_secret` + `ensure_resolved_key` + `df_ai::build_provider`。
|
||||||
|
//!
|
||||||
|
//! 设计:
|
||||||
|
//! - keyring entry: service=`devflow-ai-provider`, username=provider_id
|
||||||
|
//! - DB `api_key` 列恒空(迁移后/新建均空),真实密钥唯一源 = OS keyring
|
||||||
|
//! - 启动一次性迁移:`migrate_secrets_to_keyring` 读老明文 → keyring → DB 置空(失败保留明文下次重试)
|
||||||
|
//! - 消费点(build_provider)经 `resolve_provider_secret` 取:DB 优先,fallback keyring(兼容未迁移)
|
||||||
|
//! - 跨平台:Windows Credential Manager / macOS Keychain / Linux Secret Service
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::crud::AiProviderRepo;
|
||||||
|
use crate::models::AiProviderRecord;
|
||||||
|
use keyring::Entry;
|
||||||
|
|
||||||
|
const KEYRING_SERVICE: &str = "devflow-ai-provider";
|
||||||
|
|
||||||
|
/// 迁移失败计数器阈值:同一 provider 累计失败到此次数 → 升级为 warn 提示明文密钥长期滞留风险。
|
||||||
|
/// 跨启动持久化(sidecar 文件),计数仅用于告警,不影响兼容时序(不强制迁移、不删明文)。
|
||||||
|
const MIGRATION_FAIL_THRESHOLD: u32 = 3;
|
||||||
|
|
||||||
|
/// 迁移失败计数 sidecar 文件(<cwd>/.devflow-keyring-failcount):逐行 `provider_id=count`。
|
||||||
|
/// cwd 未必是稳定路径,但 R-PD-4 目标仅是「检测到反复失败/滞留时告警」,误读为 0 即按未达阈值处理,无副作用。
|
||||||
|
fn failcount_path() -> PathBuf {
|
||||||
|
std::env::current_dir()
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("."))
|
||||||
|
.join(".devflow-keyring-failcount")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读取全部失败计数(id → count)。文件缺失/损坏 → 空 map(按未达阈值处理)。
|
||||||
|
fn read_failcounts() -> HashMap<String, u32> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
if let Ok(text) = fs::read_to_string(failcount_path()) {
|
||||||
|
for line in text.lines() {
|
||||||
|
let mut parts = line.splitn(2, '=');
|
||||||
|
let id = parts.next().unwrap_or("").trim();
|
||||||
|
let cnt = parts.next().and_then(|s| s.trim().parse::<u32>().ok());
|
||||||
|
if !id.is_empty() {
|
||||||
|
if let Some(c) = cnt {
|
||||||
|
map.insert(id.to_string(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 持久化全部失败计数。写入失败仅 log,不阻断迁移主流程。
|
||||||
|
fn write_failcounts(map: &HashMap<String, u32>) {
|
||||||
|
let mut text = String::new();
|
||||||
|
let mut entries: Vec<_> = map.iter().collect();
|
||||||
|
entries.sort_by(|a, b| a.0.cmp(b.0)); // 稳定顺序,减少无谓 diff
|
||||||
|
for (id, cnt) in entries {
|
||||||
|
text.push_str(id);
|
||||||
|
text.push('=');
|
||||||
|
text.push_str(&cnt.to_string());
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
if let Err(e) = fs::write(failcount_path(), text) {
|
||||||
|
tracing::debug!("[FR-S1] 迁移失败计数文件写入失败(忽略): {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 记录一次迁移失败并返回累计失败次数。持久化失败也不影响返回值(仍递增内存计数用于本次告警)。
|
||||||
|
fn record_migration_fail(id: &str) -> u32 {
|
||||||
|
let mut map = read_failcounts();
|
||||||
|
let next = map.get(id).copied().unwrap_or(0).saturating_add(1);
|
||||||
|
map.insert(id.to_string(), next);
|
||||||
|
write_failcounts(&map);
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清零某 provider 的失败计数(迁移成功后调用,避免历史失败在后续再触发误告警)。
|
||||||
|
fn clear_migration_failcount(id: &str) {
|
||||||
|
let mut map = read_failcounts();
|
||||||
|
if map.remove(id).is_some() {
|
||||||
|
write_failcounts(&map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_for(id: &str) -> anyhow::Result<Entry> {
|
||||||
|
Entry::new(KEYRING_SERVICE, id).map_err(|e| anyhow::anyhow!("keyring entry 创建失败: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读取 provider 密钥(优先 keyring;无则 None)
|
||||||
|
pub fn get_provider_secret(id: &str) -> Option<String> {
|
||||||
|
let entry = entry_for(id).ok()?;
|
||||||
|
match entry.get_password() {
|
||||||
|
Ok(s) if !s.is_empty() => Some(s),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 消费点用:解析 provider 真实密钥 — keyring 优先,fallback DB.api_key(兼容未迁移老库)
|
||||||
|
pub fn resolve_provider_secret(record: &AiProviderRecord) -> String {
|
||||||
|
if !record.api_key.is_empty() {
|
||||||
|
return record.api_key.clone();
|
||||||
|
}
|
||||||
|
get_provider_secret(&record.id).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 写入密钥到 keyring(覆盖)
|
||||||
|
pub fn set_provider_secret(id: &str, key: &str) -> anyhow::Result<()> {
|
||||||
|
let entry = entry_for(id)?;
|
||||||
|
entry.set_password(key).map_err(|e| anyhow::anyhow!("keyring 写入失败: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除 keyring 密钥(provider 删除时清理)
|
||||||
|
pub fn delete_provider_secret(id: &str) -> anyhow::Result<()> {
|
||||||
|
let entry = entry_for(id)?;
|
||||||
|
entry.delete_credential().map_err(|e| anyhow::anyhow!("keyring 删除失败: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 启动一次性迁移:DB 明文 → keyring → DB 置空(失败保留明文下次重试,非阻断)
|
||||||
|
pub async fn migrate_secrets_to_keyring(repo: &AiProviderRepo) -> anyhow::Result<usize> {
|
||||||
|
let providers = repo.list_all().await?;
|
||||||
|
let mut migrated = 0;
|
||||||
|
for mut p in providers {
|
||||||
|
if p.api_key.is_empty() {
|
||||||
|
continue; // 已迁移或无密钥
|
||||||
|
}
|
||||||
|
if let Err(e) = set_provider_secret(&p.id, &p.api_key) {
|
||||||
|
// 累计失败次数:达阈值(默认 3)升级告警,提示明文 api_key 长期滞留 SQLite(无加密)风险。
|
||||||
|
// 计数仅告警用,不改兼容时序——仍保留明文下次重试,不强制迁移、不删明文。
|
||||||
|
let n = record_migration_fail(&p.id);
|
||||||
|
if n >= MIGRATION_FAIL_THRESHOLD {
|
||||||
|
tracing::warn!(
|
||||||
|
"[FR-S1] provider {} keyring 迁移已连续失败 {} 次,明文 api_key 长期滞留 SQLite 文件(无加密)。\
|
||||||
|
建议:1) 确认 OS 钥匙串可用(Win Credential Manager / macOS Keychain);\
|
||||||
|
2) keyring 后端异常时排查对应平台后端;3) 必要时手动在设置中重新保存密钥触发写入",
|
||||||
|
p.id, n
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
"[FR-S1] keyring 迁移失败 {} (累计 {}/{},保留明文下次重试): {}",
|
||||||
|
p.id, n, MIGRATION_FAIL_THRESHOLD, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pid = p.id.clone();
|
||||||
|
p.api_key.clear();
|
||||||
|
if let Err(e) = repo.insert(p).await {
|
||||||
|
tracing::warn!("[FR-S1] 迁移后清空 DB api_key 失败 {}: {}", pid, e);
|
||||||
|
}
|
||||||
|
// 迁移成功 → 清零该 provider 的失败计数(下次若再出现失败从 1 重新累计)
|
||||||
|
clear_migration_failcount(&pid);
|
||||||
|
migrated += 1;
|
||||||
|
}
|
||||||
|
if migrated > 0 {
|
||||||
|
tracing::info!("[FR-S1] {} 条 provider 密钥迁移至 OS keyring", migrated);
|
||||||
|
}
|
||||||
|
Ok(migrated)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验已解析的密钥是否可用:空(含纯空白)→明确错误信息,非空→Ok。
|
||||||
|
/// 用于消费点(build_provider 前)早失败,避免空 key 发请求吃 401,错误伪装成"API Key 无效"。
|
||||||
|
pub fn ensure_resolved_key(provider_name: &str, resolved: &str) -> Result<(), String> {
|
||||||
|
if resolved.trim().is_empty() {
|
||||||
|
Err(format!(
|
||||||
|
"未读取到「{}」的 API 密钥(系统钥匙串无记录或已损坏),请在设置中重新填写并保存",
|
||||||
|
provider_name
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_resolved_key_rejects_empty() {
|
||||||
|
assert!(ensure_resolved_key("GLM", "").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_resolved_key_rejects_whitespace() {
|
||||||
|
// 纯空白也视为无密钥(防粘贴时只有空格)
|
||||||
|
assert!(ensure_resolved_key("GLM", " ").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_resolved_key_accepts_nonempty() {
|
||||||
|
assert!(ensure_resolved_key("GLM", "sk-abc").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_resolved_key_error_mentions_provider_name() {
|
||||||
|
let err = ensure_resolved_key("我的提供商", "").unwrap_err();
|
||||||
|
assert!(err.contains("我的提供商"), "错误信息应含 provider 名便于定位");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_prefers_db_when_non_empty() {
|
||||||
|
// DB api_key 非空 → 直接返回 DB 值,不触发 keyring(FR-S1 兼容未迁移老库)
|
||||||
|
// 纯逻辑路径,不碰 OS keyring,CI 任意 OS 安全。
|
||||||
|
let rec = AiProviderRecord {
|
||||||
|
id: "t1".into(), name: "t".into(), provider_type: "openai_compat".into(),
|
||||||
|
api_key: "sk-db-fallback".into(), base_url: "https://x".into(),
|
||||||
|
default_model: "m".into(), models: None, is_default: false,
|
||||||
|
config: None, created_at: "0".into(), updated_at: "0".into(),
|
||||||
|
};
|
||||||
|
assert_eq!(resolve_provider_secret(&rec), "sk-db-fallback");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// keyring 相关单测(cfg-gate):避 CI OS keyring 副作用(无后端/无 GUI 会话报错)。
|
||||||
|
/// 仅在「桌面 OS + 本地手动」跑(Win/macOS/Linux 桌面环境)。
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
#[test]
|
||||||
|
fn set_get_delete_roundtrip_on_os_keyring() {
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
// 用纳秒戳造唯一 id,避与真实 provider 冲突 + 测后清理。
|
||||||
|
let id = format!(
|
||||||
|
"df-test-{}",
|
||||||
|
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
|
||||||
|
);
|
||||||
|
// 清理历史残留(上次测试崩溃留下)
|
||||||
|
let _ = delete_provider_secret(&id);
|
||||||
|
assert_eq!(get_provider_secret(&id), None, "清理后应读不到");
|
||||||
|
assert!(set_provider_secret(&id, "sk-roundtrip").is_ok());
|
||||||
|
assert_eq!(get_provider_secret(&id).as_deref(), Some("sk-roundtrip"));
|
||||||
|
assert!(delete_provider_secret(&id).is_ok());
|
||||||
|
assert_eq!(get_provider_secret(&id), None, "删除后应读不到");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ df-project = { path = "../crates/df-project" }
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
# write_file base64 编码:写二进制/非 UTF-8 文件(图片/PDF/Excel 等)
|
# write_file base64 编码:写二进制/非 UTF-8 文件(图片/PDF/Excel 等)
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
# keyring v3 默认不带任何 platform 后端,必须显式启 feature——
|
# keyring:密钥解析下沉到 df-storage(workspace 统一声明平台 feature),
|
||||||
# 否则 Entry 走 noop store:set_password 静默成功不持久化,get_password 永远空(=FR-S1 密钥存了等于没存,根因见 docs/09-问题排查/aichat-apikey-401排查-2026-06-15.md)
|
# src-tauri 经 workspace 引用(转发壳 build_provider_for 不直接碰 keyring,但旧路径/兼容保留)。
|
||||||
# Win+mac 启用原生后端;linux(secret-service)按需补
|
# 根因见 docs/09-问题排查/aichat-apikey-401排查-2026-06-15.md
|
||||||
keyring = { version = "3", features = ["windows-native", "apple-native"] }
|
keyring = { workspace = true }
|
||||||
|
|||||||
@@ -1,159 +1,25 @@
|
|||||||
//! FR-S1 api_key 密钥管理 — 真实密钥存 OS keyring,DB `api_key` 列迁移后存空串
|
//! FR-S1 api_key 密钥管理 — **转发壳(方案 B 下沉,2026-06-16)**
|
||||||
//!
|
//!
|
||||||
//! 设计:
|
//! 纯密钥逻辑(get/set/delete/resolve/ensure/migrate + failcount sidecar)已下沉到
|
||||||
//! - keyring entry: service=`devflow-ai-provider`, username=provider_id
|
//! `df_storage::secret`(密钥解析唯一源,供 df-nodes AiNode 与本转发壳共用,DRY)。
|
||||||
//! - DB `api_key` 列恒空(迁移后/新建均空),真实密钥唯一源 = OS keyring
|
//!
|
||||||
//! - 启动一次性迁移:`migrate_secrets_to_keyring` 读老明文 → keyring → DB 置空(失败保留明文下次重试)
|
//! 本文件保留两项职责:
|
||||||
//! - 消费点(build_provider)经 `resolve_provider_secret` 取:keyring 优先,fallback DB(兼容未迁移)
|
//! 1. **转发**:`pub use df_storage::secret::*;` —— 保 src-tauri 内调用路径
|
||||||
//! - 跨平台:Windows Credential Manager / macOS Keychain / Linux Secret Service
|
//! `crate::commands::ai::secret::*` / `super::secret::*` 不变(12 调用点零改动)。
|
||||||
|
//! 2. **`build_provider_for`**:消费点统一入口(resolve→ensure→`df_ai::build_provider` 三步打包)。
|
||||||
|
//! 此函数依赖 `df_ai::build_provider`,**不下沉** —— df-storage 不依赖 df-ai,
|
||||||
|
//! 下沉会引 df-storage→df-ai 反向依赖(循环)。src-tauri 既依赖 df-storage 又依赖 df-ai,
|
||||||
|
//! 天然适合拼装,故留 app 层。
|
||||||
|
//!
|
||||||
|
//! 下沉前原文件全貌见 git 历史(commit c10adaf 前 src-tauri/src/commands/ai/secret.rs:1-228)。
|
||||||
|
|
||||||
use std::collections::HashMap;
|
// 转发所有下沉的纯密钥函数,保 src-tauri 内调用路径不变。
|
||||||
use std::fs;
|
// 不再额外 `use df_storage::secret::{...}` 显式导入 —— 会与 glob re-export 冲突
|
||||||
use std::path::PathBuf;
|
// (编译器判 private function import)。build_provider_for 内用全限定路径调即可。
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use df_storage::secret::*;
|
||||||
|
|
||||||
use df_storage::crud::AiProviderRepo;
|
|
||||||
use df_storage::models::AiProviderRecord;
|
use df_storage::models::AiProviderRecord;
|
||||||
use keyring::Entry;
|
|
||||||
|
|
||||||
const KEYRING_SERVICE: &str = "devflow-ai-provider";
|
|
||||||
|
|
||||||
/// 迁移失败计数器阈值:同一 provider 累计失败到此次数 → 升级为 warn 提示明文密钥长期滞留风险。
|
|
||||||
/// 跨启动持久化(sidecar 文件),计数仅用于告警,不影响兼容时序(不强制迁移、不删明文)。
|
|
||||||
const MIGRATION_FAIL_THRESHOLD: u32 = 3;
|
|
||||||
|
|
||||||
/// 迁移失败计数 sidecar 文件(<cwd>/.devflow-keyring-failcount):逐行 `provider_id=count`。
|
|
||||||
/// cwd 未必是稳定路径,但 R-PD-4 目标仅是「检测到反复失败/滞留时告警」,误读为 0 即按未达阈值处理,无副作用。
|
|
||||||
fn failcount_path() -> PathBuf {
|
|
||||||
std::env::current_dir()
|
|
||||||
.unwrap_or_else(|_| PathBuf::from("."))
|
|
||||||
.join(".devflow-keyring-failcount")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 读取全部失败计数(id → count)。文件缺失/损坏 → 空 map(按未达阈值处理)。
|
|
||||||
fn read_failcounts() -> HashMap<String, u32> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
if let Ok(text) = fs::read_to_string(failcount_path()) {
|
|
||||||
for line in text.lines() {
|
|
||||||
let mut parts = line.splitn(2, '=');
|
|
||||||
let id = parts.next().unwrap_or("").trim();
|
|
||||||
let cnt = parts.next().and_then(|s| s.trim().parse::<u32>().ok());
|
|
||||||
if !id.is_empty() {
|
|
||||||
if let Some(c) = cnt {
|
|
||||||
map.insert(id.to_string(), c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 持久化全部失败计数。写入失败仅 log,不阻断迁移主流程。
|
|
||||||
fn write_failcounts(map: &HashMap<String, u32>) {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut entries: Vec<_> = map.iter().collect();
|
|
||||||
entries.sort_by(|a, b| a.0.cmp(b.0)); // 稳定顺序,减少无谓 diff
|
|
||||||
for (id, cnt) in entries {
|
|
||||||
text.push_str(id);
|
|
||||||
text.push('=');
|
|
||||||
text.push_str(&cnt.to_string());
|
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
if let Err(e) = fs::write(failcount_path(), text) {
|
|
||||||
tracing::debug!("[FR-S1] 迁移失败计数文件写入失败(忽略): {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录一次迁移失败并返回累计失败次数。持久化失败也不影响返回值(仍递增内存计数用于本次告警)。
|
|
||||||
fn record_migration_fail(id: &str) -> u32 {
|
|
||||||
let mut map = read_failcounts();
|
|
||||||
let next = map.get(id).copied().unwrap_or(0).saturating_add(1);
|
|
||||||
map.insert(id.to_string(), next);
|
|
||||||
write_failcounts(&map);
|
|
||||||
next
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 清零某 provider 的失败计数(迁移成功后调用,避免历史失败在后续再触发误告警)。
|
|
||||||
fn clear_migration_failcount(id: &str) {
|
|
||||||
let mut map = read_failcounts();
|
|
||||||
if map.remove(id).is_some() {
|
|
||||||
write_failcounts(&map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entry_for(id: &str) -> anyhow::Result<Entry> {
|
|
||||||
Entry::new(KEYRING_SERVICE, id).map_err(|e| anyhow::anyhow!("keyring entry 创建失败: {}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 读取 provider 密钥(优先 keyring;无则 None)
|
|
||||||
pub fn get_provider_secret(id: &str) -> Option<String> {
|
|
||||||
let entry = entry_for(id).ok()?;
|
|
||||||
match entry.get_password() {
|
|
||||||
Ok(s) if !s.is_empty() => Some(s),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 消费点用:解析 provider 真实密钥 — keyring 优先,fallback DB.api_key(兼容未迁移老库)
|
|
||||||
pub fn resolve_provider_secret(record: &AiProviderRecord) -> String {
|
|
||||||
if !record.api_key.is_empty() {
|
|
||||||
return record.api_key.clone();
|
|
||||||
}
|
|
||||||
get_provider_secret(&record.id).unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 写入密钥到 keyring(覆盖)
|
|
||||||
pub fn set_provider_secret(id: &str, key: &str) -> anyhow::Result<()> {
|
|
||||||
let entry = entry_for(id)?;
|
|
||||||
entry.set_password(key).map_err(|e| anyhow::anyhow!("keyring 写入失败: {}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 删除 keyring 密钥(provider 删除时清理)
|
|
||||||
pub fn delete_provider_secret(id: &str) -> anyhow::Result<()> {
|
|
||||||
let entry = entry_for(id)?;
|
|
||||||
entry.delete_credential().map_err(|e| anyhow::anyhow!("keyring 删除失败: {}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 启动一次性迁移:DB 明文 → keyring → DB 置空(失败保留明文下次重试,非阻断)
|
|
||||||
pub async fn migrate_secrets_to_keyring(repo: &AiProviderRepo) -> anyhow::Result<usize> {
|
|
||||||
let providers = repo.list_all().await?;
|
|
||||||
let mut migrated = 0;
|
|
||||||
for mut p in providers {
|
|
||||||
if p.api_key.is_empty() {
|
|
||||||
continue; // 已迁移或无密钥
|
|
||||||
}
|
|
||||||
if let Err(e) = set_provider_secret(&p.id, &p.api_key) {
|
|
||||||
// 累计失败次数:达阈值(默认 3)升级告警,提示明文密钥长期滞留 SQLite(无加密)风险。
|
|
||||||
// 计数仅告警用,不改兼容时序——仍保留明文下次重试,不强制迁移、不删明文。
|
|
||||||
let n = record_migration_fail(&p.id);
|
|
||||||
if n >= MIGRATION_FAIL_THRESHOLD {
|
|
||||||
tracing::warn!(
|
|
||||||
"[FR-S1] provider {} keyring 迁移已连续失败 {} 次,明文 api_key 长期滞留 SQLite 文件(无加密)。\
|
|
||||||
建议:1) 确认 OS 钥匙串可用(Win Credential Manager / macOS Keychain);\
|
|
||||||
2) keyring 后端异常时排查对应平台后端;3) 必要时手动在设置中重新保存密钥触发写入",
|
|
||||||
p.id, n
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tracing::warn!(
|
|
||||||
"[FR-S1] keyring 迁移失败 {} (累计 {}/{},保留明文下次重试): {}",
|
|
||||||
p.id, n, MIGRATION_FAIL_THRESHOLD, e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let pid = p.id.clone();
|
|
||||||
p.api_key.clear();
|
|
||||||
if let Err(e) = repo.insert(p).await {
|
|
||||||
tracing::warn!("[FR-S1] 迁移后清空 DB api_key 失败 {}: {}", pid, e);
|
|
||||||
}
|
|
||||||
// 迁移成功 → 清零该 provider 的失败计数(下次若再出现失败从 1 重新累计)
|
|
||||||
clear_migration_failcount(&pid);
|
|
||||||
migrated += 1;
|
|
||||||
}
|
|
||||||
if migrated > 0 {
|
|
||||||
tracing::info!("[FR-S1] {} 条 provider 密钥迁移至 OS keyring", migrated);
|
|
||||||
}
|
|
||||||
Ok(migrated)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 消费点统一入口:resolve_provider_secret → ensure_resolved_key → build_provider 三步打包。
|
/// 消费点统一入口:resolve_provider_secret → ensure_resolved_key → build_provider 三步打包。
|
||||||
///
|
///
|
||||||
@@ -161,11 +27,13 @@ pub async fn migrate_secrets_to_keyring(repo: &AiProviderRepo) -> anyhow::Result
|
|||||||
/// 空 key 照发请求吃 401,用户看「key 已保存」反复重试无解)。返 Result 让调用方按场景处理:
|
/// 空 key 照发请求吃 401,用户看「key 已保存」反复重试无解)。返 Result 让调用方按场景处理:
|
||||||
/// - 主链(agentic loop):空 key 早失败 emit AiError
|
/// - 主链(agentic loop):空 key 早失败 emit AiError
|
||||||
/// - 后台 task(title/knowledge 提炼/embedding):Err 上层降级/记 log
|
/// - 后台 task(title/knowledge 提炼/embedding):Err 上层降级/记 log
|
||||||
|
///
|
||||||
|
/// **不下沉原因**:依赖 `df_ai::build_provider`,下沉 df-storage 会引 df-storage→df-ai 循环依赖。
|
||||||
pub fn build_provider_for(
|
pub fn build_provider_for(
|
||||||
record: &AiProviderRecord,
|
record: &AiProviderRecord,
|
||||||
) -> Result<Box<dyn df_ai::provider::LlmProvider>, String> {
|
) -> Result<Box<dyn df_ai::provider::LlmProvider>, String> {
|
||||||
let api_key = resolve_provider_secret(record);
|
let api_key = df_storage::secret::resolve_provider_secret(record);
|
||||||
ensure_resolved_key(&record.name, &api_key)?;
|
df_storage::secret::ensure_resolved_key(&record.name, &api_key)?;
|
||||||
Ok(df_ai::build_provider(
|
Ok(df_ai::build_provider(
|
||||||
&record.provider_type,
|
&record.provider_type,
|
||||||
&record.base_url,
|
&record.base_url,
|
||||||
@@ -173,56 +41,3 @@ pub fn build_provider_for(
|
|||||||
&record.default_model,
|
&record.default_model,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 校验已解析的密钥是否可用:空(含纯空白)→明确错误信息,非空→Ok。
|
|
||||||
/// 用于消费点(build_provider 前)早失败,避免空 key 发请求吃 401,错误伪装成"API Key 无效"。
|
|
||||||
pub fn ensure_resolved_key(provider_name: &str, resolved: &str) -> Result<(), String> {
|
|
||||||
if resolved.trim().is_empty() {
|
|
||||||
Err(format!(
|
|
||||||
"未读取到「{}」的 API 密钥(系统钥匙串无记录或已损坏),请在设置中重新填写并保存",
|
|
||||||
provider_name
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_resolved_key_rejects_empty() {
|
|
||||||
assert!(ensure_resolved_key("GLM", "").is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_resolved_key_rejects_whitespace() {
|
|
||||||
// 纯空白也视为无密钥(防粘贴时只有空格)
|
|
||||||
assert!(ensure_resolved_key("GLM", " ").is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_resolved_key_accepts_nonempty() {
|
|
||||||
assert!(ensure_resolved_key("GLM", "sk-abc").is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_resolved_key_error_mentions_provider_name() {
|
|
||||||
let err = ensure_resolved_key("我的提供商", "").unwrap_err();
|
|
||||||
assert!(err.contains("我的提供商"), "错误信息应含 provider 名便于定位");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn resolve_prefers_db_when_non_empty() {
|
|
||||||
// DB api_key 非空 → 直接返回 DB 值,不触发 keyring(FR-S1 兼容未迁移老库)
|
|
||||||
use df_storage::models::AiProviderRecord;
|
|
||||||
let rec = AiProviderRecord {
|
|
||||||
id: "t1".into(), name: "t".into(), provider_type: "openai_compat".into(),
|
|
||||||
api_key: "sk-db-fallback".into(), base_url: "https://x".into(),
|
|
||||||
default_model: "m".into(), models: None, is_default: false,
|
|
||||||
config: None, created_at: "0".into(), updated_at: "0".into(),
|
|
||||||
};
|
|
||||||
assert_eq!(resolve_provider_secret(&rec), "sk-db-fallback");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user