# MarkBase Authentication System Design

**文档版本：** 1.0  
**创建日期：** 2026-05-16  
**最后更新：** 2026-05-16  
**作者：** Warren Lo  

---

## 目录

1. [Overview（概述）](#1-overview概述)
2. [Sync Architecture（同步架构）](#2-sync-architecture同步架构)
3. [Authentication Flow（认证流程）](#3-authentication-flow认证流程)
4. [API Design（API设计）](#4-api-designapi设计)
5. [Database Design（数据库设计）](#5-database-design数据库设计)
6. [Test Results（测试记录）](#6-test-results测试记录)
7. [Future Enhancements（未来扩展）](#7-future-enhancements未来扩展)
8. [Design Decisions（设计决策）](#8-design-decisions设计决策)
9. [Appendix（附录）](#9-appendix附录)

---

## 1. Overview（概述）

### 1.1 设计目标

MarkBase认证系统旨在与SFTPGo认证系统实现无缝集成，提供统一的认证入口和管理机制。核心设计理念是：

- **统一认证**：通过SFTPGo管理用户，MarkBase提供认证服务
- **高可用性**：即使PostgreSQL故障，仍可使用缓存数据认证
- **性能优化**：本地SQLite查询避免网络延迟
- **扩展性**：支持groups、permissions，易于未来扩展RBAC

### 1.2 核心特性

#### Token-based Authentication
- UUID Token生成（`Uuid::new_v4()`）
- 24小时有效期
- In-memory Session管理（`HashMap<String, Session>`）
- 支持Bearer Token认证（Authorization header）

#### PostgreSQL同步
- SFTPGo PostgreSQL → auth.sqlite
- 三种同步模式：
  - **启动时同步**（server startup）
  - **每小时同步**（hourly interval）
  - **手动同步**（manual API）
- 失败时使用缓存fallback

#### bcrypt密码验证
- bcrypt hash存储（cost=10）
- 与SFTPGo密码格式一致
- 统一密码管理（SFTPGo负责密码设置）

#### Groups + Permissions支持
- 用户群组关系同步（users_groups_mapping表）
- 权限信息同步（permissions字段）
- Session包含groups和permissions

### 1.3 技术栈

|技术组件 |版本 |用途 |
|----------|------|------|
| Rust | 1.92+ | 核心实作语言 |
| Axum | 0.7 | Web framework（REST API） |
| SQLite | 3.x | 本地认证数据库 |
| tokio-postgres | 0.7 | PostgreSQL客户端 |
| bcrypt | 0.15 | 密码hash验证 |
| uuid | 1.0 | Token生成 |
| chrono | 0.4 | 时间处理 |
| serde | 1.0 | JSON序列化 |

### 1.4 模组划分

认证系统由以下Rust模组组成：

|模组 |档案 |行数 |核心功能 |
|------|------|------|----------|
| auth.rs | src/auth.rs | 225 | 认证核心逻辑 |
| sync.rs | src/sync.rs | 273 | 同步逻辑与数据结构 |
| pg_client.rs | src/pg_client.rs | 247 | PostgreSQL客户端 |
| server.rs | src/server.rs | ~200 | API handlers与启动tasks |

**代码量统计：**
- 核心模组：945行（auth + sync + pg_client）
- 相关API handlers：~200行（server.rs部分）
- 总计：~1145行

### 1.5 核心数据结构

#### Session结构（auth.rs:17）

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
    pub token: String,           // UUID token
    pub user_id: String,         // 用户ID
    pub username: String,        // 用户名
    pub created_at: String,      // 创建时间（RFC3339）
    pub expires_at: String,      // 过期时间（RFC3339）
    pub groups: Vec<String>,     // 用户所属群组
    pub permissions: String,     // 权限配置（JSON string）
}
```

#### LoginResponse结构（auth.rs:34）

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginResponse {
    pub token: String,           // UUID token
    pub expires_at: String,      // 过期时间
    pub user_id: String,         // 用户ID
    pub groups: Vec<String>,     // 用户群组
    pub permissions: String,     // 权限配置
}
```

#### AuthState结构（auth.rs:43）

```rust
#[derive(Clone)]
pub struct AuthState {
    pub sessions: Arc<Mutex<HashMap<String, Session>>>,  // In-memory sessions
    pub users: Arc<Mutex<HashMap<String, User>>>,        // 默认用户（fallback）
    pub auth_db: Option<crate::sync::AuthDb>,            // auth.sqlite连接
}
```

### 1.6 系统架构概览

```
┌─────────────────────────────────────────────────────────┐
│                     MarkBase System                     │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  ┌─────────────┐        ┌──────────────┐               │
│  │  SFTPGo     │───────>│ PostgreSQL   │               │
│  │  (Users)    │        │  (users表)    │               │
│  └─────────────┘        └──────────────┘               │
│                                 │                        │
│                                 │ Sync                   │
│                                 ▼                        │
│                         ┌────────────┐                  │
│                         │auth.sqlite │                  │
│                         │(synced)    │                  │
│                         └────────────┘                  │
│                                 │                        │
│                                 │ Auth                   │
│                                 ▼                        │
│                         ┌────────────┐                  │
│                         │ MarkBase   │                  │
│                         │  Auth API  │                  │
│                         └────────────┘                  │
│                                 │                        │
│                                 │ Token                  │
│                                 ▼                        │
│                         ┌────────────┐                  │
│                         │ Protected   │                 │
│                         │  APIs       │                 │
│                         └────────────┘                  │
│                                                          │
└─────────────────────────────────────────────────────────┘
```

---

## 2. Sync Architecture（同步架构）

### 2.1 系统架构图

完整的同步流程架构：

```
┌─────────────┐        ┌──────────────┐        ┌────────────┐
│  SFTPGo     │───────>│ PostgreSQL   │───────>│auth.sqlite │
│  (Users)    │        │  (users表)    │        │(synced)    │
│             │        │              │        │            │
│ - warren    │        │ - users      │        │ - users    │
│ - momentry  │        │ - groups     │        │ - groups   │
│ - demo      │        │ - mapping    │        │ - mapping  │
└─────────────┘        └──────────────┘        └────────────┘
                                                       │
                                                       ▼
                                               ┌────────────┐
                                               │ MarkBase   │
                                               │  Auth API  │
                                               │            │
                                               │ - login    │
                                               │ - verify   │
                                               │ - logout   │
                                               └────────────┘
                                                       │
                                                       ▼
                                               ┌────────────┐
                                               │ Protected   │
                                               │  APIs       │
                                               │            │
                                               │ - /tree    │
                                               │ - /files   │
                                               └────────────┘
```

### 2.2 同步流程详解

#### 2.2.1 启动时同步（Startup Sync）

**位置：** server.rs:62

**实现代码：**

```rust
// Server startup sync task
tokio::spawn(async move {
    let syncer = crate::pg_client::SftpGoSync::new(&auth_db_path);
    match syncer {
        Ok(syncer) => {
            match syncer.full_sync().await {
                Ok(result) => {
                    log::info!(
                        "Initial sync completed: users={}, groups={}, mappings={}, status={}",
                        result.users_synced,
                        result.groups_synced,
                        result.mappings_synced,
                        result.status
                    );
                }
                Err(e) => {
                    log::error!("Initial sync failed: {}", e);
                    // Continue using cached auth.sqlite
                }
            }
        }
        Err(e) => {
            log::error!("Failed to create syncer: {}", e);
        }
    }
});
```

**执行时机：**
- 服务器启动时立即执行（tokio::spawn异步执行）
- 在其他API handlers初始化之前完成
- 失败时继续启动，使用cached auth.sqlite

**目的：**
- 确保启动时认证数据是最新的
- 初始化auth.sqlite（如果不存在）
- 记录同步状态到sync_log表

#### 2.2.2 每小时同步（Hourly Sync）

**位置：** server.rs:81

**实现代码：**

```rust
// Hourly sync task
tokio::spawn(async move {
    let mut interval = tokio::time::interval(
        std::time::Duration::from_secs(3600)  // 1 hour = 3600 seconds
    );
    
    loop {
        interval.tick().await;  // Wait for next interval
        
        match syncer_clone.full_sync().await {
            Ok(result) => {
                log::info!(
                    "Hourly sync: users={}, groups={}, mappings={}, status={}",
                    result.users_synced,
                    result.groups_synced,
                    result.mappings_synced,
                    result.status
                );
            }
            Err(e) => {
                log::error!("Hourly sync failed: {}", e);
                // Continue using cached data
            }
        }
    }
});
```

**执行策略：**
- 每3600秒（1小时）执行一次
- 失败时继续运行，不影响服务
- 记录每次同步结果到sync_log表

**优势：**
- 定期更新认证数据
- 平衡数据新鲜度与系统负载
- 最大延迟：1小时

#### 2.2.3 手动同步API（Manual Sync）

**位置：** server.rs:1356

**API Endpoint：** `/api/v2/admin/sync` (POST)

**实现代码：**

```rust
async fn manual_sync_handler(
    State(state): State<AppState>,
) -> impl IntoResponse {
    let syncer = crate::pg_client::SftpGoSync::new(&state.auth_db_path);
    
    match syncer {
        Ok(syncer) => {
            match syncer.full_sync().await {
                Ok(result) => {
                    if result.status == "success" {
                        (
                            StatusCode::OK,
                            Json(serde_json::json!({
                                "status": "success",
                                "users_synced": result.users_synced,
                                "groups_synced": result.groups_synced,
                                "mappings_synced": result.mappings_synced
                            }))
                        ).into_response()
                    } else if result.status == "partial_success" {
                        (
                            StatusCode::OK,
                            Json(serde_json::json!({
                                "status": "partial_success",
                                "users_synced": result.users_synced,
                                "users_failed": result.users_failed,
                                "groups_synced": result.groups_synced,
                                "groups_failed": result.groups_failed,
                                "errors": result.errors
                            }))
                        ).into_response()
                    } else {
                        (
                            StatusCode::INTERNAL_SERVER_ERROR,
                            Json(serde_json::json!({
                                "status": "failed",
                                "errors": result.errors
                            }))
                        ).into_response()
                    }
                }
                Err(e) => {
                    (
                        StatusCode::INTERNAL_SERVER_ERROR,
                        Json(serde_json::json!({
                            "status": "error",
                            "message": e.to_string()
                        }))
                    ).into_response()
                }
            }
        }
        Err(e) => {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "status": "error",
                    "message": format!("Failed to create syncer: {}", e)
                }))
            ).into_response()
        }
    }
}
```

**使用场景：**
- 用户管理后立即同步（新增/删除用户）
- 测试认证系统时手动触发
- 监控同步状态时检查最新数据

**响应格式：**

成功响应：
```json
{
  "status": "success",
  "users_synced": 3,
  "groups_synced": 1,
  "mappings_synced": 0
}
```

部分成功：
```json
{
  "status": "partial_success",
  "users_synced": 2,
  "users_failed": 1,
  "groups_synced": 1,
  "groups_failed": 0,
  "errors": ["Connection timeout for user xyz"]
}
```

失败响应：
```json
{
  "status": "failed",
  "errors": ["PostgreSQL connection refused"]
}
```

### 2.3 PostgreSQL连接配置

#### 2.3.1 默认配置（pg_client.rs:14-21）

```rust
pub struct PgClient {
    host: String,
    port: u16,
    user: String,
    password: String,
    database: String,
}

impl PgClient {
    pub fn new() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 5432,
            user: "sftpgo".to_string(),
            password: "sftpgo_pass_2026".to_string(),
            database: "sftpgo".to_string(),
        }
    }
}
```

**默认值说明：**
- `host`: `127.0.0.1`（本地PostgreSQL）
- `port`: `5432`（PostgreSQL默认端口）
- `user`: `sftpgo`（SFTPGo专用用户）
- `password`: `sftpgo_pass_2026`（预设密码）
- `database`: `sftpgo`（SFTPGo数据库）

#### 2.3.2 环境变量配置（优先）

**位置：** pg_client.rs:24-38

```rust
pub fn from_env() -> Self {
    Self {
        host: std::env::var("PG_HOST")
            .unwrap_or_else(|_| "127.0.0.1".to_string()),
        port: std::env::var("PG_PORT")
            .unwrap_or_else(|_| "5432".to_string())
            .parse()
            .unwrap_or(5432),
        user: std::env::var("PG_USER")
            .unwrap_or_else(|_| "sftpgo".to_string()),
        password: std::env::var("PG_PASSWORD")
            .unwrap_or_else(|_| "sftpgo_pass_2026".to_string()),
        database: std::env::var("PG_DATABASE")
            .unwrap_or_else(|_| "sftpgo".to_string()),
    }
}
```

**环境变量列表：**

|环境变量 |默认值 |说明 |
|----------|--------|------|
| `PG_HOST` | 127.0.0.1 | PostgreSQL服务器地址 |
| `PG_PORT` | 5432 | PostgreSQL端口 |
| `PG_USER` | sftpgo | PostgreSQL用户名 |
| `PG_PASSWORD` | sftpgo_pass_2026 | PostgreSQL密码 |
| `PG_DATABASE` | sftpgo | 数据库名称 |

**使用范例：**

```bash
# 配置环境变量
export PG_HOST=192.168.1.100
export PG_PORT=5432
export PG_USER=sftpgo_admin
export PG_PASSWORD=secure_password_here
export PG_DATABASE=sftpgo_production

# 启动MarkBase（使用环境变量配置）
cargo run --release -- display
```

#### 2.3.3 连接字符串生成

**位置：** pg_client.rs:40-44

```rust
pub fn connection_string(&self) -> String {
    format!(
        "postgres://{}:{}@{}:{}/{}",
        self.user, self.password, self.host, self.port, self.database
    )
}
```

**生成的连接字符串范例：**
```
postgres://sftpgo:sftpgo_pass_2026@127.0.0.1:5432/sftpgo
```

### 2.4 失败处理策略

#### 2.4.1 PostgreSQL连接失败

**处理流程：**
```
1. 尝试连接PostgreSQL
2. 连接失败 →记录错误到sync_log
3. 继续使用cached auth.sqlite
4. 认证请求正常处理（使用缓存数据）
5. 下次hourly sync再次尝试连接
```

**优势：**
- 服务不中断（高可用性）
- 最大数据延迟：1小时（hourly sync）
- 用户管理操作不受影响

#### 2.4.2 同步部分失败

**处理策略：**
- **用户同步失败**：跳过该用户，继续同步其他用户
- **群组同步失败**：跳过该群组，继续同步其他群组
- **映射同步失败**：跳过该映射，继续同步其他映射

**状态记录：**
```rust
pub struct SyncResult {
    pub sync_type: String,         // "full"
    pub sync_time: i64,            // Unix timestamp
    pub users_synced: usize,       // 成功同步的用户数
    pub users_failed: usize,       // 失败的用户数
    pub groups_synced: usize,      // 成功同步的群组数
    pub groups_failed: usize,      // 失败的群组数
    pub mappings_synced: usize,    // 成功同步的映射数
    pub status: String,            // "success" / "partial_success" / "failed"
    pub errors: Vec<String>,       // 错误信息列表
}
```

**状态判断逻辑（sync.rs:99-105）：**
```rust
if self.users_failed > 0 || self.groups_failed > 0 || self.mappings_failed > 0 {
    if self.users_synced > 0 || self.groups_synced > 0 || self.mappings_synced > 0 {
        self.status = "partial_success".to_string();
    } else {
        self.status = "failed".to_string();
    }
}
```

#### 2.4.3 缓存Fallback机制

**触发条件：**
- PostgreSQL连接失败
- auth.sqlite不存在（首次启动）
- 同步完全失败

**Fallback流程：**
```
1. 尝试从auth.sqlite获取用户
2. auth.sqlite不存在 →尝试默认用户（auth.rs:50 "demo"）
3. 默认用户认证 →返回token
4. 记录警告日志："Using fallback auth"
```

**代码实现（auth.rs:116）：**
```rust
pub fn login_with_sync(&self, username: &str, password: &str) -> Option<LoginResponse> {
    if let Some(auth_db) = &self.auth_db {
        // Try synced auth first
        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;
            }
        };
        
        // Verify password and create session...
    } else {
        // Fallback to default auth (auth.rs:82)
        self.login(username, password)
    }
}
```

### 2.5 同步数据结构

#### 2.5.1 PgUser结构（sync.rs:8-20）

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgUser {
    pub username: String,        // 用户名（主键）
    pub password_hash: String,   // bcrypt hash
    pub email: Option<String>,   // 邮箱（可选）
    pub status: i32,             // 状态（1=active, 0=disabled）
    pub home_dir: String,        // 主目录路径
    pub permissions: String,     // 权限配置（JSON）
    pub uid: i64,                // UID
    pub gid: i64,                // GID
    pub last_login: i64,         // 最后登录时间（timestamp）
    pub created_at: i64,         // 创建时间
    pub updated_at: i64,         // 更新时间
}
```

**字段对应关系：**

|PgUser字段 |PostgreSQL字段 |auth.sqlite字段 |
|------------|----------------|-----------------|
| username | users.username | sftpgo_users.username |
| password_hash | users.password | sftpgo_users.password_hash |
| email | users.email | sftpgo_users.email |
| status | users.status | sftpgo_users.status |
| home_dir | users.home_dir | sftpgo_users.home_dir |
| permissions | users.permissions | sftpgo_users.permissions |
| uid | users.uid | sftpgo_users.uid |
| gid | users.gid | sftpgo_users.gid |
| last_login | users.last_login | sftpgo_users.last_login |
| created_at | users.created_at | sftpgo_users.created_at |
| updated_at | users.updated_at | sftpgo_users.updated_at |

#### 2.5.2 PgGroup结构（sync.rs:22-28）

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgGroup {
    pub name: String,            // 群组名（主键）
    pub description: String,     // 群组描述
    pub created_at: i64,         // 创建时间
    pub updated_at: i64,         // 更新时间
}
```

#### 2.5.3 PgUserGroupMapping结构（sync.rs:30-35）

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgUserGroupMapping {
    pub username: String,        // 用户名
    pub group_name: String,      // 群组名
}
```

#### 2.5.4 SyncResult结构（sync.rs:38-50）

```rust
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyncResult {
    pub sync_type: String,         // 同步类型（"full"）
    pub sync_time: i64,            // 同步时间（Unix timestamp）
    pub users_synced: usize,       // 成功同步的用户数
    pub users_failed: usize,       // 失败的用户数
    pub groups_synced: usize,      // 成功同步的群组数
    pub groups_failed: usize,      // 失败的群组数
    pub mappings_synced: usize,    // 成功同步的映射数
    pub mappings_failed: usize,    // 失败的映射数
    pub status: String,            // 状态（success/partial_success/failed）
    pub errors: Vec<String>,       // 错误信息列表
}
```

### 2.6 同步实现详解

#### 2.6.1 full_sync函数（pg_client.rs:180-220）

```rust
pub async fn full_sync(&self) -> Result<SyncResult> {
    let mut result = SyncResult {
        sync_type: "full".to_string(),
        sync_time: Utc::now().timestamp(),
        status: "success".to_string(),
        ..Default::default()
    };
    
    // Sync users
    let users = self.pg_client.fetch_users().await?;
    for user in users {
        match self.auth_db.save_user(&user) {
            Ok(_) => result.users_synced += 1,
            Err(e) => {
                result.users_failed += 1;
                result.errors.push(format!("User {}: {}", user.username, e));
            }
        }
    }
    
    // Sync groups
    let groups = self.pg_client.fetch_groups().await?;
    for group in groups {
        match self.auth_db.save_group(&group) {
            Ok(_) => result.groups_synced += 1,
            Err(e) => {
                result.groups_failed += 1;
                result.errors.push(format!("Group {}: {}", group.name, e));
            }
        }
    }
    
    // Sync mappings
    let mappings = self.pg_client.fetch_mappings().await?;
    for mapping in mappings {
        match self.auth_db.save_mapping(&mapping) {
            Ok(_) => result.mappings_synced += 1,
            Err(e) => {
                result.mappings_failed += 1;
                result.errors.push(format!("Mapping {}: {}", mapping.username, e));
            }
        }
    }
    
    // Update status
    result.update_status();
    
    // Save sync log
    self.auth_db.save_sync_log(&result)?;
    
    Ok(result)
}
```

#### 2.6.2 fetch_users函数（pg_client.rs:60-100）

```rust
pub async fn fetch_users(&self) -> Result<Vec<PgUser>> {
    let (client, connection) = tokio_postgres::connect(
        &self.connection_string(),
        NoTls,
    ).await?;
    
    // Spawn connection handler
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            log::error!("PostgreSQL connection error: {}", e);
        }
    });
    
    // Query users
    let rows = client.query(
        "SELECT username, password, email, status, home_dir, permissions,
                uid, gid, last_login, created_at, updated_at
         FROM users WHERE status = 1",
        &[],
    ).await?;
    
    let users: Vec<PgUser> = rows.iter().map(|row| PgUser {
        username: row.get(0),
        password_hash: row.get(1),
        email: row.get(2),
        status: row.get(3),
        home_dir: row.get(4),
        permissions: row.get(5),
        uid: row.get(6),
        gid: row.get(7),
        last_login: row.get(8),
        created_at: row.get(9),
        updated_at: row.get(10),
    }).collect();
    
    log::info!("Fetched {} users from PostgreSQL", users.len());
    Ok(users)
}
```

#### 2.6.3 save_user函数（sync.rs:120-145）

```rust
pub fn save_user(&self, user: &PgUser) -> Result<()> {
    let conn = self.open()?;
    let now = Utc::now().timestamp();
    
    conn.execute(
        "INSERT OR REPLACE INTO sftpgo_users 
         (username, password_hash, email, status, home_dir, permissions, 
          uid, gid, last_login, created_at, updated_at, last_sync_at, sync_status)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
        params![
            user.username,
            user.password_hash,
            user.email,
            user.status,
            user.home_dir,
            user.permissions,
            user.uid,
            user.gid,
            user.last_login,
            user.created_at,
            user.updated_at,
            now,  // last_sync_at
            1,    // sync_status = 1 (success)
        ],
    )?;
    
    log::info!("Saved user {} to auth.sqlite", user.username);
    Ok(())
}
```

**关键点：**
- 使用`INSERT OR REPLACE`确保更新现有数据
- 添加`last_sync_at`时间戳
- 设置`sync_status=1`表示同步成功

---

## 3. Authentication Flow（认证流程）

### 3.1 Login流程详解

#### 3.1.1 流程图

```
Client Request                    Server Processing                 Response
    │                                   │                               │
    │ POST /api/v2/auth/login           │                               │
    │ {username, password}              │                               │
    ├──────────────────────────────────>│                               │
    │                                   │ 1. Parse request              │
    │                                   │                               │
    │                                   │ 2. Query auth.sqlite          │
    │                                   │    get_user(username)         │
    │                                   │                               │
    │                                   │ 3. Check user status          │
    │                                   │    if status != 1 → None      │
    │                                   │                               │
    │                                   │ 4. bcrypt verify              │
    │                                   │    verify(password, hash)     │
    │                                   │                               │
    │                                   │ 5. Get groups                 │
    │                                   │    get_user_groups(username)  │
    │                                   │                               │
    │                                   │ 6. Generate UUID token        │
    │                                   │    Uuid::new_v4()             │
    │                                   │                               │
    │                                   │ 7. Calculate expiry (24h)     │
    │                                   │                               │
    │                                   │ 8. Create Session             │
    │                                   │    HashMap.insert(token)      │
    │                                   │                               │
    │                                   │ 9. Build response             │
    │                                   │                               │
    │                                   ├───────────────────────────────>│
    │                                   │ {token, expires_at,           │
    │                                   │  user_id, groups,             │
    │                                   │  permissions}                 │
    │<──────────────────────────────────┤                               │
    │                                   │                               │
```

#### 3.1.2 Login实现代码（auth.rs:116）

**完整代码：**

```rust
pub fn login_with_sync(&self, username: &str, password: &str) -> Option<LoginResponse> {
    if let Some(auth_db) = &self.auth_db {
        // Step 1: 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;
            }
        };
        
        // Step 2: Check user status
        if user.status != 1 {
            log::warn!("User {} is disabled", username);
            return None;
        }
        
        // Step 3: bcrypt password verification
        if verify(password, &user.password_hash).unwrap_or(false) {
            // Step 4: Get user groups
            let groups = auth_db.get_user_groups(username).unwrap_or_default();
            let permissions = user.permissions.clone();
            
            // Step 5: Generate UUID token
            let token = Uuid::new_v4().to_string();
            let now = Utc::now();
            let expires_at = now + Duration::hours(24);
            
            // Step 6: Create Session
            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(),
            };
            
            // Step 7: Insert to in-memory HashMap
            let mut sessions = self.sessions.lock().unwrap();
            sessions.insert(token.clone(), session);
            
            log::info!("User {} logged in successfully", username);
            
            // Step 8: Return LoginResponse
            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 {
        // Fallback to default auth
        self.login(username, password)
    }
}
```

#### 3.1.3 get_user实现（sync.rs:232）

```rust
pub fn get_user(&self, username: &str) -> Result<Option<PgUser>> {
    let conn = self.open()?;
    
    let result = conn.query_row(
        "SELECT username, password_hash, email, status, home_dir, permissions,
                uid, gid, last_login, created_at, updated_at
         FROM sftpgo_users WHERE username = ?1 AND status = 1",
        params![username],
        |row| Ok(PgUser {
            username: row.get(0)?,
            password_hash: row.get(1)?,
            email: row.get(2)?,
            status: row.get(3)?,
            home_dir: row.get(4)?,
            permissions: row.get(5)?,
            uid: row.get(6)?,
            gid: row.get(7)?,
            last_login: row.get(8)?,
            created_at: row.get(9)?,
            updated_at: row.get(10)?,
        })
    );
    
    match result {
        Ok(user) => Ok(Some(user)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(e) => Err(e.into()),
    }
}
```

**查询条件：**
- `username = ?1`：精确匹配用户名
- `status = 1`：只查询active用户

#### 3.1.4 get_user_groups实现（sync.rs:260）

```rust
pub fn get_user_groups(&self, username: &str) -> Result<Vec<String>> {
    let conn = self.open()?;
    
    let groups: Vec<String> = conn
        .prepare(
            "SELECT group_name FROM users_groups_mapping WHERE username = ?1"
        )?
        .query_map(params![username], |row| row.get(0))?
        .collect::<Result<Vec<_>, _>>()?;
    
    Ok(groups)
}
```

**查询逻辑：**
- 从`users_groups_mapping`表查询用户所属群组
- 返回`Vec<String>`（群组名列表）

#### 3.1.5 bcrypt验证细节

**bcrypt库使用：**

```rust
use bcrypt::{verify, DEFAULT_COST};

// 密码验证
if verify(password, &user.password_hash).unwrap_or(false) {
    // 密码匹配
} else {
    // 密码不匹配
}
```

**密码hash格式：**
```
$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6
```

格式解析：
- `$2b$`：bcrypt算法标识
- `10$`：cost factor（10轮）
- `ha5wU...`：salt + hash

**验证流程：**
1. 从hash提取salt
2. 使用相同salt对输入密码hash
3. 比较生成的hash与存储的hash

### 3.2 Token验证流程

#### 3.2.1 流程图

```
Client Request                    Server Processing                 Response
    │                                   │                               │
    │ GET /api/v2/tree/demo             │                               │
    │ Authorization: Bearer <token>     │                               │
    ├──────────────────────────────────>│                               │
    │                                   │ 1. Parse Authorization header │
    │                                   │    parse_auth_header()        │
    │                                   │                               │
    │                                   │ 2. Query Session HashMap      │
    │                                   │    sessions.get(token)        │
    │                                   │                               │
    │                                   │ 3. Check expiration           │
    │                                   │    parse RFC3339              │
    │                                   │    compare with Utc::now()    │
    │                                   │                               │
    │                                   │ 4. Return Session or None     │
    │                                   │                               │
    │                                   ├───────────────────────────────>│
    │                                   │ {valid: true/false}           │
    │<──────────────────────────────────┤                               │
    │                                   │                               │
```

#### 3.2.2 verify_token实现（auth.rs:175）

```rust
pub fn verify_token(&self, token: &str) -> Option<Session> {
    // Step 1: Get Session from HashMap
    let sessions = self.sessions.lock().unwrap();
    let session = sessions.get(token)?;
    
    // Step 2: Parse expiration time
    let expires_at = chrono::DateTime::parse_from_rfc3339(&session.expires_at)
        .ok()?
        .with_timezone(&Utc);
    
    // Step 3: Check expiration
    if Utc::now() > expires_at {
        return None;  // Token expired
    }
    
    // Step 4: Return Session
    Some(session.clone())
}
```

**关键点：**
- In-memory HashMap查询（性能高）
- RFC3339时间格式解析
- 过期时间比较（`Utc::now() > expires_at`）

#### 3.2.3 parse_auth_header实现（auth.rs:210）

```rust
pub fn parse_auth_header(header: &str) -> Option<String> {
    if header.starts_with("Bearer ") {
        Some(header.trim_start_matches("Bearer ").to_string())
    } else {
        None
    }
}
```

**Header格式：**
```
Authorization: Bearer 8d85c37d-8cc2-4633-a838-5400bb88dc6f
```

提取逻辑：
- 检查是否以`"Bearer "`开头
- 提取token部分（去除`"Bearer "`前缀）

#### 3.2.4 Protected API认证middleware

**位置：** server.rs各handler中

**典型实现：**

```rust
async fn get_tree(
    State(state): State<AppState>,
    Path(user_id): Path<String>,
    headers: HeaderMap,
) -> impl IntoResponse {
    // Step 1: Parse Authorization header
    let auth_header = headers
        .get("Authorization")
        .and_then(|h| h.to_str().ok())
        .and_then(|h| crate::auth::parse_auth_header(h));
    
    match auth_header {
        Some(token) => {
            // Step 2: Verify token
            match state.auth.verify_token(&token) {
                Some(session) => {
                    // Step 3: Check user_id match
                    if session.user_id != user_id {
                        return (
                            StatusCode::FORBIDDEN,
                            Json(json!({"error": "Access denied"}))
                        ).into_response();
                    }
                    
                    // Step 4: Process request
                    let tree = FileTree::load(&state.db_path, &user_id);
                    // Return tree data...
                }
                None => (
                    StatusCode::UNAUTHORIZED,
                    Json(json!({"error": "Unauthorized"}))
                ).into_response(),
            }
        }
        None => (
            StatusCode::BAD_REQUEST,
            Json(json!({"error": "Missing Authorization header"}))
        ).into_response(),
    }
}
```

### 3.3 Logout流程

#### 3.3.1 logout实现（auth.rs:181）

```rust
pub fn logout(&self, token: &str) -> bool {
    let mut sessions = self.sessions.lock().unwrap();
    sessions.remove(token).is_some()
}
```

**流程：**
1. 从HashMap中移除Session
2. 返回`true`（成功移除）或`false`（token不存在）

#### 3.3.2 Logout API handler（server.rs:1240）

```rust
async fn logout_handler(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> impl IntoResponse {
    let auth_header = headers
        .get("Authorization")
        .and_then(|h| h.to_str().ok())
        .and_then(|h| crate::auth::parse_auth_header(h));
    
    match auth_header {
        Some(token) => {
            if state.auth.logout(&token) {
                (
                    StatusCode::OK,
                    Json(serde_json::json!({"success": true}))
                ).into_response()
            } else {
                (
                    StatusCode::NOT_FOUND,
                    Json(serde_json::json!({"error": "Token not found"}))
                ).into_response()
            }
        }
        None => (
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({"error": "Missing Authorization header"}))
        ).into_response(),
    }
}
```

### 3.4 Session管理策略

#### 3.4.1 In-memory HashMap

**数据结构：**

```rust
pub struct AuthState {
    pub sessions: Arc<Mutex<HashMap<String, Session>>>,
    // ...
}
```

**优势：**
- 性能高（无磁盘IO）
- 易于实现（HashMap天然支持）
- 无外部依赖

**劣势：**
- 服务重启后Session丢失（用户需重新登录）
- 内存占用（大量Session时）

#### 3.4.2 Session生命周期

```
┌─────────────┐
│  Login      │──────> Create Session
│             │        - Generate UUID token
│             │        - Set 24h expiry
│             │        - Insert to HashMap
└─────────────┘
        │
        │
        ▼
┌─────────────┐
│  Active     │──────> Session in HashMap
│  Period     │        - Token valid
│             │        - Can access APIs
│  (0-24h)    │
└─────────────┘
        │
        │
        ▼
┌─────────────┐
│  Expiry     │──────> Session invalid
│  Check      │        - verify_token() returns None
│             │        - User must re-login
│  (>24h)     │
└─────────────┘
        │
        │
        ▼
┌─────────────┐
│  Logout     │──────> Remove Session
│             │        - HashMap.remove(token)
│             │        - Token invalid immediately
└─────────────┘
```

#### 3.4.3 Session并发处理

**使用Arc<Mutex>：**

```rust
Arc<Mutex<HashMap<String, Session>>>
```

**作用：**
- `Arc`：多线程共享所有权
- `Mutex`：互斥锁，防止并发冲突

**操作流程：**
```rust
// Lock Mutex
let mut sessions = self.sessions.lock().unwrap();

// Modify HashMap
sessions.insert(token.clone(), session);

// Mutex automatically unlocked when `sessions` goes out of scope
```

---

## 4. API Design（API设计）

### 4.1 Authentication APIs

#### 4.1.1 Login API

**Endpoint:** `/api/v2/auth/login`

**Method:** POST

**Request Headers:**
```
Content-Type: application/json
```

**Request Body:**
```json
{
  "username": "demo",
  "password": "demo123"
}
```

**成功响应（200 OK）：**
```json
{
  "token": "8d85c37d-8cc2-4633-a838-5400bb88dc6f",
  "expires_at": "2026-05-17T10:39:05Z",
  "user_id": "demo",
  "groups": [],
  "permissions": "{\"/*\": [\"*\"]}"
}
```

**失败响应（401 Unauthorized）：**
```json
{
  "error": "Invalid credentials"
}
```

**测试命令：**
```bash
curl -X POST http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"demo123"}'
```

#### 4.1.2 Verify API

**Endpoint:** `/api/v2/auth/verify`

**Method:** GET

**Request Headers:**
```
Authorization: Bearer <token>
```

**成功响应（200 OK）：**
```json
{
  "valid": true,
  "user_id": "demo",
  "username": "demo",
  "expires_at": "2026-05-17T10:39:05Z"
}
```

**失败响应（401 Unauthorized）：**
```json
{
  "valid": false,
  "error": "Token expired or invalid"
}
```

**测试命令：**
```bash
curl http://localhost:11438/api/v2/auth/verify \
  -H "Authorization: Bearer 8d85c37d-8cc2-4633-a838-5400bb88dc6f"
```

#### 4.1.3 Logout API

**Endpoint:** `/api/v2/auth/logout`

**Method:** POST

**Request Headers:**
```
Authorization: Bearer <token>
```

**成功响应（200 OK）：**
```json
{
  "success": true
}
```

**失败响应（404 Not Found）：**
```json
{
  "error": "Token not found"
}
```

**测试命令：**
```bash
curl -X POST http://localhost:11438/api/v2/auth/logout \
  -H "Authorization: Bearer 8d85c37d-8cc2-4633-a838-5400bb88dc6f"
```

### 4.2 Sync APIs

#### 4.2.1 Manual Sync API

**Endpoint:** `/api/v2/admin/sync`

**Method:** POST

**Request Headers:**
无特殊要求（公开API）

**成功响应（200 OK）：**
```json
{
  "status": "success",
  "users_synced": 3,
  "groups_synced": 1,
  "mappings_synced": 0
}
```

**部分成功响应（200 OK）：**
```json
{
  "status": "partial_success",
  "users_synced": 2,
  "users_failed": 1,
  "groups_synced": 1,
  "groups_failed": 0,
  "errors": ["Connection timeout for user xyz"]
}
```

**失败响应（500 Internal Server Error）：**
```json
{
  "status": "failed",
  "errors": ["PostgreSQL connection refused"]
}
```

**测试命令：**
```bash
curl -X POST http://localhost:11438/api/v2/admin/sync
```

#### 4.2.2 Sync Status API

**Endpoint:** `/api/v2/admin/sync/status`

**Method:** GET

**成功响应（200 OK）：**
```json
{
  "status": "ok",
  "latest_sync": {
    "sync_type": "full",
    "sync_time": 1778927765,
    "users_synced": 3,
    "users_failed": 0,
    "groups_synced": 1,
    "groups_failed": 0,
    "mappings_synced": 0,
    "status": "success"
  }
}
```

**无同步记录响应（200 OK）：**
```json
{
  "status": "ok",
  "message": "No sync logs found"
}
```

**测试命令：**
```bash
curl http://localhost:11438/api/v2/admin/sync/status
```

### 4.3 Protected APIs

#### 4.3.1 认证要求

所有以下API需要Bearer token认证：

**认证方式：**
```
Authorization: Bearer <token>
```

**认证失败响应：**
```json
{
  "error": "Unauthorized"
}
```

#### 4.3.2 Protected API列表

|Endpoint |Method |功能 |认证要求 |
|----------|--------|------|----------|
| `/api/v2/tree/:user_id` | GET | 获取文件树 | Bearer token + user_id匹配 |
| `/api/v2/tree/:user_id/node` | POST | 创建节点 | Bearer token |
| `/api/v2/tree/:user_id/node/:node_id` | PUT | 更新节点 | Bearer token |
| `/api/v2/tree/:user_id/node/:node_id` | DELETE | 删除节点 | Bearer token |
| `/api/v2/tree/:user_id` | DELETE | 删除所有节点 | Bearer token |
| `/api/v2/tree/:user_id/restore` | POST | 恢复文件树 | Bearer token |
| `/api/v2/dupes/:user_id` | GET | 查找重复文件 | Bearer token |
| `/api/v2/unregister/:file_uuid` | POST | 注销文件 | Bearer token |
| `/api/v2/upload/:user_id` | POST | 上传文件 | Bearer token |
| `/api/v2/render/:file_uuid` | GET | 渲染文件 | Bearer token |
| `/api/v2/tree/:user_id/node/:node_id/move` | PUT | 移动节点 | Bearer token |
| `/api/v2/tree/:user_id/node/:node_id/alias` | PATCH | 更新别名 | Bearer token |

#### 4.3.3 Protected API使用范例

**获取文件树：**
```bash
# Step 1: Login获取token
TOKEN=$(curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"demo123"}' | jq -r '.token')

# Step 2: 使用token访问Protected API
curl http://localhost:11438/api/v2/tree/demo \
  -H "Authorization: Bearer $TOKEN"
```

**成功响应：**
```json
{
  "mode": "tree",
  "nodes": [
    {
      "node_id": "de8b3e67-731e-45d9-9c53-e0185d89b412",
      "label": "Home",
      "node_type": "folder",
      "parent_id": null,
      "children": [],
      "aliases": {},
      "icon": "🏠",
      "color": null,
      "bg_color": null,
      "file_uuid": null,
      "sha256": null,
      "file_size": null,
      "registered_at": null
    }
  ]
}
```

**认证失败响应（401 Unauthorized）：**
```json
{
  "error": "Unauthorized"
}
```

**权限不足响应（403 Forbidden）：**
```json
{
  "error": "Access denied"
}
```

### 4.4 API错误处理

#### 4.4.1 错误响应格式

**标准错误格式：**
```json
{
  "error": "Error message",
  "details": "Additional details (optional)"
}
```

#### 4.4.2 HTTP状态码使用

|状态码 |含义 |使用场景 |
|--------|------|----------|
| 200 OK | 成功 | Login成功、API调用成功 |
| 400 Bad Request | 请求错误 | Missing Authorization header、JSON解析失败 |
| 401 Unauthorized | 未认证 | Invalid credentials、Token expired |
| 403 Forbidden | 权限不足 | user_id不匹配、权限验证失败 |
| 404 Not Found | 未找到 | Token not found、Resource不存在 |
| 500 Internal Server Error |服务器错误 | PostgreSQL连接失败、同步失败 |

---

## 5. Database Design（数据库设计）

### 5.1 auth.sqlite表结构详解

#### 5.1.1 sftpgo_users表

**创建语句（init_auth_db.sql:5-20）：**
```sql
CREATE TABLE IF NOT EXISTS sftpgo_users (
    username TEXT PRIMARY KEY,           -- 用户名（主键）
    password_hash TEXT NOT NULL,         -- bcrypt密码hash
    email TEXT,                          -- 邮箱（可选）
    status INTEGER DEFAULT 1,            -- 状态（1=active, 0=disabled）
    home_dir TEXT,                       -- 主目录路径
    permissions TEXT,                    -- 权限配置（JSON格式）
    uid INTEGER,                         -- UID
    gid INTEGER,                         -- GID
    last_login INTEGER,                  -- 最后登录时间（timestamp）
    created_at INTEGER,                  -- 创建时间（timestamp）
    updated_at INTEGER,                  -- 更新时间（timestamp）
    last_sync_at INTEGER,                -- 最后同步时间（timestamp）
    sync_status INTEGER DEFAULT 0        -- 同步状态（0=failed, 1=success）
);

CREATE INDEX IF NOT EXISTS idx_users_status ON sftpgo_users(status);
CREATE INDEX IF NOT EXISTS idx_users_sync_status ON sftpgo_users(sync_status);
```

**字段说明：**

|字段名 |类型 |必填 |默认值 |说明 |
|--------|------|------|--------|------|
| username | TEXT | ✓ | - | 用户名（唯一主键） |
| password_hash | TEXT | ✓ | - | bcrypt hash（$2b$10$...） |
| email | TEXT | - | NULL | 用户邮箱 |
| status | INTEGER | - | 1 | 1=active, 0=disabled |
| home_dir | TEXT | - | NULL | 用户主目录路径 |
| permissions | TEXT | - | NULL | 权限配置（JSON string） |
| uid | INTEGER | - | NULL | Unix UID |
| gid | INTEGER | - | NULL | Unix GID |
| last_login | INTEGER | - | NULL | 最后登录Unix timestamp |
| created_at | INTEGER | - | NULL | 创建时间Unix timestamp |
| updated_at | INTEGER | - | NULL | 更新时间Unix timestamp |
| last_sync_at | INTEGER | - | NULL | 最后同步时间 |
| sync_status | INTEGER | - | 0 | 0=同步失败, 1=同步成功 |

**索引设计：**
- `idx_users_status`：按status查询（login时过滤active用户）
- `idx_users_sync_status`：按sync_status查询（监控同步状态）

**查询范例：**
```sql
-- 查询active用户
SELECT * FROM sftpgo_users WHERE status = 1;

-- 查询同步失败的用户
SELECT username FROM sftpgo_users WHERE sync_status = 0;

-- 更新同步状态
UPDATE sftpgo_users SET last_sync_at = 1778927765, sync_status = 1 
WHERE username = 'demo';
```

#### 5.1.2 sftpgo_groups表

**创建语句（init_auth_db.sql:22-30）：**
```sql
CREATE TABLE IF NOT EXISTS sftpgo_groups (
    name TEXT PRIMARY KEY,               -- 群组名（主键）
    description TEXT,                    -- 群组描述
    created_at INTEGER,                  -- 创建时间（timestamp）
    updated_at INTEGER,                  -- 更新时间（timestamp）
    last_sync_at INTEGER                 -- 最后同步时间（timestamp）
);
```

**字段说明：**

|字段名 |类型 |必填 |说明 |
|--------|------|------|------|
| name | TEXT | ✓ | 群组名（唯一主键） |
| description | TEXT | - | 群组描述信息 |
| created_at | INTEGER | - | 创建时间Unix timestamp |
| updated_at | INTEGER | - | 更新时间Unix timestamp |
| last_sync_at | INTEGER | - | 最后同步时间 |

#### 5.1.3 users_groups_mapping表

**创建语句（init_auth_db.sql:32-42）：**
```sql
CREATE TABLE IF NOT EXISTS users_groups_mapping (
    id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增主键
    username TEXT NOT NULL,               -- 用户名
    group_name TEXT NOT NULL,             -- 群组名
    created_at INTEGER,                   -- 创建时间（timestamp）
    FOREIGN KEY (username) REFERENCES sftpgo_users(username) ON DELETE CASCADE,
    FOREIGN KEY (group_name) REFERENCES sftpgo_groups(name) ON DELETE CASCADE,
    UNIQUE(username, group_name)          -- 唯一约束（防止重复映射）
);

CREATE INDEX IF NOT EXISTS idx_mapping_username ON users_groups_mapping(username);
CREATE INDEX IF NOT EXISTS idx_mapping_group ON users_groups_mapping(group_name);
```

**字段说明：**

|字段名 |类型 |必填 |说明 |
|--------|------|------|------|
| id | INTEGER | ✓ | 自增主键 |
| username | TEXT | ✓ | 用户名（外键） |
| group_name | TEXT | ✓ | 群组名（外键） |
| created_at | INTEGER | - | 创建时间 |

**外键约束：**
- `FOREIGN KEY (username)` → 删除用户时自动删除映射
- `FOREIGN KEY (group_name)` → 删除群组时自动删除映射

**唯一约束：**
- `UNIQUE(username, group_name)` → 防止同一用户多次加入同一群组

**索引设计：**
- `idx_mapping_username`：按username查询用户所属群组
- `idx_mapping_group`：按group_name查询群组包含的用户

**查询范例：**
```sql
-- 查询用户所属群组
SELECT group_name FROM users_groups_mapping WHERE username = 'demo';

-- 查询群组包含的用户
SELECT username FROM users_groups_mapping WHERE group_name = 'admin';

-- 添加用户到群组
INSERT INTO users_groups_mapping (username, group_name, created_at)
VALUES ('demo', 'admin', 1778927765);
```

#### 5.1.4 sync_log表

**创建语句（init_auth_db.sql:44-56）：**
```sql
CREATE TABLE IF NOT EXISTS sync_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增主键
    sync_type TEXT,                       -- 同步类型（"full"）
    sync_time INTEGER,                    -- 同步时间（timestamp）
    users_synced INTEGER DEFAULT 0,       -- 成功同步的用户数
    users_failed INTEGER DEFAULT 0,       -- 失败的用户数
    groups_synced INTEGER DEFAULT 0,      -- 成功同步的群组数
    groups_failed INTEGER DEFAULT 0,      -- 失败的群组数
    mappings_synced INTEGER DEFAULT 0,    -- 成功同步的映射数
    status TEXT,                          -- 状态（success/partial_success/failed）
    error_message TEXT,                   -- 错误信息（合并后的string）
    details TEXT                          -- 详细信息（可选）
);

CREATE INDEX IF NOT EXISTS idx_sync_time ON sync_log(sync_time);
CREATE INDEX IF NOT EXISTS idx_sync_status ON sync_log(status);
```

**字段说明：**

|字段名 |类型 |默认值 |说明 |
|--------|------|--------|------|
| id | INTEGER | - | 自增主键 |
| sync_type | TEXT | - | 同步类型（目前只有"full"） |
| sync_time | INTEGER | - | 同步时间Unix timestamp |
| users_synced | INTEGER | 0 | 成功同步的用户数 |
| users_failed | INTEGER | 0 | 失败的用户数 |
| groups_synced | INTEGER | 0 | 成功同步的群组数 |
| groups_failed | INTEGER | 0 | 失败的群组数 |
| mappings_synced | INTEGER | 0 | 成功同步的映射数 |
| status | TEXT | - | success/partial_success/failed |
| error_message | TEXT | - | 错误信息（分号分隔） |
| details | TEXT | - | 详细JSON信息 |

**索引设计：**
- `idx_sync_time`：按时间查询最近同步记录
- `idx_sync_status`：按状态查询失败记录

**查询范例：**
```sql
-- 查询最近5次同步记录
SELECT * FROM sync_log ORDER BY sync_time DESC LIMIT 5;

-- 查询失败的同步记录
SELECT * FROM sync_log WHERE status = 'failed';

-- 查询最近成功的同步时间
SELECT sync_time FROM sync_log WHERE status = 'success' ORDER BY sync_time DESC LIMIT 1;
```

### 5.2 PostgreSQL源表结构

#### 5.2.1 users表（SFTPGo）

**表结构：**
```
            Column            |          Type          | Collation | Nullable |           Default            
-----------------------------+------------------------+-----------+----------+------------------------------
 id                          | integer                |           | not null | generated always as identity
 username                    | character varying(255) |           | not null | 
 status                      | integer                |           | not null | 
 expiration_date             | bigint                 |           | not null | 
 description                 | character varying(512) |           |          | 
 password                    | text                   |           |          | 
 public_keys                 | text                   |           |          | 
 home_dir                    | text                   |           | not null | 
 uid                         | bigint                 |           | not null | 
 gid                         | bigint                 |           | not null | 
 max_sessions                | integer                |           | not null | 
 quota_size                  | bigint                 |           | not null | 
 quota_files                 | integer                |           | not null | 
 permissions                 | text                   |           | not null | 
 used_quota_size             | bigint                 |           | not null | 
 used_quota_files            | integer                |           | not null | 
 last_quota_update           | bigint                 |           | not null | 
```

**同步字段映射：**

|PostgreSQL字段 |auth.sqlite字段 |同步说明 |
|----------------|-----------------|----------|
| username | username | 主键，直接映射 |
| password | password_hash | bcrypt hash，直接映射 |
| email | email | 可选字段 |
| status | status | 用户状态（1=active） |
| home_dir | home_dir | 主目录路径 |
| permissions | permissions | JSON权限配置 |
| uid | uid | Unix UID |
| gid | gid | Unix GID |
| last_login | last_login | 最后登录时间 |
| created_at | created_at | 创建时间（需查询其他表） |
| updated_at | updated_at | 更新时间（需查询其他表） |

#### 5.2.2 groups表（SFTPGo）

**查询语句：**
```sql
SELECT name, description, created_at, updated_at FROM groups;
```

**字段映射：**
- `name` → `sftpgo_groups.name`
- `description` → `sftpgo_groups.description`
- `created_at` → `sftpgo_groups.created_at`
- `updated_at` → `sftpgo_groups.updated_at`

#### 5.2.3 users_groups_mapping表（SFTPGo）

**查询语句：**
```sql
SELECT username, group_name FROM users_groups_mapping;
```

**字段映射：**
- `username` → `users_groups_mapping.username`
- `group_name` → `users_groups_mapping.group_name`

### 5.3 同步策略与实现

#### 5.3.1 INSERT OR REPLACE策略

**目的：**
- 更新现有用户数据
- 插入新用户数据
- 避免重复插入

**实现（sync.rs:130）：**
```sql
INSERT OR REPLACE INTO sftpgo_users 
(username, password_hash, email, status, home_dir, permissions, 
 uid, gid, last_login, created_at, updated_at, last_sync_at, sync_status)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
```

**行为：**
- 如果`username`已存在 → REPLACE（更新）
- 如果`username`不存在 → INSERT（新增）

#### 5.3.2 时间戳记录策略

**last_sync_at字段：**
```rust
let now = Utc::now().timestamp();  // Unix timestamp
conn.execute("... last_sync_at = ?12", params![now])?;
```

**作用：**
- 记录每次同步时间
- 监控同步频率
- 识别长时间未同步的用户

#### 5.3.3 同步状态记录

**sync_status字段：**
```rust
// 成功同步
conn.execute("... sync_status = 1", ...)?;

// 失败同步
conn.execute("UPDATE sftpgo_users SET sync_status = 0 WHERE username = ?", ...)?;
```

**状态值：**
- `0`：同步失败
- `1`：同步成功

**监控查询：**
```sql
-- 查询同步失败的用户
SELECT username, last_sync_at FROM sftpgo_users WHERE sync_status = 0;

-- 查询长时间未同步的用户（超过7天）
SELECT username, last_sync_at FROM sftpgo_users 
WHERE last_sync_at < (strftime('%s', 'now') - 7*24*3600);
```

---

## 6. Test Results（测试记录）

### 6.1 测试环境

**硬件配置：**
- 设备：Mac mini M4
- 处理器：Apple M4 ARM64
- 内存：16GB
- 存储：256GB SSD
- 操作系统：macOS Darwin

**软件配置：**
- Rust：1.92+
- PostgreSQL：14.x（127.0.0.1:5432）
- SFTPGo：2.x
- SQLite：3.x

**网络配置：**
- PostgreSQL连接：127.0.0.1:5432
- SFTPGo API：http://localhost:8080
- MarkBase API：http://localhost:11438

### 6.2 测试时间

**测试日期：** 2026-05-16  
**测试时间：** 18:39  
**测试时长：** 约30分钟  
**测试人员：** Warren Lo

### 6.3 测试用户

**SFTPGo PostgreSQL用户：**

|用户名 |状态 |密码hash |测试密码 |
|--------|------|----------|----------|
| warren | 1 (active) | $2a$10$TpGOufSlx... | demo123（测试） |
| momentry | 1 (active) | $2a$10$Yn/43aBY... | demo123（测试） |
| demo | 1 (active) | $2a$10$wCQC0wGRe... | demo123（测试） |

**密码配置：**
为测试目的，所有用户密码统一设置为`demo123`：
```bash
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c "
UPDATE users SET password = '\$2b\$10\$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6' 
WHERE username IN ('warren', 'momentry', 'demo');
"
```

**bcrypt hash：**
```
$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6
```

### 6.4 测试项目与结果

#### 6.4.1 PostgreSQL连接测试

**测试命令：**
```bash
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c "SELECT username, status FROM users;"
```

**测试结果：**
```
 username | status 
----------+--------
 warren   |      1
 momentry |      1
 demo     |      1
(3 rows)
```

**状态：** ✅ 成功  
**说明：** PostgreSQL连接正常，可查询users表

#### 6.4.2 手动同步API测试

**测试命令：**
```bash
curl -X POST http://localhost:11438/api/v2/admin/sync
```

**测试结果：**
```json
{
  "groups_synced": 1,
  "mappings_synced": 0,
  "status": "success",
  "users_synced": 3
}
```

**状态：** ✅ 成功  
**说明：** 
- 同步了3个用户（warren, momentry, demo）
- 同步了1个群组
- mappings_synced=0（无用户-群组映射）

#### 6.4.3 auth.sqlite数据验证

**验证命令：**
```bash
sqlite3 data/auth.sqlite "SELECT username, status FROM sftpgo_users;"
sqlite3 data/auth.sqlite "SELECT name FROM sftpgo_groups;"
sqlite3 data/auth.sqlite "SELECT * FROM sync_log ORDER BY sync_time DESC LIMIT 1;"
```

**验证结果：**
```
momentry|1
warren|1
demo|1

demo

3|full|1778927765|3|0|1|0|0|success||
```

**状态：** ✅ 成功  
**说明：**
- auth.sqlite中用户数据正确
- 群组数据正确
- sync_log记录正常

#### 6.4.4 Login测试（demo用户）

**测试命令：**
```bash
curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"demo123"}'
```

**测试结果：**
```json
{
  "token": "8d85c37d-8cc2-4633-a838-5400bb88dc6f",
  "expires_at": "2026-05-17T10:39:05Z",
  "user_id": "demo",
  "groups": [],
  "permissions": "{\"/*\": [\"*\"]}"
}
```

**状态：** ✅ 成功  
**说明：**
- Token生成成功（UUID格式）
- expires_at为24小时后
- groups为空数组（该用户无群组）
- permissions正确返回（JSON格式）

#### 6.4.5 Login测试（momentry用户）

**测试命令：**
```bash
curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"momentry","password":"demo123"}'
```

**测试结果：**
```json
{
  "token": "3e77c0e9-b09c-4013-ad09-2ffccc0df005",
  "expires_at": "2026-05-17T10:39:18Z",
  "user_id": "momentry",
  "groups": [],
  "permissions": "{\"/*\": [\"*\"]}"
}
```

**状态：** ✅ 成功  
**说明：** bcrypt密码验证成功，Token生成正常

#### 6.4.6 Login测试（warren用户）

**测试命令：**
```bash
curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"warren","password":"demo123"}'
```

**测试结果：**
```json
{
  "token": "b4583824-156c-463c-9f35-cdbc79402ccb",
  "expires_at": "2026-05-17T10:39:30Z",
  "user_id": "warren",
  "groups": [],
  "permissions": "{\"/*\": [\"*\"]}"
}
```

**状态：** ✅ 成功  
**说明：** 所有用户Login测试成功

#### 6.4.7 Token Verification测试

**测试命令：**
```bash
TOKEN="8d85c37d-8cc2-4633-a838-5400bb88dc6f"
curl http://localhost:11438/api/v2/auth/verify \
  -H "Authorization: Bearer $TOKEN"
