@
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 秒。
|
/// JWT 载荷:`sub` 为用户名,`exp` 为到期 Unix 秒。
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
|
|||||||
@ -117,7 +117,10 @@ async fn login(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(req): Json<LoginRequest>,
|
Json(req): Json<LoginRequest>,
|
||||||
) -> Result<Json<LoginResponse>, StatusCode> {
|
) -> 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(Json(LoginResponse {
|
||||||
ok: true,
|
ok: true,
|
||||||
username: req.username.clone(),
|
username: req.username.clone(),
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::auth::{self, DevGate, UserStore};
|
use crate::auth::{self, DevGate, UserStore, WallUserStore};
|
||||||
use crate::folders::{FolderRegistry, FolderStore};
|
use crate::folders::{FolderRegistry, FolderStore};
|
||||||
use crate::monitor::Stats;
|
use crate::monitor::Stats;
|
||||||
use crate::photos::PhotoStore;
|
use crate::photos::PhotoStore;
|
||||||
@ -23,6 +23,8 @@ pub struct AppState {
|
|||||||
pub gate: Arc<DevGate>,
|
pub gate: Arc<DevGate>,
|
||||||
/// 天气/定位服务(自带每日限流 + 缓存)。
|
/// 天气/定位服务(自带每日限流 + 缓存)。
|
||||||
pub weather: Arc<WeatherService>,
|
pub weather: Arc<WeatherService>,
|
||||||
|
/// 照片墙用户校验后端(wall_users 表)。
|
||||||
|
pub wall_users: Arc<WallUserStore>,
|
||||||
/// 照片墙图片仓库(按用户隔离的磁盘目录)。
|
/// 照片墙图片仓库(按用户隔离的磁盘目录)。
|
||||||
pub photos: PhotoStore,
|
pub photos: PhotoStore,
|
||||||
/// 特殊文件夹注册表(白名单)。
|
/// 特殊文件夹注册表(白名单)。
|
||||||
@ -38,8 +40,9 @@ impl AppState {
|
|||||||
Self {
|
Self {
|
||||||
snapshot: Arc::new(RwLock::new(Stats::default())),
|
snapshot: Arc::new(RwLock::new(Stats::default())),
|
||||||
users: Arc::new(UserStore::new(pool.clone())),
|
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()),
|
weather: Arc::new(WeatherService::from_env()),
|
||||||
|
wall_users: Arc::new(WallUserStore::new(pool.clone())),
|
||||||
photos: PhotoStore::from_env(),
|
photos: PhotoStore::from_env(),
|
||||||
folders: FolderRegistry::from_env(),
|
folders: FolderRegistry::from_env(),
|
||||||
folder_photos: FolderStore::from_env(),
|
folder_photos: FolderStore::from_env(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user