feat: implement user authentication module

- Backend (FastAPI):
  - External API authentication (pj-auth-api.vercel.app)
  - JWT token validation with Redis session storage
  - RBAC with department isolation
  - User, Role, Department models with pjctrl_ prefix
  - Alembic migrations with project-specific version table
  - Complete test coverage (13 tests)

- Frontend (React + Vite):
  - AuthContext for state management
  - Login page with error handling
  - Protected route component
  - Dashboard with user info display

- OpenSpec:
  - 7 capability specs defined
  - add-user-auth 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:
beabigegg
2025-12-28 23:41:37 +08:00
commit 1fda7da2c2
77 changed files with 6562 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
# Design: add-user-auth
## Context
這是專案的第一個模組,需要建立認證與授權的基礎架構。系統使用外部認證 API 進行身份驗證,本地不儲存密碼。認證成功後由本地系統核發 JWT Token 並管理 Session。
### Stakeholders
- 所有系統使用者工程師、主管、PMO
- 系統管理員 (ymirliu@panjit.com.tw)
- 外部認證 API 維護者
### Constraints
- 必須使用外部 API 認證,不可本地繞過
- 資料表必須使用 `pjctrl_` 前綴
- 需支援部門級資料隔離
## Goals / Non-Goals
### Goals
- 實現安全的外部 API 認證整合
- 建立 RBAC 權限控制框架
- 支援部門級資料隔離
- 預設系統管理員帳號
### Non-Goals
- 本地密碼儲存與驗證
- 多因素認證 (MFA) - 由外部 API 處理
- OAuth2 第三方登入
- 使用者自助註冊
## Decisions
### Decision 1: 認證流程架構
**選擇**: Frontend-to-External-API 模式
```
┌─────────┐ ┌─────────────┐ ┌──────────────────────────┐
│ User │────▶│ Frontend │────▶│ pj-auth-api.vercel.app │
└─────────┘ └─────────────┘ └──────────────────────────┘
│ │
│◀────── Auth Token ──────│
┌─────────────┐
│ Backend │
│ - 驗證 Token│
│ - 核發 JWT │
│ - 建立 Session
└─────────────┘
```
**原因**:
- 減少後端介入認證流程的攻擊面
- 外部 API 已處理密碼安全性
- 後端只負責驗證與授權
**替代方案考量**:
- Backend Proxy 模式:增加延遲與複雜度,無明顯優勢
### Decision 2: JWT Token 策略
**選擇**: 短期 Access Token + Redis Session
```python
JWT_PAYLOAD = {
"sub": "user_id",
"email": "user@example.com",
"role": "engineer",
"department_id": "uuid",
"is_system_admin": false,
"exp": "15 minutes from now",
"iat": "now"
}
```
**原因**:
- 短期 Token 減少被盜用風險
- Redis Session 可即時撤銷
- 權限資訊嵌入 Token 減少 DB 查詢
### Decision 3: 權限模型
**選擇**: RBAC + 部門隔離混合模式
```python
# 權限檢查順序
def check_permission(user, resource, action):
# 1. 系統管理員 - 全權通過
if user.is_system_admin:
return True
# 2. 角色權限檢查
if not has_role_permission(user.role, action):
return False
# 3. 部門隔離檢查
if not is_same_department_or_allowed(user, resource):
return False
return True
```
**原因**:
- 系統管理員需無限制存取(用於問題排查)
- RBAC 提供功能層面控制
- 部門隔離提供資料層面控制
### Decision 4: 資料表設計
```sql
-- 使用 UUID 而非 auto-increment
-- 便於分散式環境與資料合併
CREATE TABLE pjctrl_roles (
id CHAR(36) PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
permissions JSON NOT NULL,
is_system_role BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE pjctrl_departments (
id CHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
parent_id CHAR(36) REFERENCES pjctrl_departments(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE pjctrl_users (
id CHAR(36) PRIMARY KEY,
email VARCHAR(200) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
department_id CHAR(36) REFERENCES pjctrl_departments(id),
role_id CHAR(36) REFERENCES pjctrl_roles(id),
skills JSON,
capacity DECIMAL(5,2) DEFAULT 40.00,
is_active BOOLEAN DEFAULT TRUE,
is_system_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
## Risks / Trade-offs
| 風險 | 影響 | 緩解措施 |
|------|------|---------|
| 外部 API 不可用 | 所有使用者無法登入 | 顯示友善錯誤訊息、記錄事件、監控告警 |
| JWT Token 洩漏 | 短期內帳號被盜用 | 15 分鐘過期、Redis 可即時撤銷 |
| 部門隔離邏輯錯誤 | 資料外洩 | 完整測試案例、程式碼審查 |
## Migration Plan
1. 執行資料庫 migration 建立資料表
2. 執行 seed data 建立預設角色與管理員
3. 部署後端認證模組
4. 部署前端登入頁面
5. 驗證管理員可登入
**Rollback**:
- 資料庫:執行 down migration
- 程式碼:回復至前一版本
## Environment Configuration
```bash
# Database
MYSQL_HOST=mysql.theaken.com
MYSQL_PORT=33306
MYSQL_USER=A060
MYSQL_PASSWORD=<見密碼管理>
MYSQL_DATABASE=db_A060
# Redis (待設定)
REDIS_HOST=localhost
REDIS_PORT=6379
# JWT
JWT_SECRET_KEY=<生成隨機金鑰>
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=15
# External Auth API
AUTH_API_URL=https://pj-auth-api.vercel.app
```
## Admin Credentials
- **Email**: `ymirliu@panjit.com.tw`
- **Password**: 由外部認證 API 管理(已設定於外部系統)
## Open Questions
1. ~~外部認證 API 的回應格式?~~ (需確認)
2. ~~Session 過期時間?~~ 建議 15 分鐘,可透過環境變數調整
3. ~~是否需要 Refresh Token 機制?~~ 暫不需要,視實際使用情況再評估
4. ~~MySQL 連線資訊?~~ 已提供
5. ~~Admin 帳號密碼?~~ 已提供 (ymirliu@panjit.com.tw)