```

**测试结果：**
```json
{
  "expires_at": "2026-05-17T10:39:05Z",
  "user_id": "demo",
  "username": "demo",
  "valid": true
}
```

**状态：** ✅ 成功  
**说明：**
- Token验证成功（valid=true）
- 返回user_id、username、expires_at
- 时间格式正确（RFC3339）

#### 6.4.8 Protected API访问测试

**测试命令：**
```bash
TOKEN="8d85c37d-8cc2-4633-a838-5400bb88dc6f"
curl http://localhost:11438/api/v2/tree/demo \
  -H "Authorization: Bearer $TOKEN"
```

**测试结果：**
```json
{
  "mode": "tree",
  "nodes": [
    {
      "aliases": {},
      "bg_color": null,
      "children": [],
      "color": null,
      "file_size": null,
      "file_uuid": null,
      "icon": "🏠",
      "label": "Home",
      "node_id": "de8b3e67-731e-45d9-9c53-e0185d89b412",
      "node_type": "folder",
      "parent_id": null,
      "registered_at": null,
      "sha256": null
    }
  ]
}
```

**状态：** ✅ 成功  
**说明：**
- Bearer token认证成功
- 返回文件树数据
- user_id匹配（demo用户访问demo的tree）

#### 6.4.9 Sync Status API测试

**测试命令：**
```bash
curl http://localhost:11438/api/v2/admin/sync/status
```

**测试结果：**
```json
{
  "latest_sync": {
    "groups_failed": 0,
    "groups_synced": 1,
    "mappings_synced": 0,
    "status": "success",
    "sync_time": 1778927765,
    "sync_type": "full",
    "users_failed": 0,
    "users_synced": 3
  },
  "status": "ok"
}
```

**状态：** ✅ 成功  
**说明：**
- 返回最新同步记录
- sync_time为Unix timestamp
- 所有字段正确

### 6.5 密码Hash验证测试

#### 6.5.1 Python bcrypt测试

**测试代码：**
```python
import bcrypt

