Security Validation (enhance-security-validation): - JWT secret validation with entropy checking and pattern detection - CSRF protection middleware with token generation/validation - Frontend CSRF token auto-injection for DELETE/PUT/PATCH requests - MIME type validation with magic bytes detection for file uploads Error Resilience (add-error-resilience): - React ErrorBoundary component with fallback UI and retry functionality - ErrorBoundaryWithI18n wrapper for internationalization support - Page-level and section-level error boundaries in App.tsx Query Performance (optimize-query-performance): - Query monitoring utility with threshold warnings - N+1 query fixes using joinedload/selectinload - Optimized project members, tasks, and subtasks endpoints Bug Fixes: - WebSocket session management (P0): Return primitives instead of ORM objects - LIKE query injection (P1): Escape special characters in search queries Tests: 543 backend tests, 56 frontend tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.5 KiB
9.5 KiB
Document Management
Purpose
文件管理系統,提供檔案附件、版本控制、加密存儲與浮水印功能。
Requirements
Requirement: File Attachments
系統 SHALL 支援任務層級的檔案附件,儲存於地端 NAS。
Scenario: 上傳附件
- GIVEN 使用者擁有任務的編輯權限
- WHEN 使用者上傳檔案至任務
- THEN 系統將檔案儲存至 NAS
- AND 建立附件記錄關聯至該任務
Scenario: 下載附件
- GIVEN 使用者擁有任務的存取權限
- WHEN 使用者下載附件
- THEN 系統驗證權限後提供檔案下載
- AND 記錄下載操作日誌
Scenario: 刪除附件
- GIVEN 使用者擁有任務的編輯權限
- WHEN 使用者刪除附件
- THEN 系統標記附件為已刪除(軟刪除)
- AND 保留檔案供稽核追溯
Requirement: Version Control
系統 SHALL 支援檔案版本控制,追蹤所有版本變更。
Scenario: 上傳新版本
- GIVEN 任務已有同名附件
- WHEN 使用者上傳同名檔案
- THEN 系統建立新版本而非覆蓋
- AND 版本號自動遞增
Scenario: 查看版本歷史
- GIVEN 附件有多個版本
- WHEN 使用者查看版本歷史
- THEN 顯示所有版本清單
- AND 包含上傳者、上傳時間、檔案大小
Scenario: 回復舊版本
- GIVEN 使用者需要使用舊版本
- WHEN 使用者選擇回復特定版本
- THEN 系統將該版本設為當前版本
- AND 記錄回復操作
Requirement: File Encryption
系統 SHALL 對半導體敏感圖檔進行 AES-256 加密存儲。
Scenario: 自動加密判斷
- GIVEN 使用者上傳檔案至任務
- WHEN 該任務所屬專案的 security_level 為 "confidential"
- THEN 系統自動使用 AES-256-GCM 加密檔案
- AND 設定 is_encrypted = true 及 encryption_key_id
Scenario: 加密存儲
- GIVEN 專案設定為機密等級
- WHEN 使用者上傳檔案
- THEN 系統使用 AES-256 加密後存儲
- AND 加密金鑰安全管理
Scenario: 解密讀取
- GIVEN 使用者請求下載加密檔案
- WHEN 系統驗證權限通過
- THEN 系統解密檔案後提供下載
- AND 解密過程透明,使用者無感
Scenario: 串流處理大檔案
- GIVEN 使用者上傳或下載大型加密檔案
- WHEN 系統處理加密或解密
- THEN 使用串流方式處理避免記憶體溢出
- AND 效能損耗在可接受範圍內
Scenario: 金鑰輪換
- GIVEN 安全政策要求金鑰輪換
- WHEN 管理員執行金鑰輪換
- THEN 系統建立新金鑰並標記為 active
- AND 舊金鑰保留用於解密既有檔案
- AND 新上傳檔案使用新金鑰加密
Scenario: Master Key 管理
- GIVEN 系統需要加解密檔案
- WHEN 系統取得加密金鑰
- THEN 使用 Master Key 解密金鑰後使用
- AND Master Key 從環境變數讀取,不存於資料庫
Scenario: 加密操作稽核
- GIVEN 發生加密相關操作
- WHEN 操作完成
- THEN 系統記錄操作類型、金鑰 ID、檔案 ID、操作者、時間
- AND 日誌不可竄改
Scenario: 金鑰管理權限
- GIVEN 使用者嘗試管理加密金鑰
- WHEN 使用者不是系統管理員
- THEN 系統拒絕操作並返回 403 錯誤
Requirement: Dynamic Watermarking
系統 SHALL 在下載時自動為檔案加上使用者浮水印。
Scenario: 圖片浮水印
- GIVEN 使用者下載圖片類型附件 (PNG, JPG, JPEG)
- WHEN 系統處理下載請求
- THEN 自動加上包含使用者姓名、工號、下載時間的浮水印
- AND 浮水印位置不影響主要內容
Scenario: PDF 浮水印
- GIVEN 使用者下載 PDF 類型附件
- WHEN 系統處理下載請求
- THEN 每頁加上浮水印
- AND 浮水印透明度適中
Scenario: 浮水印內容
- GIVEN 需要加上浮水印
- WHEN 系統生成浮水印
- THEN 浮水印包含:
- 使用者姓名
- 使用者工號
- 下載日期時間
- 機密等級標示(如適用)
Scenario: 不支援的檔案類型
- GIVEN 使用者下載非圖片/PDF 類型附件
- WHEN 系統處理下載請求
- THEN 直接提供原始檔案下載
- AND 不嘗試加上浮水印
Scenario: 浮水印服務異常處理
- GIVEN 浮水印生成過程發生錯誤
- WHEN 系統無法完成浮水印處理
- THEN 記錄錯誤日誌
- AND 提供原始檔案下載(降級處理)
Requirement: Audit Trail
系統 SHALL 記錄所有文件操作供稽核追溯。
Scenario: 操作日誌記錄
- GIVEN 使用者對附件執行任何操作
- WHEN 操作完成
- THEN 系統記錄操作類型、操作者、時間、IP 位址
- AND 日誌不可竄改
Scenario: 稽核查詢
- GIVEN 稽核人員需要查詢文件操作歷史
- WHEN 稽核人員執行查詢
- THEN 顯示完整操作歷史
- AND 支援依時間、操作者、檔案篩選
Requirement: Storage Path Validation
The system SHALL validate file storage configuration on startup to ensure reliability.
Scenario: Valid NAS storage path
- WHEN application starts with valid UPLOAD_DIR configuration
- THEN system verifies path exists and is writable
- THEN system logs confirmation of storage configuration
Scenario: Invalid storage path
- WHEN application starts with invalid or inaccessible UPLOAD_DIR
- THEN system logs error with specific issue (not found, not writable)
- THEN system falls back to local storage with warning
Scenario: Storage health check
- WHEN health check endpoint is called
- THEN response includes storage availability status
- THEN response includes available disk space if accessible
Requirement: Notification Delivery Reliability
The system SHALL ensure notification delivery even during temporary Redis failures.
Scenario: Redis temporarily unavailable
- WHEN Redis publish fails due to connection error
- THEN system queues message in local memory
- WHEN Redis connection recovers
- THEN system retries queued messages
Scenario: Queue overflow prevention
- WHEN local message queue exceeds maximum size
- THEN oldest messages are dropped
- THEN system logs warning about dropped messages
Requirement: Task Deletion Safety
The system SHALL warn users when deleting tasks with unresolved blockers.
Scenario: Delete task with active blockers
- WHEN user attempts to delete task with unresolved blockers
- THEN system returns warning with blocker count
- THEN user must confirm or use force_delete flag
Scenario: Force delete with blockers
- WHEN user force deletes task with blockers
- THEN system auto-resolves all blockers with "task deleted" reason
- THEN system proceeds with task deletion
Requirement: File MIME Type Validation
The system SHALL validate file content type using magic bytes detection.
Scenario: Valid file with matching extension
- WHEN a user uploads a file
- AND the detected MIME type matches the file extension
- THEN the upload SHALL be accepted
Scenario: Spoofed file extension rejected
- WHEN a user uploads a file with extension
.jpg - AND the actual content is detected as
application/x-executable - THEN the upload SHALL be rejected with error "File type mismatch"
Scenario: Unsupported MIME type rejected
- WHEN a user uploads a file with an unsupported MIME type
- THEN the upload SHALL be rejected with error "Unsupported file type"
Scenario: MIME validation bypass for trusted sources
- WHEN a file is uploaded from a trusted internal source
- AND the system is configured to allow bypass
- THEN MIME validation MAY be skipped
Data Model
pjctrl_attachments
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── filename: VARCHAR(500)
├── original_filename: VARCHAR(500)
├── file_path: VARCHAR(1000) (NAS path)
├── file_size: BIGINT
├── mime_type: VARCHAR(100)
├── version: INT DEFAULT 1
├── is_encrypted: BOOLEAN DEFAULT false
├── encryption_key_id: UUID (FK -> encryption_keys)
├── checksum: VARCHAR(64) (SHA-256)
├── uploaded_by: UUID (FK -> users)
├── is_deleted: BOOLEAN DEFAULT false
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_attachment_versions
├── id: UUID (PK)
├── attachment_id: UUID (FK -> attachments)
├── version: INT
├── file_path: VARCHAR(1000)
├── file_size: BIGINT
├── checksum: VARCHAR(64)
├── uploaded_by: UUID (FK -> users)
├── is_current: BOOLEAN DEFAULT false
└── created_at: TIMESTAMP
pjctrl_encryption_keys
├── id: UUID (PK)
├── key_hash: VARCHAR(64) (for verification)
├── algorithm: VARCHAR(20) DEFAULT 'AES-256'
├── is_active: BOOLEAN DEFAULT true
├── created_at: TIMESTAMP
└── rotated_at: TIMESTAMP
pjctrl_document_audit_logs
├── id: UUID (PK)
├── attachment_id: UUID (FK -> attachments)
├── user_id: UUID (FK -> users)
├── action: ENUM('upload', 'download', 'delete', 'restore', 'version_create', 'version_restore')
├── ip_address: VARCHAR(45)
├── user_agent: VARCHAR(500)
├── details: JSON
└── created_at: TIMESTAMP
Technical Notes
- 加密金鑰存儲於獨立的 Key Management Service (KMS)
- 浮水印使用 Pillow (圖片) 和 PyPDF2 (PDF) 處理
- 大檔案使用串流處理避免記憶體溢出
- NAS 存儲路徑結構:
/{project_id}/{task_id}/{attachment_id}/{version}/