feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage

Major Features:
-  Multi-tenant architecture (tenant isolation)
-  Employee CRUD with lifecycle management (onboarding/offboarding)
-  Department tree structure with email domain management
-  Company info management (single-record editing)
-  System functions CRUD (permission management)
-  Email account management (multi-account per employee)
-  Keycloak SSO integration (auth.lab.taipei)
-  Redis session storage (10.1.0.254:6379)
  - Solves Cookie 4KB limitation
  - Cross-system session sharing
  - Sliding expiration (8 hours)
  - Automatic token refresh

Technical Stack:
Backend:
- FastAPI + SQLAlchemy
- PostgreSQL 16 (10.1.0.20:5433)
- Keycloak Admin API integration
- Docker Mailserver integration (SSH)
- Alembic migrations

Frontend:
- Next.js 14 (App Router)
- NextAuth 4 with Keycloak Provider
- Redis session storage (ioredis)
- Tailwind CSS

Infrastructure:
- Redis 7 (10.1.0.254:6379) - Session + Cache
- Keycloak 26.1.0 (auth.lab.taipei)
- Docker Mailserver (10.1.0.254)

Architecture Highlights:
- Session管理由 Keycloak + Redis 統一控制
- 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session
- Token 自動刷新,異質服務整合
- 未來可無縫遷移到雲端

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:12:43 +08:00
commit 360533393f
386 changed files with 70353 additions and 0 deletions

215
KEYCLOAK-SETUP-CHECKLIST.md Normal file
View File

