From 0e6c774ed69f5b381c9c40709a66e09e628d6b7b Mon Sep 17 00:00:00 2001 From: cc Date: Wed, 24 Jun 2026 22:42:06 +0800 Subject: [PATCH] =?UTF-8?q?@=20feat(auth):=20=E6=96=B0=E5=A2=9E=20wall=5Fu?= =?UTF-8?q?sers=20=E8=A1=A8=EF=BC=8C=E7=85=A7=E7=89=87=E5=A2=99=E4=B8=8E?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=80=85=E7=99=BB=E5=BD=95=E5=88=86=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 登录先查 ccuser(开发者),再查 wall_users(照片墙用户)。 wall_users 初始用户:cc / UserTest。 Co-Authored-By: Claude @ --- Server/src/auth.rs | 63 ++++++++++++++++++++++++++++++++++++++++ Server/src/routes/web.rs | 5 +++- Server/src/state.rs | 7 +++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Server/src/auth.rs b/Server/src/auth.rs index 82f05ab..595f81f 100644 --- a/Server/src/auth.rs +++ b/Server/src/auth.rs @@ -188,6 +188,69 @@ impl UserStore { } } +/// 照片墙用户校验后端:查 `wall_users` 表(与 `ccuser` 物理隔离)。 +/// 未设 DATABASE_URL 时退回内置兜底。 +pub enum WallUserStore { + Db(MySqlPool), + Memory(HashMap), +} + +impl WallUserStore { + pub fn new(pool: Option) -> Self { + match pool { + Some(p) => { + eprintln!("[auth] 照片墙用户校验走 MySQL (wall_users)"); + WallUserStore::Db(p) + } + None => { + eprintln!("[auth] 未设置 DATABASE_URL,照片墙用户回退到内置表"); + Self::seed() + } + } + } + + /// 内置兜底:cc + UserTest + pub fn seed() -> Self { + let mut users = HashMap::new(); + // cc / 192118Lht + users.insert( + "cc".to_string(), + "0086e83c9b108b227eed55425e9641286f42bd0c31a1b95afbb9edd6d3aa6234".to_string(), + ); + // UserTest / 2015.08.607 + users.insert( + "UserTest".to_string(), + "76f03f2eaa05e66c82d25e168fec27e7d0d202f15da73aa5852562f863633b8e".to_string(), + ); + WallUserStore::Memory(users) + } + + pub async fn verify(&self, username: &str, password: &str) -> bool { + let actual_hex = sha256_hex(password); + let expected_hex = match self { + WallUserStore::Memory(users) => users.get(username).cloned(), + WallUserStore::Db(pool) => { + let row: Result, sqlx::Error> = + sqlx::query_as("SELECT password_sha256 FROM wall_users WHERE username = ?") + .bind(username) + .fetch_optional(pool) + .await; + match row { + Ok(found) => found.map(|(hex,)| hex), + Err(e) => { + eprintln!("[auth] 查询 wall_users 失败: {e}"); + None + } + } + } + }; + match expected_hex { + Some(expected) => constant_time_eq(actual_hex.as_bytes(), expected.as_bytes()), + None => false, + } + } +} + /// JWT 载荷:`sub` 为用户名,`exp` 为到期 Unix 秒。 #[derive(Serialize, Deserialize)] struct Claims { diff --git a/Server/src/routes/web.rs b/Server/src/routes/web.rs index 7d85811..37fd2f0 100644 --- a/Server/src/routes/web.rs +++ b/Server/src/routes/web.rs @@ -117,7 +117,10 @@ async fn login( State(state): State, Json(req): Json, ) -> Result, StatusCode> { - if state.users.verify(&req.username, &req.password).await { + // 先查开发者表 ccuser,再查照片墙用户表 wall_users + if state.users.verify(&req.username, &req.password).await + || state.wall_users.verify(&req.username, &req.password).await + { Ok(Json(LoginResponse { ok: true, username: req.username.clone(), diff --git a/Server/src/state.rs b/Server/src/state.rs index a3a2b15..bf94577 100644 --- a/Server/src/state.rs +++ b/Server/src/state.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tokio::sync::RwLock; -use crate::auth::{self, DevGate, UserStore}; +use crate::auth::{self, DevGate, UserStore, WallUserStore}; use crate::folders::{FolderRegistry, FolderStore}; use crate::monitor::Stats; use crate::photos::PhotoStore; @@ -23,6 +23,8 @@ pub struct AppState { pub gate: Arc, /// 天气/定位服务(自带每日限流 + 缓存)。 pub weather: Arc, + /// 照片墙用户校验后端(wall_users 表)。 + pub wall_users: Arc, /// 照片墙图片仓库(按用户隔离的磁盘目录)。 pub photos: PhotoStore, /// 特殊文件夹注册表(白名单)。 @@ -38,8 +40,9 @@ impl AppState { Self { snapshot: Arc::new(RwLock::new(Stats::default())), users: Arc::new(UserStore::new(pool.clone())), - gate: Arc::new(DevGate::new(pool)), + gate: Arc::new(DevGate::new(pool.clone())), weather: Arc::new(WeatherService::from_env()), + wall_users: Arc::new(WallUserStore::new(pool.clone())), photos: PhotoStore::from_env(), folders: FolderRegistry::from_env(), folder_photos: FolderStore::from_env(),