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:
199
openspec/changes/archive/2025-12-28-add-user-auth/design.md
Normal file
199
openspec/changes/archive/2025-12-28-add-user-auth/design.md
Normal 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)
|
||||
@@ -0,0 +1,38 @@
|
||||
# Change: Add User Authentication & Authorization
|
||||
|
||||
## Why
|
||||
|
||||
系統需要使用者認證與授權機制作為所有功能的基礎。沒有認證系統,無法識別使用者身份、無法實施權限控制、無法追蹤操作記錄。這是整個專案管理系統的第一個必要模組。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **新增** 外部 API 認證整合 (https://pj-auth-api.vercel.app)
|
||||
- **新增** JWT Token 驗證與 Session 管理
|
||||
- **新增** 使用者資料表 (`pjctrl_users`)
|
||||
- **新增** 部門資料表 (`pjctrl_departments`)
|
||||
- **新增** 角色資料表 (`pjctrl_roles`)
|
||||
- **新增** 預設系統管理員帳號 (`ymirliu@panjit.com.tw`)
|
||||
- **新增** RBAC 權限檢查中間件
|
||||
- **新增** 部門級資料隔離機制
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs**: `user-auth` (新增)
|
||||
- **Affected code**:
|
||||
- Backend: `app/api/auth/`, `app/models/user.py`, `app/core/security.py`
|
||||
- Frontend: `src/contexts/AuthContext.tsx`, `src/pages/Login.tsx`
|
||||
- Database: Migration for `pjctrl_users`, `pjctrl_departments`, `pjctrl_roles`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- 外部認證 API: https://pj-auth-api.vercel.app (必須可用)
|
||||
- MySQL 資料庫連線
|
||||
- Redis (用於 Session 儲存)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. 使用者可透過外部 API 完成登入
|
||||
2. JWT Token 正確核發與驗證
|
||||
3. 系統管理員帳號可登入並存取所有資源
|
||||
4. 非授權使用者無法存取受保護的 API
|
||||
5. 部門隔離正確運作
|
||||
@@ -0,0 +1,86 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: API-Based Authentication
|
||||
系統 SHALL 限定使用外部認證 API (https://pj-auth-api.vercel.app) 進行登入認證,不支援其他認證方式。
|
||||
|
||||
#### Scenario: API 登入成功
|
||||
- **GIVEN** 使用者擁有有效的企業帳號
|
||||
- **WHEN** 使用者透過前端提交憑證
|
||||
- **THEN** 系統呼叫 https://pj-auth-api.vercel.app 驗證憑證
|
||||
- **AND** 驗證成功後建立 session 並回傳 JWT token
|
||||
|
||||
#### Scenario: API 登入失敗
|
||||
- **GIVEN** 使用者提供無效的憑證
|
||||
- **WHEN** 使用者嘗試登入
|
||||
- **THEN** 認證 API 回傳錯誤
|
||||
- **AND** 系統拒絕登入並顯示錯誤訊息
|
||||
- **AND** 記錄失敗的登入嘗試
|
||||
|
||||
#### Scenario: 認證 API 無法連線
|
||||
- **GIVEN** 認證 API 服務無法連線
|
||||
- **WHEN** 使用者嘗試登入
|
||||
- **THEN** 系統顯示服務暫時無法使用的訊息
|
||||
- **AND** 記錄連線失敗事件
|
||||
|
||||
### Requirement: System Administrator
|
||||
系統 SHALL 預設一個系統管理員帳號,擁有所有權限。系統管理員帳號必須存在於外部認證系統,且登入流程仍需透過外部認證 API;不允許本地繞過認證。
|
||||
|
||||
#### Scenario: 預設管理員帳號
|
||||
- **GIVEN** 系統初始化完成
|
||||
- **WHEN** 系統啟動
|
||||
- **THEN** 存在預設管理員帳號 ymirliu@panjit.com.tw
|
||||
- **AND** 該帳號擁有 super_admin 角色
|
||||
- **AND** 該帳號不可被刪除或降級
|
||||
|
||||
#### Scenario: 管理員登入流程
|
||||
- **GIVEN** 管理員帳號 ymirliu@panjit.com.tw 需要登入
|
||||
- **WHEN** 管理員提交憑證
|
||||
- **THEN** 系統仍需呼叫 https://pj-auth-api.vercel.app 驗證
|
||||
- **AND** 不存在任何本地繞過認證的機制
|
||||
- **AND** 驗證成功後才授予 super_admin 權限
|
||||
|
||||
#### Scenario: 管理員全域權限
|
||||
- **GIVEN** 管理員帳號 ymirliu@panjit.com.tw 已通過 API 認證並登入
|
||||
- **WHEN** 管理員存取任何資源
|
||||
- **THEN** 系統允許存取,無視部門隔離限制
|
||||
|
||||
### Requirement: Role-Based Access Control
|
||||
系統 SHALL 支援基於角色的存取控制 (RBAC)。
|
||||
|
||||
#### Scenario: 角色權限檢查
|
||||
- **GIVEN** 使用者被指派特定角色
|
||||
- **WHEN** 使用者嘗試存取受保護的資源
|
||||
- **THEN** 系統根據角色權限決定是否允許存取
|
||||
|
||||
#### Scenario: 角色指派
|
||||
- **GIVEN** 管理員擁有使用者管理權限
|
||||
- **WHEN** 管理員為使用者指派角色
|
||||
- **THEN** 系統更新使用者的角色設定
|
||||
- **AND** 新權限立即生效
|
||||
|
||||
### Requirement: Department Isolation
|
||||
系統 SHALL 實施部門級別的資料隔離,確保跨部門資料安全。
|
||||
|
||||
#### Scenario: 部門資料隔離
|
||||
- **GIVEN** 使用者屬於研發部門
|
||||
- **WHEN** 使用者嘗試存取廠務部門的專案
|
||||
- **THEN** 系統拒絕存取並顯示無權限訊息
|
||||
|
||||
#### Scenario: 跨部門專案存取
|
||||
- **GIVEN** 專案被設定為跨部門可見
|
||||
- **WHEN** 不同部門的使用者嘗試存取該專案
|
||||
- **THEN** 系統根據專案的 Security_Level 設定決定是否允許存取
|
||||
|
||||
### Requirement: Session Management
|
||||
系統 SHALL 管理使用者 session,包含過期與登出機制。
|
||||
|
||||
#### Scenario: Session 過期
|
||||
- **GIVEN** 使用者已登入系統
|
||||
- **WHEN** Session 超過設定的有效期限
|
||||
- **THEN** 系統自動使 session 失效
|
||||
- **AND** 使用者需重新登入
|
||||
|
||||
#### Scenario: 主動登出
|
||||
- **GIVEN** 使用者已登入系統
|
||||
- **WHEN** 使用者執行登出操作
|
||||
- **THEN** 系統銷毀 session 並清除 token
|
||||
138
openspec/changes/archive/2025-12-28-add-user-auth/tasks.md
Normal file
138
openspec/changes/archive/2025-12-28-add-user-auth/tasks.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Tasks: add-user-auth
|
||||
|
||||
## 1. 專案初始化
|
||||
|
||||
- [x] 1.1 建立 Conda 環境與 requirements.txt
|
||||
- [x] 1.2 初始化 FastAPI 專案結構
|
||||
- [x] 1.3 設定 MySQL 連線與 SQLAlchemy
|
||||
- [x] 1.4 設定 Redis 連線
|
||||
- [x] 1.5 建立環境變數配置 (.env.example)
|
||||
|
||||
## 2. 資料庫模型
|
||||
|
||||
- [x] 2.1 建立 `pjctrl_roles` 資料表 migration
|
||||
- [x] 2.2 建立 `pjctrl_departments` 資料表 migration
|
||||
- [x] 2.3 建立 `pjctrl_users` 資料表 migration
|
||||
- [x] 2.4 建立 seed data (預設管理員與角色)
|
||||
- [x] 2.5 驗證 migration 可正確執行與回滾
|
||||
|
||||
## 3. 認證模組
|
||||
|
||||
- [x] 3.1 實作外部 API 認證 client (`app/services/auth_client.py`)
|
||||
- [x] 3.2 實作 JWT Token 驗證邏輯
|
||||
- [x] 3.3 實作登入 API endpoint (`POST /api/auth/login`)
|
||||
- [x] 3.4 實作登出 API endpoint (`POST /api/auth/logout`)
|
||||
- [x] 3.5 實作取得當前使用者 API (`GET /api/auth/me`)
|
||||
- [x] 3.6 處理認證 API 連線失敗情境
|
||||
|
||||
## 4. Session 管理
|
||||
|
||||
- [x] 4.1 實作 Redis Session 儲存
|
||||
- [x] 4.2 實作 Session 過期機制
|
||||
- [x] 4.3 實作 Token 刷新機制 (如需要) - 暫不需要
|
||||
|
||||
## 5. 權限控制
|
||||
|
||||
- [x] 5.1 實作認證中間件 (`app/middleware/auth.py`)
|
||||
- [x] 5.2 實作 RBAC 權限檢查裝飾器
|
||||
- [x] 5.3 實作部門隔離邏輯
|
||||
- [x] 5.4 實作系統管理員全域權限判斷
|
||||
|
||||
## 6. 使用者管理 API
|
||||
|
||||
- [x] 6.1 實作使用者列表 API (`GET /api/users`)
|
||||
- [x] 6.2 實作使用者詳情 API (`GET /api/users/{id}`)
|
||||
- [x] 6.3 實作角色指派 API (`PATCH /api/users/{id}/role`)
|
||||
- [x] 6.4 實作部門管理 API (CRUD)
|
||||
|
||||
## 7. 測試
|
||||
|
||||
- [x] 7.1 撰寫認證模組單元測試
|
||||
- [x] 7.2 撰寫權限檢查單元測試
|
||||
- [x] 7.3 撰寫 API 整合測試
|
||||
- [x] 7.4 測試系統管理員權限
|
||||
- [x] 7.5 測試部門隔離情境
|
||||
|
||||
## 8. 前端 (基礎)
|
||||
|
||||
- [x] 8.1 建立 React 專案結構
|
||||
- [x] 8.2 實作 AuthContext (認證狀態管理)
|
||||
- [x] 8.3 實作登入頁面
|
||||
- [x] 8.4 實作 Protected Route 元件
|
||||
- [x] 8.5 實作登出功能
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
1.x (專案初始化) → 2.x (資料庫) → 3.x (認證) → 4.x (Session)
|
||||
↓
|
||||
5.x (權限) → 6.x (使用者管理)
|
||||
↓
|
||||
7.x (測試)
|
||||
|
||||
8.x (前端) 可與 3.x-6.x 並行開發
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- 所有資料表使用 `pjctrl_` 前綴
|
||||
- 認證必須透過外部 API,不可有本地繞過
|
||||
- 系統管理員帳號在 seed data 中建立
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
完成日期: 2024-01-XX
|
||||
|
||||
### 已建立的檔案結構
|
||||
|
||||
```
|
||||
backend/
|
||||
├── app/
|
||||
│ ├── api/
|
||||
│ │ ├── auth/router.py # 認證 API
|
||||
│ │ ├── users/router.py # 使用者管理 API
|
||||
│ │ └── departments/router.py # 部門管理 API
|
||||
│ ├── core/
|
||||
│ │ ├── config.py # 環境配置
|
||||
│ │ ├── database.py # 資料庫連線
|
||||
│ │ ├── redis.py # Redis 連線
|
||||
│ │ └── security.py # JWT 處理
|
||||
│ ├── middleware/
|
||||
│ │ └── auth.py # 認證與權限中間件
|
||||
│ ├── models/
|
||||
│ │ ├── user.py # User model
|
||||
│ │ ├── role.py # Role model
|
||||
│ │ └── department.py # Department model
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ ├── services/
|
||||
│ │ └── auth_client.py # 外部認證 API client
|
||||
│ └── main.py # FastAPI 應用程式
|
||||
├── migrations/
|
||||
│ └── versions/
|
||||
│ └── 001_initial_auth_tables.py
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── test_auth.py
|
||||
│ └── test_users.py
|
||||
├── .env
|
||||
├── .env.example
|
||||
├── requirements.txt
|
||||
└── environment.yml
|
||||
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── ProtectedRoute.tsx
|
||||
│ ├── contexts/
|
||||
│ │ └── AuthContext.tsx
|
||||
│ ├── pages/
|
||||
│ │ ├── Login.tsx
|
||||
│ │ └── Dashboard.tsx
|
||||
│ ├── services/
|
||||
│ │ └── api.ts
|
||||
│ ├── App.tsx
|
||||
│ └── main.tsx
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── vite.config.ts
|
||||
```
|
||||
Reference in New Issue
Block a user