Files
markbase/markbase-core/src/auth.rs
Warren 1300a4e223
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能:
-  Categories/Series双视图管理(category_view.rs + import_markdown.rs)
-  FUSE Multi-Volume支持(tree_type参数)
-  SSH/SFTP/SCP/rsync协议完整实现(4042行)
-  NFS/SMB Module Phase 1-3完成
-  Archive Module Phase 1-4完成(2916行)
-  Download Center API完整实现
-  S3兼容API实现(560行)

Git配置修正:
-  删除错误origin(gitea.momentry.ddns.net)
-  删除m5max128(指向机器名)
-  设置origin = m5max128gitea.momentry.ddns.net/admin/markbase
-  设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase

数据清理:
-  删除38个临时SQLite(保留accusys.sqlite、demo.sqlite)
-  删除.bak、test_*.bin、调试脚本等临时文件
-  删除临时目录(build/、download files/、raid_test/等)
-  更新.gitignore排除临时文件

架构优化:
- 52个文件修改,2434行新增,4739行删除
- Workspace成员整合(16个crate)
- 数据库状态:accusys.sqlite保留(主demo测试)

远程同步:
-  准备推送到m5max128gitea(远程Gitea)
-  准备推送到m4minigitea(本地Gitea)
2026-06-12 12:59:54 +08:00

320 lines
10 KiB
Rust

