@
feat(auth): 新增 wall_users 表,照片墙与开发者登录分离 登录先查 ccuser(开发者),再查 wall_users(照片墙用户)。 wall_users 初始用户:cc / UserTest。 Co-Authored-By: Claude <noreply@anthropic.com> @
This commit is contained in:
parent
8d89cee451
commit
0e6c774ed6
@ -188,6 +188,69 @@ impl UserStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// 照片墙用户校验后端:查 `wall_users` 表(与 `ccuser` 物理隔离)。
|
||||
/// 未设 DATABASE_URL 时退回内置兜底。
|
||||
pub enum WallUserStore {
|
||||
Db(MySqlPool),
|
||||
Memory(HashMap<String, String>),
|
||||
}
|
||||
|
||||
impl WallUserStore {
|
||||
pub fn new(pool: Option<MySqlPool>) -> 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<Option<(String,)>, 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 {
|
||||
|
||||
@ -117,7 +117,10 @@ async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
) -> Result<Json<LoginResponse>, 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(),
|
||||
|
||||
@ -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<DevGate>,
|
||||
/// 天气/定位服务(自带每日限流 + 缓存)。
|
||||
pub weather: Arc<WeatherService>,
|
||||
/// 照片墙用户校验后端(wall_users 表)。
|
||||
pub wall_users: Arc<WallUserStore>,
|
||||
/// 照片墙图片仓库(按用户隔离的磁盘目录)。
|
||||
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(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user