@@ -0,0 +1,215 @@
# Keycloak Client 設定檢查清單
## Client: hr-portal-web
### ✅ Access Settings (已完成)
- [x] Valid redirect URIs: 已設定三個環境
- [x] Valid post logout redirect URIs: 已設定
- [x] Web origins: 已設定 CORS
### ⏳ General Settings (需確認)
請確認以下設定:
#### Client ID
```
hr-portal-web
```
#### Client Protocol
```
openid-connect
```
#### Access Type
```
public ← 必須是 public,因為是 SPA 應用
```
#### Standard Flow Enabled
```
ON ← 必須開啟
```
#### Direct Access Grants Enabled
```
OFF ← 建議關閉 (SPA 不需要)
```
#### Implicit Flow Enabled
```
OFF ← 必須關閉 (使用 Standard Flow + PKCE)
```
### ⏳ Advanced Settings (需確認)
#### Proof Key for Code Exchange Code Challenge Method
```
S256 ← 必須設定為 S256
```
這個設定非常重要!必須啟用 PKCE 以確保安全性。
位置:
1. 進入 Client 詳細頁面
2. 切換到 **Advanced Settings** 標籤
3. 找到 **Proof Key for Code Exchange Code Challenge Method**
4. 選擇 **S256**
5. 點擊 **Save**
### ⏳ Client Scopes (需確認)
確認以下 scopes 在 **Assigned Default Client Scopes**:
#### 必須有的 Scopes
- [x] `openid` - OpenID Connect 核心
- [x] `profile` - 使用者基本資料 (name, username)
- [x] `email` - 使用者電子郵件
#### 檢查方式
1. 進入 Client 詳細頁面
2. 切換到 **Client Scopes** 標籤
3. 查看 **Assigned Default Client Scopes** 區塊
4. 確認 `openid`, `profile`, `email` 都在列表中
5. 如果沒有,從 **Available Client Scopes** 加入
### ⏳ Roles (選用,但建議設定)
為了權限控制,建議在 Realm 中建立以下 Roles:
#### 進入 Realm Roles
1. 左側選單選擇 **Realm Roles**
2. 點擊 **Add Role**
#### 建議的 Roles
```
hr-admin - HR 管理員 (完整權限)
hr-manager - HR 經理 (查看與編輯)
hr-viewer - HR 檢視者 (僅查看)
```
#### 設定步驟 (每個 Role)
1. Role Name: 輸入 `hr-admin` (或其他)
2. Description: 輸入說明,例如 "HR Portal Administrator"
3. 點擊 **Save**
### ⏳ 使用者設定 (測試用)
為測試帳號指派 Role:
1. 左側選單選擇 **Users**
2. 找到你的測試帳號 (例如: porsche)
3. 點擊進入使用者詳細頁面
4. 切換到 **Role Mappings** 標籤
5.**Available Roles** 中找到 `hr-admin`
6. 點擊 **Add selected** 將 role 加入
7. 確認 role 出現在 **Assigned Roles**
## 測試流程
### 1. 檢查 OIDC Discovery Endpoint
在瀏覽器或使用 curl 訪問:
```
https://auth.ease.taipei/realms/porscheworld/.well-known/openid-configuration
```
應該能看到完整的 OpenID Connect 配置。
### 2. 測試前端登入
#### 步驟:
1. 確認開發伺服器運行: `npm run dev`
2. 瀏覽器開啟: `http://localhost:3000`
3. 應該會看到登入頁面
4. 點擊「使用 SSO 登入」
5. 應該會跳轉到 Keycloak 登入頁面
6. 輸入帳號密碼登入
7. 登入成功後應該回到 HR Portal 首頁
8. Header 應該顯示使用者名稱和 Email
#### 預期結果:
- ✅ 成功跳轉到 Keycloak
- ✅ 登入成功回到應用
- ✅ Header 顯示正確的使用者資訊
- ✅ 可以正常瀏覽各個頁面
- ✅ 登出功能正常
#### 如果失敗:
1. 開啟瀏覽器開發者工具 (F12)
2. 查看 Console 是否有錯誤訊息
3. 查看 Network 標籤,檢查 Keycloak 的請求
4. 常見錯誤:
- **Invalid redirect_uri**: 檢查 Valid Redirect URIs 設定
- **CORS error**: 檢查 Web Origins 設定
- **Invalid client**: 檢查 Client ID 是否正確
- **PKCE required**: 檢查 PKCE 是否設定為 S256
### 3. 測試 Token 刷新
登入後:
1. 保持應用開啟
2. 觀察 Console (每 30 秒會檢查 Token)
3. 應該會看到 "Token 已刷新" 訊息 (如果 Token 快過期)
### 4. 測試 API 呼叫
登入後:
1. 訪問員工列表頁面: `http://localhost:3000/employees`
2. 開啟開發者工具 Network 標籤
3. 檢查 API 請求 (例如: GET /api/v1/employees)
4. 查看 Request Headers
5. 應該包含: `Authorization: Bearer eyJhbG...` (Token)
## 完成確認
全部完成後,請確認:
- [ ] Client ID 為 `hr-portal-web`
- [ ] Access Type 為 `public`
- [ ] Standard Flow 已啟用
- [ ] PKCE 設定為 S256
- [ ] Valid Redirect URIs 已正確設定
- [ ] Web Origins 已正確設定
- [ ] Client Scopes 包含 openid, profile, email
- [ ] 測試帳號已指派 hr-admin role
- [ ] 前端登入測試成功
- [ ] Token 自動刷新運作正常
- [ ] API 請求自動帶入 Bearer Token
## 疑難排解
### 問題: Invalid redirect_uri
**原因**: Valid Redirect URIs 設定錯誤
**解決**: 確認 URIs 完全匹配,包含 protocol (http/https)、host、port
### 問題: CORS error
**原因**: Web Origins 未設定
**解決**: 在 Web Origins 加入應用的 origin
### 問題: Token 無法刷新
**原因**: Realm 的 Token Lifespan 設定太短
**解決**:
1. Realm Settings → Tokens
2. 調整 Access Token Lifespan (建議 5-30 分鐘)
3. 調整 SSO Session Idle (建議 30 分鐘)
4. 調整 SSO Session Max (建議 10 小時)
### 問題: 使用者資訊不完整
**原因**: Client Scopes 缺少 profile 或 email
**解決**: 在 Client Scopes 加入對應的 scope
### 問題: 角色資訊取不到
**原因**: Token 中沒有包含 roles
**解決**:
1. Client Scopes → Create new scope: `roles`
2. Mappers → Create Protocol Mapper
3. Mapper Type: User Realm Role
4. Token Claim Name: realm_access.roles
5. 將 scope 加入 Client 的 Default Client Scopes
---
**文件版本**: 1.0
**最後更新**: 2026-02-09
**狀態**: 待驗證