# 测试hash
hash_from_db = '$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6'
password = 'demo123'

# 验证
result = bcrypt.checkpw(password.encode(), hash_from_db.encode())
print(f'Match: {result}')  # Output: True
```

**测试结果：** ✅ Match: True  
**说明：** bcrypt验证逻辑正确

#### 6.5.2 错误密码测试

**测试代码：**
```python
import bcrypt
hash_from_db = '$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6'
wrong_password = 'wrongpass'

result = bcrypt.checkpw(wrong_password.encode(), hash_from_db.encode())
print(f'Match: {result}')  # Output: False
```

**测试结果：** ✅ Match: False  
**说明：** 错误密码验证失败，符合预期

#### 6.5.3 Login失败测试

**测试命令：**
```bash
curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"wrongpass"}'
```

**测试结果：**
```json
{
  "error": "Invalid credentials"
}
```

**状态：** ✅ 成功（错误密码正确拒绝）  
**说明：** 错误密码返回401 Unauthorized

### 6.6 测试总结

#### 6.6.1 测试覆盖率

|测试类别 |测试项目数 |成功数 |失败数 |成功率 |
|----------|------------|--------|--------|--------|
| PostgreSQL连接 | 1 | 1 | 0 | 100% |
| 同步功能 | 3 | 3 | 0 | 100% |
| Login认证 | 4 | 4 | 0 | 100% |
| Token验证 | 2 | 2 | 0 | 100% |
| Protected API | 1 | 1 | 0 | 100% |
| 密码验证 | 3 | 3 | 0 | 100% |
| **总计** | **14** | **14** | **0** | **100%** |

#### 6.6.2 关键测试结果

**核心功能验证：**
- ✅ PostgreSQL同步成功（3 users, 1 group）
- ✅ bcrypt密码验证成功（demo123）
- ✅ Token生成与验证成功
- ✅ Protected API认证成功

**数据一致性验证：**
- ✅ PostgreSQL → auth.sqlite数据一致
- ✅ 密码hash同步正确
- ✅ sync_log记录完整

**错误处理验证：**
- ✅ 错误密码拒绝登录
- ✅ Token验证失败返回正确错误
- ✅ 认证失败返回401 Unauthorized

#### 6.6.3 测试命令汇总

**完整测试流程：**

```bash
# 1. 启动服务器
cargo run --release -- display

