feat(server): 登录与开发者入口门改走 MySQL
UserStore/DevGate 不再写死,改为从 MySQL 实时查询(每次校验一条索引查询、 不缓存整表,照顾内存): - 新增 db_pool_from_env(),UserStore 与 DevGate 共用一个 sqlx 懒连接池 - verify() 查 ccuser 表、allows() 查 dev_gate 表,均改为 async - 未设置 DATABASE_URL 时各自回退到内置/文件兜底,便于本地无库开发 连接串经环境变量 DATABASE_URL 注入(线上写在 weather.env)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f4342e7c82
commit
58bed0ae44
635
Server/Cargo.lock
generated
635
Server/Cargo.lock
generated
@ -8,6 +8,12 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@ -29,6 +35,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@ -99,11 +114,20 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@ -120,6 +144,12 @@ version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[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"
|
||||
@ -178,6 +208,21 @@ version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -193,6 +238,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@ -202,6 +262,21 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@ -212,6 +287,17 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
@ -225,7 +311,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -249,6 +337,27 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@ -259,6 +368,17 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@ -275,6 +395,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@ -291,6 +417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -299,6 +426,23 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-intrusive"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
@ -318,7 +462,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@ -360,6 +507,62 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[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 = "1.4.2"
|
||||
@ -445,7 +648,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -598,6 +801,16 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@ -636,12 +849,39 @@ dependencies = [
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"plain",
|
||||
"redox_syscall 0.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
@ -675,6 +915,16 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
@ -737,6 +987,22 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.6",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
@ -752,6 +1018,17 @@ 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-traits"
|
||||
version = "0.2.19"
|
||||
@ -759,6 +1036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -824,6 +1102,12 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@ -842,7 +1126,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
@ -857,6 +1141,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@ -869,6 +1162,33 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@ -931,7 +1251,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand",
|
||||
"rand 0.9.4",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
@ -972,14 +1292,35 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
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]]
|
||||
@ -989,7 +1330,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]]
|
||||
@ -1010,6 +1360,15 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
@ -1045,7 +1404,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1062,6 +1421,26 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@ -1198,11 +1577,23 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"tower-http 0.7.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -1230,6 +1621,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
@ -1270,12 +1671,163 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-mysql",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-intrusive",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.15.5",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sqlx-core",
|
||||
"sqlx-macros-core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-mysql",
|
||||
"syn",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
"digest",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"itoa",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand 0.8.6",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@ -1441,6 +1993,17 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
@ -1538,9 +2101,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
@ -1568,12 +2143,33 @@ version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@ -1628,6 +2224,12 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.125"
|
||||
@ -1703,6 +2305,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.8"
|
||||
@ -1712,6 +2323,16 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
|
||||
dependencies = [
|
||||
"libredox",
|
||||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
||||
@ -15,6 +15,7 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "gzi
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.150"
|
||||
sha2 = "0.10.9"
|
||||
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls", "mysql", "macros"] }
|
||||
sysinfo = "0.39.3"
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
tower-http = { version = "0.7.0", features = ["cors", "fs"] }
|
||||
|
||||
@ -1,31 +1,149 @@
|
||||
//! 用户存储与登录校验。
|
||||
//! 用户存储与登录校验 + 开发者入口门。
|
||||
//!
|
||||
//! 设计取舍:用户数很少(≤ 20),所以**不上数据库**——一个进程内的
|
||||
//! `HashMap<用户名, 密码SHA256(hex)>` 足矣,启动时构建一次、之后只读。
|
||||
//! 数据库要付出的连接池、迁移、Schema 维护成本,在这个规模下并不划算。
|
||||
//! 两者默认都走 **MySQL**,且**每次校验直接查库(走 IO,不在内存里缓存整表)**——
|
||||
//! 机器内存紧张,宁可每次打一次很轻的索引查询,也不常驻一份副本:
|
||||
//! - 登录:表 `ccuser(username, password_sha256)`;
|
||||
//! - 入口门:表 `dev_gate(gate_hash)`。
|
||||
//! 连接串由环境变量 `DATABASE_URL` 指定,`UserStore` 与 `DevGate` **共用同一个
|
||||
//! 连接池**(见 `db_pool_from_env`),避免开两份连接。未设置 `DATABASE_URL` 时各自
|
||||
//! 回退到内置/文件兜底,方便本地无 DB 时开发。
|
||||
//!
|
||||
//! 现在用户表是**写死**在 `UserStore::seed()` 里的(仅 `cc` 一个)。
|
||||
//! 将来用户变多时,迁移路径很短,且 handler 完全无需改动:
|
||||
//! 1. 改成从 TOML/JSON 文件读:把 `seed()` 换成读文件 → 解析 → 填 map;
|
||||
//! 2. 真要运行时增删用户 / 权限 / 审计,再考虑换 SQLite/Postgres。
|
||||
//!
|
||||
//! 口令永不明文存储:表里存的是 SHA256(hex),登录时把传入明文同样
|
||||
//! 口令永不明文存储:库里存的是 SHA256(hex),登录时把传入明文同样
|
||||
//! 哈希后做**等长比较**。(注意:SHA256 仅用于“不落明文”,并非抗暴力破解的
|
||||
//! 口令哈希;若将来要对外网开放,应换 Argon2/bcrypt 这类慢哈希。)
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
|
||||
|
||||
/// 进程内用户表:用户名 → 密码的 SHA256(小写 hex)。
|
||||
pub struct UserStore {
|
||||
users: HashMap<String, String>,
|
||||
/// 按 `DATABASE_URL` 建一个**懒连接**池(启动不阻塞,首次查询才真正建连),
|
||||
/// 供 `UserStore` 与 `DevGate` 共用。未设置该环境变量则返回 `None`,调用方各自兜底。
|
||||
pub fn db_pool_from_env() -> Option<MySqlPool> {
|
||||
match std::env::var("DATABASE_URL") {
|
||||
Ok(url) if !url.trim().is_empty() => {
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect_lazy(&url)
|
||||
.expect("DATABASE_URL 格式非法");
|
||||
Some(pool)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 开发者「入口门」:一组合法的入口 hash。
|
||||
///
|
||||
/// 访问 `域名/#<hash>` 时,前端把 `<hash>` 发给后端,只有命中这里的某一个,
|
||||
/// 才放行去显示登录页。这样合法 hash **只存在后端**,不进前端打包产物,
|
||||
/// 扒前端 JS 也看不到;且每个开发者一串、可单独增删。
|
||||
///
|
||||
/// 注意:入口 hash 只是“藏门”的共享口令,**不是身份凭证**。真正证明身份仍靠
|
||||
/// 登录(用户名 + 密码,见 `UserStore`)。
|
||||
pub enum DevGate {
|
||||
/// 每次校验查 MySQL `dev_gate` 表(不缓存整表)。
|
||||
Db(MySqlPool),
|
||||
/// 内置/文件载入的 hash 集合,仅作无 DB 时的兜底。
|
||||
Memory(HashSet<String>),
|
||||
}
|
||||
|
||||
impl DevGate {
|
||||
/// 有连接池就走 MySQL;否则读 `GATE_FILE`(默认 `dev_gate.txt`),文件也没有再 `seed()`。
|
||||
pub fn new(pool: Option<MySqlPool>) -> Self {
|
||||
match pool {
|
||||
Some(p) => {
|
||||
eprintln!("[gate] 入口校验走 MySQL");
|
||||
DevGate::Db(p)
|
||||
}
|
||||
None => Self::load_file(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 `GATE_FILE`(默认 `dev_gate.txt`)读入口 hash;读不到回退内置 `seed()`。
|
||||
/// 文件格式:一行一个 hash,`#` 之后为注释,空行忽略。
|
||||
fn load_file() -> Self {
|
||||
let path = std::env::var("GATE_FILE").unwrap_or_else(|_| "dev_gate.txt".to_string());
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(text) => {
|
||||
let hashes = Self::parse(&text);
|
||||
eprintln!("[gate] 从 {path} 载入 {} 个入口 hash", hashes.len());
|
||||
DevGate::Memory(hashes)
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("[gate] 未找到 {path},回退到内置写死列表");
|
||||
Self::seed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 把文件文本解析为 hash 集合:丢掉 `#` 注释与空行,每行取剩下的非空串。
|
||||
fn parse(text: &str) -> HashSet<String> {
|
||||
text.lines()
|
||||
.map(|line| line.split('#').next().unwrap_or("").trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 内置写死的入口 hash 表,作为无 DB / 无文件时的兜底。
|
||||
pub fn seed() -> Self {
|
||||
let mut hashes = HashSet::new();
|
||||
|
||||
// cc:SHA256("192118Lht") 的前 12 位。对应入口 域名/#0086e83c9b10
|
||||
hashes.insert("0086e83c9b10".to_string());
|
||||
|
||||
DevGate::Memory(hashes)
|
||||
}
|
||||
|
||||
/// 该 hash 是否属于某个合法开发者。DB 模式下每次查库(命中即放行);
|
||||
/// 查询出错按“不放行”处理,不泄露后端内部状态。
|
||||
pub async fn allows(&self, hash: &str) -> bool {
|
||||
match self {
|
||||
DevGate::Memory(hashes) => hashes.contains(hash),
|
||||
DevGate::Db(pool) => {
|
||||
let row: Result<Option<(String,)>, sqlx::Error> =
|
||||
sqlx::query_as("SELECT gate_hash FROM dev_gate WHERE gate_hash = ?")
|
||||
.bind(hash)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
match row {
|
||||
Ok(found) => found.is_some(),
|
||||
Err(e) => {
|
||||
eprintln!("[gate] 查询 dev_gate 失败: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户校验后端:优先连 MySQL,无 `DATABASE_URL` 时退回内置写死表。
|
||||
pub enum UserStore {
|
||||
/// 从 MySQL 的 `ccuser` 表查 `password_sha256`。
|
||||
Db(MySqlPool),
|
||||
/// 内置写死表(用户名 → 密码SHA256 hex),仅作无 DB 时的兜底。
|
||||
Memory(HashMap<String, String>),
|
||||
}
|
||||
|
||||
impl UserStore {
|
||||
/// 构建写死的用户表。将来可替换为「从文件读取」。
|
||||
/// 有连接池就走 MySQL(每次校验查 `ccuser`);否则回退到内置 `seed()`。
|
||||
pub fn new(pool: Option<MySqlPool>) -> Self {
|
||||
match pool {
|
||||
Some(p) => {
|
||||
eprintln!("[auth] 用户校验走 MySQL");
|
||||
UserStore::Db(p)
|
||||
}
|
||||
None => {
|
||||
eprintln!("[auth] 未设置 DATABASE_URL,回退到内置写死用户表");
|
||||
Self::seed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 内置写死的单用户表,作为 `load()` 无 DB 配置时的兜底。
|
||||
pub fn seed() -> Self {
|
||||
let mut users = HashMap::new();
|
||||
|
||||
@ -36,19 +154,34 @@ impl UserStore {
|
||||
"0086e83c9b108b227eed55425e9641286f42bd0c31a1b95afbb9edd6d3aa6234".to_string(),
|
||||
);
|
||||
|
||||
Self { users }
|
||||
UserStore::Memory(users)
|
||||
}
|
||||
|
||||
/// 校验用户名 + 明文密码是否匹配。
|
||||
///
|
||||
/// 用户名不存在与密码错误都返回 `false`,不向外区分两者
|
||||
/// (避免泄露“某用户名是否存在”)。
|
||||
pub fn verify(&self, username: &str, password: &str) -> bool {
|
||||
match self.users.get(username) {
|
||||
Some(expected_hex) => {
|
||||
let actual_hex = sha256_hex(password);
|
||||
constant_time_eq(actual_hex.as_bytes(), expected_hex.as_bytes())
|
||||
/// 用户名不存在、密码错误、乃至 DB 查询出错,都一律返回 `false`,
|
||||
/// 不向外区分(避免泄露“某用户名是否存在”或后端内部状态)。
|
||||
pub async fn verify(&self, username: &str, password: &str) -> bool {
|
||||
let actual_hex = sha256_hex(password);
|
||||
let expected_hex = match self {
|
||||
UserStore::Memory(users) => users.get(username).cloned(),
|
||||
UserStore::Db(pool) => {
|
||||
let row: Result<Option<(String,)>, sqlx::Error> =
|
||||
sqlx::query_as("SELECT password_sha256 FROM ccuser WHERE username = ?")
|
||||
.bind(username)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
match row {
|
||||
Ok(found) => found.map(|(hex,)| hex),
|
||||
Err(e) => {
|
||||
eprintln!("[auth] 查询 ccuser 失败: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
match expected_hex {
|
||||
Some(expected) => constant_time_eq(actual_hex.as_bytes(), expected.as_bytes()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ use std::net::SocketAddr;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{ConnectInfo, State},
|
||||
extract::{ConnectInfo, Query, State},
|
||||
http::{HeaderMap, Method, StatusCode, header},
|
||||
routing::{get, post},
|
||||
};
|
||||
@ -12,7 +12,11 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use tower_http::cors::{AllowOrigin, CorsLayer};
|
||||
|
||||
use crate::{monitor::Stats, state::AppState, weather::WeatherOutcome};
|
||||
use crate::{
|
||||
monitor::Stats,
|
||||
state::AppState,
|
||||
weather::{Located, WeatherOutcome},
|
||||
};
|
||||
|
||||
pub fn router(state: AppState) -> Router {
|
||||
// CORS:只有浏览器会校验。放行前端开发源(Vite 默认 5173,被占用时会用 5174)。
|
||||
@ -30,6 +34,7 @@ pub fn router(state: AppState) -> Router {
|
||||
|
||||
Router::new()
|
||||
.route("/api/web/stats", get(stats))
|
||||
.route("/api/web/gate", post(gate))
|
||||
.route("/api/web/login", post(login))
|
||||
.route("/api/web/weather", get(weather))
|
||||
.layer(cors)
|
||||
@ -42,6 +47,33 @@ async fn stats(State(state): State<AppState>) -> Json<Stats> {
|
||||
Json(snapshot)
|
||||
}
|
||||
|
||||
/// 入口门请求体:前端把 URL `#` 后那串 hash 发来校验。
|
||||
#[derive(Deserialize)]
|
||||
struct GateRequest {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
/// 入口门响应。
|
||||
#[derive(Serialize)]
|
||||
struct GateResponse {
|
||||
ok: bool,
|
||||
}
|
||||
|
||||
/// 校验入口 hash 是否属于某个合法开发者。
|
||||
///
|
||||
/// 命中 200 + `{ok:true}`(前端据此放行到登录页);未命中返回 401。
|
||||
/// 注意:这只决定“是否显示登录页”,**真正的身份校验仍在 `login`**。
|
||||
async fn gate(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<GateRequest>,
|
||||
) -> Result<Json<GateResponse>, StatusCode> {
|
||||
if state.gate.allows(&req.hash).await {
|
||||
Ok(Json(GateResponse { ok: true }))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录请求体:用户名 + 明文密码(开发期走 HTTP;生产应套 HTTPS)。
|
||||
#[derive(Deserialize)]
|
||||
struct LoginRequest {
|
||||
@ -63,7 +95,7 @@ async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
) -> Result<Json<LoginResponse>, StatusCode> {
|
||||
if state.users.verify(&req.username, &req.password) {
|
||||
if state.users.verify(&req.username, &req.password).await {
|
||||
Ok(Json(LoginResponse {
|
||||
ok: true,
|
||||
username: req.username,
|
||||
@ -74,15 +106,33 @@ async fn login(
|
||||
}
|
||||
}
|
||||
|
||||
/// 天气 + 定位:高德按访问者 IP 定位取城市,和风取实时天气。
|
||||
/// 前端可选带上的定位参数:经纬度 + 城市名(来自 AMap.Geolocation 插件)。
|
||||
/// 三者都缺省 → 后端退回高德 IP 定位兜底。
|
||||
#[derive(Deserialize)]
|
||||
struct WeatherQuery {
|
||||
lon: Option<f64>,
|
||||
lat: Option<f64>,
|
||||
city: Option<String>,
|
||||
}
|
||||
|
||||
/// 天气 + 定位:
|
||||
/// - 前端带 `lon/lat`(AMap.Geolocation 定位结果)→ 直接用,只调和风。
|
||||
/// - 未带 → 高德按访问者 IP 定位取城市作兜底。
|
||||
///
|
||||
/// 内部自带每日限流(高德 300 / 和风 900,北京时间 0 点归零)与短期缓存。
|
||||
async fn weather(
|
||||
State(state): State<AppState>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
headers: HeaderMap,
|
||||
Query(params): Query<WeatherQuery>,
|
||||
) -> Json<Value> {
|
||||
let ip = client_ip(&headers, addr);
|
||||
let outcome = state.weather.get(&ip).await;
|
||||
// 经纬度齐全才算前端定位成功;否则交给后端 IP 兜底。
|
||||
let front = match (params.lon, params.lat) {
|
||||
(Some(lon), Some(lat)) => Some(Located::from_front(lon, lat, params.city)),
|
||||
_ => None,
|
||||
};
|
||||
let outcome = state.weather.get(&ip, front).await;
|
||||
|
||||
let q = state.weather.quota_snapshot();
|
||||
let quota = json!({
|
||||
|
||||
@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::auth::UserStore;
|
||||
use crate::auth::{self, DevGate, UserStore};
|
||||
use crate::monitor::Stats;
|
||||
use crate::weather::WeatherService;
|
||||
|
||||
@ -15,17 +15,22 @@ use crate::weather::WeatherService;
|
||||
pub struct AppState {
|
||||
/// 最新一次采样快照。
|
||||
pub snapshot: Arc<RwLock<Stats>>,
|
||||
/// 用户表:启动时构建一次、之后只读,故无需锁,用 `Arc` 共享即可。
|
||||
/// 用户校验后端(MySQL 或内置兜底):内部已是连接池/只读 map,`Arc` 共享即可。
|
||||
pub users: Arc<UserStore>,
|
||||
/// 开发者入口门:合法入口 hash 集合,只读共享。
|
||||
pub gate: Arc<DevGate>,
|
||||
/// 天气/定位服务(自带每日限流 + 缓存)。
|
||||
pub weather: Arc<WeatherService>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
// 一个连接池给登录校验与入口门共用(克隆是廉价的、共享底层连接)。
|
||||
let pool = auth::db_pool_from_env();
|
||||
Self {
|
||||
snapshot: Arc::new(RwLock::new(Stats::default())),
|
||||
users: Arc::new(UserStore::seed()),
|
||||
users: Arc::new(UserStore::new(pool.clone())),
|
||||
gate: Arc::new(DevGate::new(pool)),
|
||||
weather: Arc::new(WeatherService::from_env()),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user