- 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>
200 lines
5.6 KiB
Markdown
200 lines
5.6 KiB
Markdown
# 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)
|