# 2. 手动同步
curl -X POST http://localhost:11438/api/v2/admin/sync

# 3. Login测试
curl -s http://localhost:11438/api/v2/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"demo123"}'

# 4. Token验证
TOKEN="<from_login_response>"
curl http://localhost:11438/api/v2/auth/verify \
  -H "Authorization: Bearer $TOKEN"

# 5. Protected API测试
curl http://localhost:11438/api/v2/tree/demo \
  -H "Authorization: Bearer $TOKEN"

# 6. 同步状态查询
curl http://localhost:11438/api/v2/admin/sync/status

# 7. Logout
curl -X POST http://localhost:11438/api/v2/auth/logout \
  -H "Authorization: Bearer $TOKEN"
```

---

## 7. Future Enhancements（未来扩展）

### 7.1 JWT支持

#### 7.1.1 当前状态

**Cargo.toml依赖：**
```toml
jsonwebtoken = "9.3"  # 已添加但未启用
```

**现状：**
- UUID token当前使用（简单可靠）
- JWT依赖已准备，可快速切换

#### 7.1.2 JWT设计方案

**JWT Token结构：**
```json
{
  "sub": "demo",                    // Subject (user_id)
  "iat": 1778927765,                // Issued at (timestamp)
  "exp": 1779011765,                // Expiration (24h later)
  "groups": [],                     // User groups
  "permissions": "{\"/*\": [\"*\"]}" // Permissions
}
```

**密钥管理：**
```rust
// 环境变量配置
let secret_key = std::env::var("JWT_SECRET")
    .unwrap_or_else(|_| "default_secret_key".to_string());

