feat: implement audit trail module
- Backend (FastAPI): - AuditLog and AuditAlert models with Alembic migration - AuditService with SHA-256 checksum for log integrity - AuditMiddleware for request metadata extraction (IP, user_agent) - Integrated audit logging into Task, Project, Blocker APIs - Query API with filtering, pagination, CSV export - Integrity verification endpoint - Sensitive operation alerts with acknowledgement - Frontend (React + Vite): - Admin AuditPage with filters and export - ResourceHistory component for change tracking - Audit service for API calls - Testing: - 15 tests covering service and API endpoints - OpenSpec: - add-audit-trail change archived 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
147
openspec/changes/archive/2025-12-29-add-audit-trail/design.md
Normal file
147
openspec/changes/archive/2025-12-29-add-audit-trail/design.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Design: add-audit-trail
|
||||
|
||||
## Architecture Decision
|
||||
|
||||
### Approach: Application-layer Middleware
|
||||
|
||||
選擇應用層中間件而非資料庫觸發器:
|
||||
|
||||
| 方案 | 優點 | 缺點 |
|
||||
|-----|------|-----|
|
||||
| **Middleware (選擇)** | 可取得完整 context (user, IP)、跨資料庫相容 | 需要在每個 API 加入 |
|
||||
| DB Trigger | 自動捕捉所有變更 | 無法取得 user context、MySQL 觸發器效能差 |
|
||||
|
||||
### Implementation Strategy
|
||||
|
||||
```
|
||||
Request → FastAPI Middleware → Extract metadata (user, IP, user_agent)
|
||||
↓
|
||||
API Handler → Execute operation
|
||||
↓
|
||||
AuditService.log() → Async write to pjctrl_audit_logs
|
||||
↓
|
||||
(if sensitive) → NotificationService → Alert admins
|
||||
```
|
||||
|
||||
## Data Model
|
||||
|
||||
### pjctrl_audit_logs
|
||||
|
||||
```sql
|
||||
CREATE TABLE pjctrl_audit_logs (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
event_type VARCHAR(50) NOT NULL,
|
||||
resource_type VARCHAR(50) NOT NULL,
|
||||
resource_id VARCHAR(36),
|
||||
user_id VARCHAR(36),
|
||||
action ENUM('create', 'update', 'delete', 'restore', 'login', 'logout') NOT NULL,
|
||||
changes JSON,
|
||||
metadata JSON,
|
||||
sensitivity_level ENUM('low', 'medium', 'high', 'critical') DEFAULT 'low',
|
||||
checksum VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_audit_user (user_id, created_at),
|
||||
INDEX idx_audit_resource (resource_type, resource_id, created_at),
|
||||
INDEX idx_audit_time (created_at),
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES pjctrl_users(id) ON DELETE SET NULL
|
||||
);
|
||||
```
|
||||
|
||||
### pjctrl_audit_alerts
|
||||
|
||||
```sql
|
||||
CREATE TABLE pjctrl_audit_alerts (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
audit_log_id VARCHAR(36) NOT NULL,
|
||||
alert_type VARCHAR(50) NOT NULL,
|
||||
recipients JSON NOT NULL,
|
||||
message TEXT,
|
||||
is_acknowledged BOOLEAN DEFAULT FALSE,
|
||||
acknowledged_by VARCHAR(36),
|
||||
acknowledged_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (audit_log_id) REFERENCES pjctrl_audit_logs(id),
|
||||
FOREIGN KEY (acknowledged_by) REFERENCES pjctrl_users(id)
|
||||
);
|
||||
```
|
||||
|
||||
## Checksum Calculation
|
||||
|
||||
確保日誌不可竄改:
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
def calculate_checksum(log: AuditLog) -> str:
|
||||
content = f"{log.event_type}|{log.resource_id}|{log.user_id}|{json.dumps(log.changes, sort_keys=True)}|{log.created_at.isoformat()}"
|
||||
return hashlib.sha256(content.encode()).hexdigest()
|
||||
```
|
||||
|
||||
## Sensitivity Levels & Event Types
|
||||
|
||||
| Event Type | Sensitivity | Alert |
|
||||
|-----------|-------------|-------|
|
||||
| task.create, task.update, task.assign | low | No |
|
||||
| task.delete, task.blocker | medium | No |
|
||||
| project.create, project.update | medium | No |
|
||||
| project.delete | high | Yes |
|
||||
| user.permission_change | critical | Yes |
|
||||
| user.login (異常) | high | Yes |
|
||||
|
||||
## API Design
|
||||
|
||||
### Query Audit Logs
|
||||
|
||||
```
|
||||
GET /api/audit-logs
|
||||
Query params:
|
||||
- start_date: datetime
|
||||
- end_date: datetime
|
||||
- user_id: UUID (optional)
|
||||
- resource_type: string (optional)
|
||||
- resource_id: UUID (optional)
|
||||
- sensitivity_level: string (optional)
|
||||
- limit: int (default 50, max 100)
|
||||
- offset: int
|
||||
```
|
||||
|
||||
### Resource History
|
||||
|
||||
```
|
||||
GET /api/audit-logs/resource/{resource_type}/{resource_id}
|
||||
Returns: Change history for specific resource
|
||||
```
|
||||
|
||||
### Export
|
||||
|
||||
```
|
||||
GET /api/audit-logs/export
|
||||
Query params: same as query + format=csv
|
||||
Returns: CSV file download
|
||||
```
|
||||
|
||||
### Integrity Check
|
||||
|
||||
```
|
||||
POST /api/audit-logs/verify-integrity
|
||||
Body: { start_date, end_date }
|
||||
Returns: { total_checked, valid_count, invalid_records: [] }
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
1. **Task API**: Log create/update/delete/assign
|
||||
2. **Project API**: Log create/update/delete
|
||||
3. **User API**: Log permission changes
|
||||
4. **Auth Middleware**: Log login/logout
|
||||
5. **Blocker API**: Log blocker events
|
||||
|
||||
## Alert Thresholds
|
||||
|
||||
- **Bulk delete**: > 5 deletes within 5 minutes
|
||||
- **Off-hours login**: Outside 06:00-22:00 local time
|
||||
- **Permission escalation**: Any admin role assignment
|
||||
Reference in New Issue
Block a user