use bcrypt::{hash, verify, DEFAULT_COST};
use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub user_id: String,
pub username: String,
pub password_hash: String,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub token: String,
pub user_id: String,
pub username: String,
pub created_at: String,
pub expires_at: String,
pub groups: Vec<String>,
pub permissions: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginResponse {
pub token: String,
pub expires_at: String,
pub user_id: String,
pub groups: Vec<String>,
pub permissions: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminLoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminSession {
pub token: String,
pub username: String,
pub created_at: String,
pub expires_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminLoginResponse {
pub token: String,
pub expires_at: String,
pub username: String,
}
#[derive(Clone)]
pub struct AuthState {
pub sessions: Arc<Mutex<HashMap<String, Session>>>,
pub users: Arc<Mutex<HashMap<String, User>>>,
pub auth_db: Option<crate::sync::AuthDb>,
pub admin_sessions: Arc<Mutex<HashMap<String, AdminSession>>>,
}
impl AuthState {
pub fn new() -> Self {
let mut users = HashMap::new();
// Create default demo user
let password_hash = hash("demo123", DEFAULT_COST).unwrap();
users.insert(
"demo".to_string(),
User {
user_id: "demo".to_string(),
username: "demo".to_string(),
password_hash,
created_at: Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string(),
},
);
AuthState {
sessions: Arc::new(Mutex::new(HashMap::new())),
users: Arc::new(Mutex::new(users)),
auth_db: None,
admin_sessions: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn with_sync(auth_db_path: &str) -> Self {
let auth_db = crate::sync::AuthDb::new(auth_db_path).ok();
AuthState {
sessions: Arc::new(Mutex::new(HashMap::new())),
users: Arc::new(Mutex::new(HashMap::new())),
auth_db,
admin_sessions: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn login(&self, username: &str, password: &str) -> Option<LoginResponse> {
let users = self.users.lock().unwrap();
let user = users.get(username)?;
if verify(password, &user.password_hash).unwrap_or(false) {
let token = Uuid::new_v4().to_string();
let now = Utc::now();
let expires_at = now + Duration::hours(24);
let session = Session {
token: token.clone(),
user_id: user.user_id.clone(),
username: user.username.clone(),
created_at: now.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
groups: vec![],
permissions: "{}".to_string(),
};
let mut sessions = self.sessions.lock().unwrap();
sessions.insert(token.clone(), session);
Some(LoginResponse {
token,
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
user_id: user.user_id.clone(),
groups: vec![],
permissions: "{}".to_string(),
})
} else {
None
}
}
pub fn admin_login(&self, username: &str, password: &str) -> Option<AdminLoginResponse> {
if let Some(auth_db) = &self.auth_db {
match auth_db.get_admin(username) {
Ok(Some(admin)) if admin.status == 1 => {
if verify(password, &admin.password_hash).unwrap_or(false) {
let token = Uuid::new_v4().to_string();
let now = Utc::now();
let expires_at = now + Duration::hours(24);
let session = AdminSession {
token: token.clone(),
username: username.to_string(),
created_at: now.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
};
let mut admin_sessions = self.admin_sessions.lock().unwrap();
admin_sessions.insert(token.clone(), session);
log::info!("Admin {} logged in successfully", username);
Some(AdminLoginResponse {
token,
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
username: username.to_string(),
})
} else {
log::warn!("Invalid password for admin {}", username);
None
}
}
Ok(Some(_)) => {
log::warn!("Admin {} is not active", username);
None
}
Ok(None) => {
log::warn!("Admin {} not found", username);
None
}
Err(e) => {
log::error!("Failed to get admin {}: {}", username, e);
None
}
}
} else {
log::warn!("Auth DB not available for admin login");
None
}
}
pub fn verify_admin_token(&self, token: &str) -> Option<AdminSession> {
let admin_sessions = self.admin_sessions.lock().unwrap();
if let Some(session) = admin_sessions.get(token) {
let expires_at = chrono::DateTime::parse_from_rfc3339(&session.expires_at)
.ok()
.map(|dt| dt.with_timezone(&Utc));
if let Some(exp) = expires_at {
if Utc::now() < exp {
return Some(session.clone());
} else {
log::warn!("Admin token {} has expired", token);
}
}
}
None
}
pub fn login_with_sync(&self, username: &str, password: &str) -> Option<LoginResponse> {
if let Some(auth_db) = &self.auth_db {
// Get user from auth.sqlite
let user = match auth_db.get_user(username) {
Ok(Some(user)) => user,
Ok(None) => {
log::warn!("User {} not found in auth database", username);
return None;
}
Err(e) => {
log::error!("Failed to get user {}: {}", username, e);
return None;
}
};
if user.status != 1 {
log::warn!("User {} is disabled", username);
return None;
}
if verify(password, &user.password_hash).unwrap_or(false) {
let groups = auth_db.get_user_groups(username).unwrap_or_default();
let permissions = user.permissions.clone();
let token = Uuid::new_v4().to_string();
let now = Utc::now();
let expires_at = now + Duration::hours(24);
let session = Session {
token: token.clone(),
user_id: username.to_string(),
username: username.to_string(),
created_at: now.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
groups: groups.clone(),
permissions: permissions.clone(),
};
let mut sessions = self.sessions.lock().unwrap();
sessions.insert(token.clone(), session);
log::info!("User {} logged in successfully", username);
Some(LoginResponse {
token,
expires_at: expires_at.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
user_id: username.to_string(),
groups,
permissions,
})
} else {
log::warn!("Invalid password for user {}", username);
None
}
} else {
self.login(username, password)
}
}
pub fn verify_token(&self, token: &str) -> Option<Session> {
let sessions = self.sessions.lock().unwrap();
let session = sessions.get(token)?;
// Check expiration
let expires_at = chrono::DateTime::parse_from_rfc3339(&session.expires_at)
.ok()?
.with_timezone(&Utc);
if Utc::now() > expires_at {
return None;
}
Some(session.clone())
}
pub fn logout(&self, token: &str) -> bool {
let mut sessions = self.sessions.lock().unwrap();
sessions.remove(token).is_some()
}
pub fn create_user(&self, username: &str, password: &str) -> Result<String, String> {
let mut users = self.users.lock().unwrap();
if users.contains_key(username) {
return Err("User already exists".to_string());
}
let password_hash = hash(password, DEFAULT_COST).map_err(|e| e.to_string())?;
let user_id = Uuid::new_v4().to_string();
let user = User {
user_id: user_id.clone(),
username: username.to_string(),
password_hash,
created_at: Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string(),
};
users.insert(username.to_string(), user);
Ok(user_id)
}
}
// Authorization header parser
pub fn parse_auth_header(header: &str) -> Option<String> {
if header.starts_with("Bearer ") {
Some(header.trim_start_matches("Bearer ").to_string())
} else {
None
}
}