// Token生成
let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(secret_key.as_ref()))?;

// Token验证
let decoded = decode(&token, &DecodingKey::from_secret(secret_key.as_ref()), &Validation::default())?;
```

**优势：**
- 自包含（无需查询Session HashMap）
- 可携带claims（groups, permissions）
- 支持refresh token机制

**切换策略：**
- 保留UUID token作为fallback
- 逐步迁移到JWT
- 提供配置选项（选择token类型）

#### 7.1.3 实现路线图

**Phase 1：准备阶段**
- 添加JWT生成/验证函数
- 添加JWT配置（secret_key, expiry）
- 单元测试JWT功能

**Phase 2：并行阶段**
- 同时支持UUID和JWT token
- Login API返回JWT（可选）
- Verify API支持JWT验证

**Phase 3：迁移阶段**
- 默认使用JWT token
- 移除UUID token支持
- 清理Session HashMap代码

### 7.2 RBAC权限控制

#### 7.2.1 当前权限模型

**现状：**
- permissions字段：JSON string（`"{\"/*\": [\"*\"]}"`）
- 简单的路径权限（`/*`表示所有路径）
- 粗粒度权限（`["*"]`表示所有操作）

#### 7.2.2 RBAC设计方案

**细粒度权限模型：**
```json
{
  "paths": {
    "/api/v2/files/*": ["read", "write", "delete"],
    "/api/v2/tree/*": ["read", "write"],
    "/api/v2/admin/*": []
  },
  "groups": ["admin", "user"],
  "roles": ["file_manager", "viewer"]
}
```

**权限检查流程：**
```rust
pub fn check_permission(session: &Session, path: &str, action: &str) -> bool {
    let permissions: Value = serde_json::from_str(&session.permissions).ok()?;
    
    // Check path permissions
    if let Some(path_perms) = permissions.get("paths").and_then(|p| p.get(path)) {
        if path_perms.as_array().map(|a| a.contains(action)).unwrap_or(false) {
            return true;
        }
    }
    
    // Check group permissions
    for group in &session.groups {
        if check_group_permission(group, path, action) {
            return true;
        }
    }
    
    false
}
```

**数据库扩展：**
```sql
-- 新增permissions_detail表
CREATE TABLE permissions_detail (
    id INTEGER PRIMARY KEY,
    user_id TEXT NOT NULL,
    path TEXT NOT NULL,
    actions TEXT NOT NULL,        -- JSON array: ["read", "write", "delete"]
    created_at INTEGER,
    FOREIGN KEY (user_id) REFERENCES sftpgo_users(username)
);

-- 新增groups_permissions表
CREATE TABLE groups_permissions (
    id INTEGER PRIMARY KEY,
    group_name TEXT NOT NULL,
    path TEXT NOT NULL,
    actions TEXT NOT NULL,
    FOREIGN KEY (group_name) REFERENCES sftpgo_groups(name)
);
```

#### 7.2.3 实现路线图

**Phase 1：权限解析**
- 解析permissions JSON
- 添加permission检查函数
- Protected API添加permission检查

**Phase 2：群组权限**
- 同步群组权限从SFTPGo
- 添加groups_permissions表
- 群组权限继承机制

**Phase 3：动态权限**
- API动态更新权限
- 权限变更实时生效
- 权限审计日志

### 7.3 WebSocket认证

#### 7.3.1 当前状态

**现状：**
- WebSocket endpoint存在（`/ws`）
- 无认证机制
- 实时通信无保护

#### 7.3.2 WebSocket认证设计方案

**握手阶段认证：**
```
Client →WebSocket连接请求（ws://localhost:11438/ws?token=<JWT>)
    ↓
Server →验证token（从query parameter或header）
    ↓
    → 如果token有效：接受连接
    → 如果token无效：拒绝连接（401）
    ↓
WebSocket连接建立（携带Session信息）
```

**实现代码：**
```rust
async fn ws_handler(
    ws: WebSocketUpgrade,
    Query(params): Query<WsParams>,  // token from query parameter
) -> impl IntoResponse {
    // Verify token
    match state.auth.verify_token(&params.token) {
        Some(session) => {
            // Accept WebSocket connection
            ws.on_upgrade(|socket| handle_ws(socket, session))
        }
        None => {
            // Reject connection
            (StatusCode::UNAUTHORIZED, "Invalid token")
        }
    }
}

async fn handle_ws(socket: WebSocket, session: Session) {
    // WebSocket handler with session context
    let (mut sender, mut receiver) = socket.split();
    
    while let Some(msg) = receiver.next().await {
        if let Ok(msg) = msg {
            // Process message with session context
            if msg.is_text() {
                // Handle text message
            }
        }
    }
}
```

**Session持久化：**
```rust
// Store session in WebSocket state
struct WsState {
    session: Session,
    // Other state...
}

// Use session in WebSocket handler
async fn handle_ws(socket: WebSocket, state: WsState) {
    // Access session.user_id, session.permissions...
}
```

#### 7.3.3 实现路线图

**Phase 1：握手认证**
- WebSocket upgrade添加token验证
- Query parameter传递token
- Reject无效token连接

**Phase 2：Session传递**
- WebSocket state携带Session
- Message处理使用Session context
- 动态权限检查

**Phase 3：安全增强**
- Token refresh机制（WebSocket长连接）
- Connection timeout处理
- Message rate limiting

### 7.4 持久化Session

#### 7.4.1 当前状态

**现状：**
- In-memory HashMap（`Arc<Mutex<HashMap<String, Session>>>`）
- 服务重启后Session丢失
- 用户需重新登录

#### 7.4.2 SQLite持久化方案

**数据库设计：**
```sql
CREATE TABLE sessions (
    token TEXT PRIMARY KEY,
    user_id TEXT NOT NULL,
    username TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    expires_at INTEGER NOT NULL,
    groups TEXT,                    -- JSON array
    permissions TEXT,
    last_activity INTEGER,          -- Last API access time
    FOREIGN KEY (user_id) REFERENCES sftpgo_users(username)
);

CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
```

**实现代码：**
```rust
pub fn save_session(&self, session: &Session) -> Result<()> {
    let conn = self.open()?;
    conn.execute(
        "INSERT INTO sessions (token, user_id, username, created_at, expires_at, groups, permissions)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
        params![
            session.token,
            session.user_id,
            session.username,
            session.created_at,
            session.expires_at,
            serde_json::to_string(&session.groups)?,
            session.permissions,
        ]
    )?;
    Ok(())
}

pub fn get_session(&self, token: &str) -> Result<Option<Session>> {
    let conn = self.open()?;
    let result = conn.query_row(
        "SELECT token, user_id, username, created_at, expires_at, groups, permissions
         FROM sessions WHERE token = ?1 AND expires_at > ?2",
        params![token, Utc::now().timestamp()],
        |row| Ok(Session {
            token: row.get(0)?,
            user_id: row.get(1)?,
            username: row.get(2)?,
            created_at: row.get(3)?,
            expires_at: row.get(4)?,
            groups: serde_json::from_str(&row.get::<_, String>(5)?)?,
            permissions: row.get(6)?,
        })
    );
    
    match result {
        Ok(session) => Ok(Some(session)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(e) => Err(e.into()),
    }
}
```

**TTL自动清理：**
```rust
// 定期清理过期Session（tokio spawn）
tokio::spawn(async move {
    let mut interval = tokio::time::interval(Duration::from_secs(3600));  // 每小时
    loop {
        interval.tick().await;
        
        let conn = Connection::open("data/auth.sqlite")?;
        conn.execute(
            "DELETE FROM sessions WHERE expires_at < ?1",
            params![Utc::now().timestamp()]
        )?;
        
        log::info!("Cleaned expired sessions");
    }
});
```

#### 7.4.3 Redis持久化方案（可选）

**优势：**
- 高性能（内存数据库）
- TTL自动清理
- 分布式Session支持

**实现方案：**
```rust
use redis::Commands;

pub fn save_session_redis(&self, session: &Session) -> Result<()> {
    let conn = redis::Client::open("redis://127.0.0.1/")?;
    let mut con = conn.get_connection()?;
    
    // Set with TTL (24 hours)
    con.set_ex(
        &format!("session:{}", session.token),
        serde_json::to_string(session)?,
        86400  // 24 hours in seconds
    )?;
    
    Ok(())
}

pub fn get_session_redis(&self, token: &str) -> Result<Option<Session>> {
    let conn = redis::Client::open("redis://127.0.0.1/")?;
    let mut con = conn.get_connection()?;
    
    let key = format!("session:{}", token);
    let result: Option<String> = con.get(&key)?;
    
    match result {
        Some(json) => Ok(Some(serde_json::from_str(&json)?)),
        None => Ok(None),
    }
}
```

#### 7.4.4 实现路线图

**Phase 1：SQLite持久化**
- 添加sessions表
- save_session/get_session函数
- Login时保存到SQLite

**Phase 2：TTL清理**
- 定期清理过期Session
- 监控Session数量
- 性能优化

**Phase 3：Redis迁移（可选）**
- Redis依赖添加
- Redis实现save/get
- 配置选择（SQLite/Redis）

### 7.5 多因素认证（MFA）

#### 7.5.1 TOTP支持

**设计方案：**
- 用户启用MFA后生成secret key
- Login时要求输入TOTP code
- 验证TOTP code + password

**实现代码：**
```rust
use otp::{TOTP, Algorithm};

pub fn generate_totp_secret(&self, username: &str) -> Result<String> {
    let secret = TOTP::generate_secret();
    
    // Save secret to database
    let conn = self.open()?;
    conn.execute(
        "UPDATE sftpgo_users SET totp_secret = ?1 WHERE username = ?2",
        params![secret, username]
    )?;
    
    Ok(secret)
}

pub fn verify_totp(&self, username: &str, code: &str) -> Result<bool> {
    let conn = self.open()?;
    let secret: String = conn.query_row(
        "SELECT totp_secret FROM sftpgo_users WHERE username = ?1",
        params![username],
        |row| row.get(0)
    )?;
    
    let totp = TOTP::new(Algorithm::SHA1, 6, 30, 0, secret)?;
    Ok(totp.verify(code, Utc::now().timestamp()))
}
```

**Login流程扩展：**
```rust
pub fn login_with_mfa(&self, username: &str, password: &str, totp_code: Option<&str>) -> Option<LoginResponse> {
    // Step 1: Password verification
    let user = self.get_user(username)?;
    
    if !verify(password, &user.password_hash).unwrap_or(false) {
        return None;
    }
    
    // Step 2: Check if MFA enabled
    if user.totp_secret.is_some() {
        // Require TOTP code
        let code = totp_code?;
        if !self.verify_totp(username, code)? {
            return None;
        }
    }
    
    // Step 3: Generate token and create session
    // ...
}
```

#### 7.5.2 OAuth2集成（可选）

**支持的OAuth2 Provider：**
- Google
- GitHub
- Custom OAuth2 server

**实现方案：**
```rust
use oauth2::{AuthorizationCode, TokenResponse};

pub async fn oauth2_login(&self, code: &str) -> Option<LoginResponse> {
    // Exchange code for token
    let token = self.oauth2_client.exchange_code(AuthorizationCode::new(code.to_string()))?;
    
    // Get user info from OAuth2 provider
    let user_info = self.get_user_info(&token.access_token())?;
    
    // Create or get user from database
    let user = self.create_or_get_oauth2_user(&user_info)?;
    
    // Generate token and create session
    // ...
}
```

---

## 8. Design Decisions（设计决策）

### 8.1 为什么选择SQLite而非直接连接PostgreSQL？

#### 8.1.1 决策背景

**需求分析：**
- SFTPGo使用PostgreSQL存储用户数据
- MarkBase需要认证用户
- 需要高可用性和性能

#### 8.1.2 SQLite优势

**1. 性能优势**
```
PostgreSQL查询延迟：
- 网络延迟：1-5ms（本地）
- 查询处理：2-10ms
- 总延迟：3-15ms

SQLite查询延迟：
- 无网络延迟：0ms
- 查询处理：0.1-1ms
- 总延迟：0.1-1ms

性能提升：10-100倍
```

**2. 可用性优势**
```
PostgreSQL故障场景：
- PostgreSQL进程崩溃 →认证失败
- 网络连接中断 →认证失败
- 服务器重启 →认证失败

SQLite fallback：
- 使用cached auth.sqlite →认证成功
- 最大延迟：1小时（hourly sync）
- 服务不中断
```

**3. 数据安全优势**
```
密码hash暴露：
- PostgreSQL（网络传输） →可能被拦截
- SQLite（本地存储） →无网络传输

安全性：SQLite更高
```

**4. 架构一致性优势**
```
MarkBase架构：
- Per-user SQLite（data/users/<user_id>.sqlite）
- auth.sqlite（统一认证）

架构统一：SQLite everywhere
```

#### 8.1.3 SQLite劣势

**1. 数据延迟**
- 最大延迟：1小时（hourly sync）
- 用户管理操作后需手动同步或等待下次同步

**2. 存储冗余**
- PostgreSQL + auth.sqlite双重存储
- 额外磁盘空间占用（但很小，每个用户记录约100 bytes）

**3. 同步复杂性**
- 需要实现同步逻辑（sync.rs, pg_client.rs）
- 需要处理同步失败、部分失败等场景

#### 8.1.4 决策结论

**选择SQLite的原因：**
- **可用性 > 数据实时性**：认证系统优先保证可用性
- **性能至关重要**：认证是高频操作（每次API调用）
- **架构一致性**：与MarkBase整体架构一致

**适用场景：**
- 用户管理操作频率：低（新增/删除用户）
- 认证操作频率：高（每次API调用）
- 可接受延迟：1小时（用户管理）

**不适用的场景：**
- 需要实时用户数据（如实时权限变更） →应使用PostgreSQL直接查询
- 用户管理操作频率高（如频繁新增删除用户） →应使用PostgreSQL直接查询

### 8.2 为什么选择UUID Token而非JWT？

#### 8.2.1 决策背景

**需求分析：**
- Token-based authentication
- Session管理
- 易于实现

#### 8.2.2 UUID Token优势

**1. 简单可靠**
```rust
// UUID token生成
let token = Uuid::new_v4().to_string();  // 一行代码

// Session管理
sessions.insert(token.clone(), session);  // HashMap操作

// 验证
let session = sessions.get(token)?;       // HashMap查询
```

**2. 无需密钥管理**
- UUID生成无需密钥
- Session存储在内存
- 无密钥泄露风险

**3. 易于Session管理**
- HashMap天然支持insert/remove/get
- 易于实现logout（remove操作）
- 易于清理过期Session（遍历HashMap）

**4. 易于调试**
- Token是随机字符串：`8d85c37d-8cc2-4633-a838-5400bb88dc6f`
- 无复杂结构（JWT有header.payload.signature）
- 易于手动测试

#### 8.2.3 UUID Token劣势

**1. 无自包含信息**
- Token本身不携带信息（需查询Session）
- 每次验证需查询HashMap（性能损失）

**2. Session持久化复杂**
- In-memory HashMap重启后丢失
- 需额外实现持久化（SQLite/Redis）

**3. 不支持refresh token**
- Token过期需重新登录
- 无法实现refresh token机制

#### 8.2.4 JWT优势

**1. 自包含信息**
```json
{
  "sub": "demo",
  "iat": 1778927765,
  "exp": 1779011765,
  "groups": [],
  "permissions": "{\"/*\": [\"*\"]}"
}
```
- 无需查询Session（解码JWT即可获取信息）
- 性能更高（无HashMap查询）

**2. 支持refresh token**
- Access token（短期，如1小时）
- Refresh token（长期，如7天）
- Refresh token换取新access token

**3. 标准化**
- RFC 7519标准
- 广泛支持（各语言库）
- 互操作性高

#### 8.2.5 JWT劣势

**1. 密钥管理复杂**
- 需要secret key（不能泄露）
- 密钥轮换机制
- 密钥存储安全

**2. 实现复杂**
```rust
// JWT生成
let header = Header::new(Algorithm::HS256);
let claims = Claims { sub: "demo", ... };
let token = encode(&header, &claims, &EncodingKey::from_secret(secret.as_ref()))?;

// JWT验证
let decoded = decode(&token, &DecodingKey::from_secret(secret.as_ref()), &Validation::new(Algorithm::HS256))?;
```

**3. Token无法撤销**
- JWT签发后无法主动撤销（除非过期）
- 需额外实现黑名单机制

#### 8.2.6 决策结论

**选择UUID Token的原因：**
- **初期优先简单可靠**：快速实现认证系统
- **无需密钥管理**：避免密钥泄露风险
- **易于调试**：初期开发阶段友好

**未来计划：**
- **逐步迁移到JWT**：依赖已准备（jsonwebtoken）
- **JWT提供更高性能**：无Session查询
- **JWT支持refresh token**：更好的用户体验

**迁移策略：**
- Phase 1：同时支持UUID和JWT（并行）
- Phase 2：默认JWT，UUID作为fallback
- Phase 3：完全迁移到JWT

### 8.3 为什么选择hourly同步而非real-time？

#### 8.3.1 决策背景

**需求分析：**
- SFTPGo用户数据变更频率
- 同步成本
- 可接受延迟

#### 8.3.2 SFTPGo用户管理操作频率分析

**典型场景：**
- 新增用户：每周1-2次（低频）
- 删除用户：每月1次（低频）
- 更新密码：每季度1次（低频）
- 更新权限：每月1-2次（低频）

**结论：** 用户管理操作频率极低（每周/月级别）

#### 8.3.3 同步成本分析

**PostgreSQL连接成本：**
- 网络连接：1-5ms
- 查询处理：2-10ms
- SQLite写入：0.1-1ms
- 总成本：3-16ms（每次同步）

**每小时同步：**
- 同步次数：24次/天
- 总耗时：24 * 15ms = 360ms/天
- 平均负载：极低

**每分钟同步：**
- 同步次数：1440次/天
- 总耗时：1440 * 15ms = 21.6s/天
- 平均负载：仍低，但无意义（用户管理操作频率远低于每分钟）

#### 8.3.4 Real-time同步成本

**WebSocket监听PostgreSQL变更：**
- 需PostgreSQL触发器（notify）
- 需WebSocket监听进程
- 需实时处理逻辑

**成本：**
- PostgreSQL触发器实现复杂
- WebSocket监听进程维护
- 实时处理可能出错

#### 8.3.5 决策结论

**选择hourly同步的原因：**
- **用户管理操作频率低**：hourly同步足够
- **同步成本低**：每小时一次，几乎无负载
- **实现简单**：tokio interval + full_sync()
- **最大延迟可接受**：1小时延迟，用户管理场景可接受

**不适用的场景：**
- 需要实时权限变更（如管理员实时禁用用户） →应使用real-time同步或手动API同步
- 用户管理操作频率高（如频繁变更） →应缩短同步间隔（如每10分钟）

**手动同步补充：**
- 提供`/api/v2/admin/sync` API
- 用户管理操作后可手动触发同步
- 确保关键操作后数据立即更新

### 8.4 为什么选择bcrypt而非其他hash算法？

#### 8.4.1 决策背景

**需求分析：**
- 密码hash存储
- 安全性要求
- 与SFTPGo一致性

#### 8.4.2 密码hash算法对比

**常见算法：**
- bcrypt
- argon2
- scrypt
- PBKDF2

**对比分析：**

|算法 |安全性 |性能 |内置salt |SFTPGo支持 |
|------|--------|------|----------|------------|
| bcrypt | 高 | 中 | ✓ | ✓（已使用） |
| argon2 | 最高 | 低 | ✓ | -（需配置） |
| scrypt | 高 | 低 | ✓ | -（需配置） |
| PBKDF2 | 中 | 高 | -（需手动salt） | ✓（可配置） |

#### 8.4.3 bcrypt优势

**1. 内置salt**
```
bcrypt hash格式：
$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6

分解：
- $2b$：算法标识
- 10$：cost factor（10轮）
- ha5wU...：salt（前22字符） + hash（后31字符）
```
- 无需手动生成salt
- 无需单独存储salt
- hash自带salt信息

**2. 可配置cost**
```rust
// Cost factor调整（增加安全性）
let hash = hash("password", DEFAULT_COST)?;        // cost=10（默认）
let hash = hash("password", bcrypt::cost(12))?;    // cost=12（更高）
```
- cost越高，hash计算越慢（更安全）
- 可根据硬件性能调整

**3. SFTPGo一致性**
- SFTPGo使用bcrypt（默认）
- 密码hash格式一致
- 无需转换hash格式

**4. Rust库支持**
```rust
use bcrypt::{hash, verify, DEFAULT_COST};

// Hash password
let hashed = hash("password", DEFAULT_COST)?;

// Verify password
let valid = verify("password", &hashed)?;
```
- bcrypt crate成熟稳定
- API简单易用

#### 8.4.4 bcrypt劣势

**1. 性能（相对argon2）**
- bcrypt：cost=10 →~100ms
- argon2：默认配置 →~200ms

**结论：** bcrypt性能略好，但argon2更安全

**2. 抗GPU攻击（相对argon2）**
- bcrypt：GPU攻击仍可行（但较慢）
- argon2：内存密集，GPU攻击困难

**结论：** argon2抗GPU攻击更强

#### 8.4.5 决策结论

**选择bcrypt的原因：**
- **SFTPGo一致性**：避免hash格式转换
- **内置salt**：简化实现
- **安全性足够**：cost=10已足够安全
- **Rust库支持好**：bcrypt crate成熟

**未来可选升级：**
- 如果安全性要求极高 →迁移到argon2
- 如果SFTPGo支持argon2 →可切换
- 当前bcrypt足够满足需求

---

## 9. Appendix（附录）

### 9.1 完整配置范例

#### 9.1.1 PostgreSQL连接配置

**环境变量配置（推荐）：**
```bash
# ~/.bashrc or ~/.zshrc
export PG_HOST=127.0.0.1
export PG_PORT=5432
export PG_USER=sftpgo
export PG_PASSWORD=sftpgo_pass_2026
export PG_DATABASE=sftpgo

# 应用配置
source ~/.bashrc
```

**默认配置（代码中）：**
```rust
// src/pg_client.rs:14-21
PgClient {
    host: "127.0.0.1".to_string(),
    port: 5432,
    user: "sftpgo".to_string(),
    password: "sftpgo_pass_2026".to_string(),
    database: "sftpgo".to_string(),
}
```

#### 9.1.2 测试用户密码配置

**统一密码设置（测试用）：**
```bash
# PostgreSQL命令
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c "
UPDATE users SET password = '\$2b\$10\$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6' 
WHERE username IN ('warren', 'momentry', 'demo');
"

# 密码：demo123
# bcrypt hash：$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6
```

**Python生成bcrypt hash：**
```python
import bcrypt

# Generate bcrypt hash for password "demo123"
password = "demo123"
salt = bcrypt.gensalt(rounds=10)  # cost=10
hash = bcrypt.hashpw(password.encode(), salt)
print(hash.decode())

# Output: $2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6
```

**恢复原始密码：**
```bash
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c "
UPDATE users SET password = '\$2a\$10\$TpGOufSlxSsQhF4X3qdVJO9YMLVg53MoeLw/GDe7q.TNNJsS9vzFO' WHERE username = 'warren';
UPDATE users SET password = '\$2a\$10\$Yn/43aBYZW32oCxBK/IYLO4T76HsbOPg4TItQWSNPe4RyNzpm8yGC' WHERE username = 'momentry';
UPDATE users SET password = '\$2a\$10\$wCQC0wGRe./riwaYjWZX7eqI/GgdYEnjXoX9mY1DBund7hVwi66l6' WHERE username = 'demo';
"
```

#### 9.1.3 auth.sqlite初始化

**首次启动自动初始化：**
```rust
// src/sync.rs:114-118
pub fn new(path: &str) -> Result<Self> {
    if !Path::new(path).exists() {
        Self::init_db(path)?;  // 自动创建表结构
    }
    Ok(Self { path: path.to_string() })
}
```

**手动初始化（可选）：**
```bash
sqlite3 data/auth.sqlite < data/init_auth_db.sql
```

**验证表结构：**
```bash
sqlite3 data/auth.sqlite ".schema sftpgo_users"
sqlite3 data/auth.sqlite ".schema sftpgo_groups"
sqlite3 data/auth.sqlite ".schema users_groups_mapping"
sqlite3 data/auth.sqlite ".schema sync_log"
```

### 9.2 错误处理范例

#### 9.2.1 Login失败响应

**错误密码：**
```json
{
  "error": "Invalid credentials"
}
```

**用户不存在：**
```json
{
  "error": "Invalid credentials"
}
```

**用户被禁用（status=0）：**
```json
{
  "error": "Invalid credentials"
}
```

**PostgreSQL连接失败（fallback生效）：**
```json
{
  "token": "...",  // 使用默认用户或cached用户登录
  "expires_at": "...",
  "user_id": "demo"
}
```

#### 9.2.2 Token验证失败响应

**Token过期：**
```json
{
  "valid": false,
  "error": "Token expired or invalid"
}
```

**Token不存在：**
```json
{
  "valid": false,
  "error": "Token expired or invalid"
}
```

**Token格式错误：**
```json
{
  "valid": false,
  "error": "Token expired or invalid"
}
```

#### 9.2.3 Protected API认证失败响应

**缺少Authorization header：**
```json
{
  "error": "Missing Authorization header"
}
```

**Token无效：**
```json
{
  "error": "Unauthorized"
}
```

**user_id不匹配：**
```json
{
  "error": "Access denied"
}
```

#### 9.2.4 同步失败响应

**PostgreSQL连接失败：**
```json
{
  "status": "failed",
  "errors": ["PostgreSQL connection refused"]
}
```

**部分同步失败：**
```json
{
  "status": "partial_success",
  "users_synced": 2,
  "users_failed": 1,
  "groups_synced": 1,
  "groups_failed": 0,
  "errors": ["Connection timeout for user xyz"]
}
```

**完全失败：**
```json
{
  "status": "failed",
  "errors": ["Failed to connect to PostgreSQL", "auth.sqlite write failed"]
}
```

### 9.3 代码结构总览

#### 9.3.1 文件组织

```
markbase/
├── src/
│   ├── auth.rs (225行) - 认证核心
│   │   ├── Session结构
│   │   ├── LoginRequest/Response结构
│   │   ├── AuthState结构
│   │   ├── login_with_sync()  - 主要登录逻辑
│   │   ├── verify_token()     - Token验证
│   │   ├── logout()           - 登出
│   │   └── parse_auth_header() - Header解析
│   │
│   ├── sync.rs (273行) - 同步逻辑
│   │   ├── PgUser/PgGroup/PgUserGroupMapping结构
│   │   ├── SyncResult结构
│   │   ├── AuthDb结构
│   │   ├── save_user()        - 保存用户到SQLite
│   │   ├── save_group()       - 保存群组
│   │   ├── save_mapping()     - 保存映射
│   │   ├── save_sync_log()    - 保存同步日志
│   │   ├── get_user()         - 查询用户
│   │   ├── get_user_groups()  - 查询用户群组
│   │   └── init_db()          - 初始化表结构
│   │
│   ├── pg_client.rs (247行) - PostgreSQL客户端
│   │   ├── PgClient结构      - 连接配置
│   │   ├── SftpGoSync结构    - 同步器
│   │   ├── fetch_users()     - 查询PostgreSQL用户
│   │   ├── fetch_groups()    - 查询群组
│   │   ├── fetch_mappings()  - 查询映射
│   │   ├── full_sync()       - 完整同步流程
│   │   ├── new()             - 默认配置
│   │   ├── from_env()        - 环境变量配置
│   │   └── connection_string() - 连接字符串生成
│   │
│   └── server.rs (~200行相关部分)
│       ├── manual_sync_handler() - 手动同步API
│       ├── sync_status_handler() - 同步状态API
│       ├── login_handler()       - Login API
│       ├── verify_handler()      - Verify API
│       ├── logout_handler()      - Logout API
│       ├── startup sync task     - 启动同步（tokio::spawn）
│       └── hourly sync task      - 每小时同步（tokio::spawn）
│
├── data/
│   ├── auth.sqlite           - 认证数据库
│   ├── init_auth_db.sql      - 初始化SQL
│   └── users/                - Per-user数据库（文件树）
│
├── docs/
│   ├── AUTH_DESIGN.md        - 本文档
│   ├── api.yaml              - API文档
│   └── filetree.md           - 文件树文档
│
└── Cargo.toml                - 依赖配置
    ├── bcrypt = "0.15"
    ├── uuid = "1.0"
    ├── tokio-postgres = "0.7"
    └── jsonwebtoken = "9.3"（未启用）
```

#### 9.3.2 依赖关系图

```
┌─────────────┐
│  server.rs  │
│             │
│ - API handlers│
│ - sync tasks │
└──────┬──────┘
       │
       ├──────────> ┌─────────────┐
       │            │  auth.rs    │
       │            │             │
       │            │ - login     │
       │            │ - verify    │
       │            │ - logout    │
       │            └──────┬──────┘
       │                   │
       │                   ├──────────> ┌─────────────┐
       │                   │            │  sync.rs    │
       │                   │            │             │
       │                   │            │ - AuthDb    │
       │                   │            │ - get_user  │
       │                   │            └──────┬──────┘
       │                   │                   │
       │                   └───────────────────┘
       │                                       │
       └───────────────────────────────────────┤
                                               │
                                               └──────────> ┌─────────────┐
                                                            │pg_client.rs│
                                                            │             │
                                                            │ - PgClient  │
                                                            │ - fetch_*   │
                                                            │ - full_sync │
                                                            └─────────────┘
```

#### 9.3.3 核心数据流

```
Client Request
    │
    ▼
server.rs (API handler)
    │
    ├─ login_handler ──> auth.rs (login_with_sync)
    │                      │
    │                      ├─> sync.rs (get_user)
    │                      │     │
    │                      │     ▼
    │                      │   auth.sqlite (query)
    │                      │
    │                      ├─> bcrypt (verify)
    │                      │
    │                      ├─> uuid (generate token)
    │                      │
    │                      ▼
    │                    Session (HashMap insert)
    │
    ├─ verify_handler ──> auth.rs (verify_token)
    │                      │
    │                      ▼
    │                    Session (HashMap get)
    │
    ├─ manual_sync_handler ──> pg_client.rs (full_sync)
    │                            │
    │                            ├─> PostgreSQL (fetch users)
    │                            │
    │                            ├─> sync.rs (save_user)
    │                            │     │
    │                            │     ▼
    │                            │   auth.sqlite (insert)
    │                            │
    │                            ▼
    │                          sync_log (save)
    │
    ▼
Protected APIs (tree, files)
    │
    ├─> auth.rs (verify_token)
    │
    ▼
Process request (with session context)
```

### 9.4 关键代码引用

#### 9.4.1 Login核心逻辑（auth.rs:116-173）

```rust
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;
            }
        };
        
        // Check user status
        if user.status != 1 {
            log::warn!("User {} is disabled", username);
            return None;
        }
        
        // Verify password
        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();
            
            // Generate UUID token
            let token = Uuid::new_v4().to_string();
            let now = Utc::now();
            let expires_at = now + Duration::hours(24);
            
            // Create Session
            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(),
            };
            
            // Insert to HashMap
            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)  // Fallback
    }
}
```

#### 9.4.2 Full Sync逻辑（pg_client.rs:180-220）

```rust
pub async fn full_sync(&self) -> Result<SyncResult> {
    let mut result = SyncResult {
        sync_type: "full".to_string(),
        sync_time: Utc::now().timestamp(),
        status: "success".to_string(),
        ..Default::default()
    };
    
    // Sync users
    let users = self.pg_client.fetch_users().await?;
    for user in users {
        match self.auth_db.save_user(&user) {
            Ok(_) => result.users_synced += 1,
            Err(e) => {
                result.users_failed += 1;
                result.errors.push(format!("User {}: {}", user.username, e));
            }
        }
    }
    
    // Sync groups
    let groups = self.pg_client.fetch_groups().await?;
    for group in groups {
        match self.auth_db.save_group(&group) {
            Ok(_) => result.groups_synced += 1,
            Err(e) => {
                result.groups_failed += 1;
                result.errors.push(format!("Group {}: {}", group.name, e));
            }
        }
    }
    
    // Sync mappings
    let mappings = self.pg_client.fetch_mappings().await?;
    for mapping in mappings {
        match self.auth_db.save_mapping(&mapping) {
            Ok(_) => result.mappings_synced += 1,
            Err(e) => {
                result.mappings_failed += 1;
                result.errors.push(format!("Mapping {}: {}", mapping.username, e));
            }
        }
    }
    
    // Update status
    result.update_status();
    
    // Save sync log
    self.auth_db.save_sync_log(&result)?;
    
    Ok(result)
}
```

#### 9.4.3 Startup Sync Task（server.rs:62-80）

```rust
// Server startup
let auth_db_path = "data/auth.sqlite".to_string();
let auth_state = AuthState::with_sync(&auth_db_path);

// Startup sync task
tokio::spawn(async move {
    let syncer = crate::pg_client::SftpGoSync::new(&auth_db_path);
    match syncer {
        Ok(syncer) => {
            match syncer.full_sync().await {
                Ok(result) => {
                    log::info!(
                        "Initial sync completed: users={}, groups={}, mappings={}, status={}",
                        result.users_synced,
                        result.groups_synced,
                        result.mappings_synced,
                        result.status
                    );
                }
                Err(e) => {
                    log::error!("Initial sync failed: {}", e);
                }
            }
        }
        Err(e) => {
            log::error!("Failed to create syncer: {}", e);
        }
    }
});

// Hourly sync task
let syncer_clone = crate::pg_client::SftpGoSync::new(&auth_db_path).unwrap();
tokio::spawn(async move {
    let mut interval = tokio::time::interval(std::time::Duration::from_secs(3600));
    loop {
        interval.tick().await;
        match syncer_clone.full_sync().await {
            Ok(result) => {
                log::info!(
                    "Hourly sync: users={}, groups={}, status={}",
                    result.users_synced,
                    result.groups_synced,
                    result.status
                );
            }
            Err(e) => {
                log::error!("Hourly sync failed: {}", e);
            }
        }
    }
});
```

### 9.5 性能指标

#### 9.5.1 Login性能

**测试环境：**
- Mac mini M4
- Rust release build
- bcrypt cost=10

**性能数据：**
```
操作：Login（bcrypt verify + UUID生成 + HashMap insert）
延迟：~100ms
分解：
- auth.sqlite query: 0.1-1ms
- bcrypt verify: ~100ms（主要耗时）
- UUID generate: <0.1ms
- HashMap insert: <0.1ms
```

**优化建议：**
- bcrypt cost调整：cost=10→12（增加安全性，降低性能）
- 缓存验证结果：相同密码重复验证（但不安全）

#### 9.5.2 Token验证性能

**性能数据：**
```
操作：Token verification（HashMap get + time check）
延迟：0.1-0.5ms
分解：
- HashMap get: <0.1ms
- Time parsing: 0.1-0.3ms
- Comparison: <0.1ms
```

**优势：**
- In-memory HashMap查询极快
- 无磁盘IO
- 无网络延迟

#### 9.5.3 同步性能

**性能数据：**
```
操作：Full sync（3 users + 1 group）
总耗时：~200ms
分解：
- PostgreSQL connect: 5-10ms
- fetch_users query: 2-5ms
- fetch_groups query: 1-3ms
- fetch_mappings query: 1-2ms
- SQLite inserts: 0.1-1ms per row
- sync_log save: 0.1-1ms
```

**每小时同步负载：**
```
每小时同步：1次
每天同步：24次
总耗时：24 * 200ms = 4.8s/天
平均负载：极低（<0.01% CPU time）
```

#### 9.5.4 API吞吐量

**理论吞吐量（单线程）：**
```
Login API:
- 每次耗时：100ms
- 吞吐量：10 requests/second（单线程）
- 多线程：可提升至100+ req/s（取决于bcrypt性能）

Token Verify API:
- 每次耗时：0.5ms
- 吨吐量：2000 requests/second（单线程）
- 多线程：可提升至10000+ req/s

Protected APIs:
- Token验证：0.5ms
- 业务逻辑：1-50ms（取决于具体API）
- 吞吐量：20-1000 req/s（取决于业务逻辑复杂度）
```

---

**文档完成**

**版本：** 1.0  
**行数：** ~1200行  
**最后更新：** 2026-05-16  

**下一步建议：**
1. 根据实际使用情况更新测试数据
2. 未来功能扩展时更新对应章节
3. 定期审核设计决策是否符合实际需求