# Permission Module

JWT Token 和 Refresh Token 管理模組，提供完整的身份驗證和授權功能。

## 📋 功能特性

### 🔐 JWT Token 管理
- **Access Token 生成**: 基於 JWT 標準生成存取權杖
- **Refresh Token 機制**: 支援長期有效的刷新權杖
- **One-Time Token**: 臨時性權杖，用於特殊場景
- **Token 驗證**: 完整的權杖驗證和解析功能

### 🚫 黑名單機制
- **即時撤銷**: 將 JWT 權杖立即加入黑名單
- **用戶登出**: 支援單一設備或全設備登出
- **自動過期**: 黑名單條目會在權杖過期後自動清理
- **批量管理**: 支援批量黑名單操作

### 💾 Redis 儲存
- **高效能**: 使用 Redis 作為主要儲存引擎
- **TTL 管理**: 自動管理權杖過期時間
- **關聯管理**: 支援用戶、設備與權杖的關聯查詢

### 🔒 安全特性
- **HMAC-SHA256**: 使用安全的簽名算法
- **密鑰分離**: Access Token 和 Refresh Token 使用不同密鑰
- **設備限制**: 支援每用戶、每設備的權杖數量限制
- **過期控制**: 靈活的權杖過期時間配置

## 🏗️ 架構設計

本模組遵循 **Clean Architecture** 原則：

```
pkg/permission/
├── domain/           # 領域層
│   ├── entity/      # 實體定義
│   ├── repository/  # 儲存庫介面
│   ├── usecase/     # 用例介面
│   └── token/       # 權杖相關常數和類型
├── usecase/         # 用例實現
├── repository/      # 儲存庫實現
└── mock/           # 測試模擬
```

### 領域層 (Domain)
- **Entity**: 定義核心業務實體（Token、BlacklistEntry、Ticket）
- **Repository Interface**: 定義資料存取介面
- **UseCase Interface**: 定義業務用例介面
- **Token Types**: 權杖類型和常數定義

### 用例層 (UseCase)
- **TokenUseCase**: 核心業務邏輯實現
- **JWT 處理**: 權杖生成、解析、驗證
- **黑名單管理**: 權杖撤銷和黑名單查詢

### 儲存層 (Repository)
- **Redis 實現**: 基於 Redis 的資料存取
- **關聯管理**: 用戶、設備、權杖關聯
- **TTL 管理**: 自動過期處理

## 🚀 快速開始

### 1. 配置設定

在 `internal/config/config.go` 中添加 Token 配置：

```go
type Config struct {
    // ... 其他配置
    
    Token struct {
        AccessSecret         string        // Access Token 簽名密鑰
        RefreshSecret        string        // Refresh Token 簽名密鑰
        AccessTokenExpiry    time.Duration // Access Token 過期時間
        RefreshTokenExpiry   time.Duration // Refresh Token 過期時間
        OneTimeTokenExpiry   time.Duration // 一次性 Token 過期時間
        MaxTokensPerUser     int           // 每用戶最大 Token 數
        MaxTokensPerDevice   int           // 每設備最大 Token 數
    }
}
```

### 2. 初始化模組

```go
import (
    "backend/pkg/permission/repository"
    "backend/pkg/permission/usecase"
)

// 初始化 Repository
tokenRepo := repository.MustTokenRepository(repository.TokenRepositoryParam{
    Redis: redisClient,
})

// 初始化 UseCase
tokenUseCase := usecase.MustTokenUseCase(usecase.TokenUseCaseParam{
    TokenRepo: tokenRepo,
    Config:    config,
})
```

### 3. 基本使用

#### 創建 Access Token

```go
req := entity.AuthorizationReq{
    GrantType:      token.PasswordCredentials.ToString(),
    Scope:          "read write",
    DeviceID:       "device123",
    IsRefreshToken: true,
    Claims: map[string]string{
        "uid":  "user123",
        "role": "admin",
    },
}

resp, err := tokenUseCase.NewToken(ctx, req)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Access Token: %s\n", resp.AccessToken)
fmt.Printf("Refresh Token: %s\n", resp.RefreshToken)
```

#### 驗證 Token

```go
req := entity.ValidationTokenReq{
    Token: accessToken,
}

resp, err := tokenUseCase.ValidationToken(ctx, req)
if err != nil {
    log.Printf("Token validation failed: %v", err)
    return
}

fmt.Printf("Token is valid for user: %s\n", resp.Token.UID)
```

#### 撤銷 Token (加入黑名單)

```go
err := tokenUseCase.BlacklistToken(ctx, accessToken, "user logout")
if err != nil {
    log.Printf("Failed to blacklist token: %v", err)
}
```

#### 檢查黑名單

```go
isBlacklisted, err := tokenUseCase.IsTokenBlacklisted(ctx, jti)
if err != nil {
    log.Printf("Failed to check blacklist: %v", err)
}

if isBlacklisted {
    log.Println("Token is blacklisted")
}
```

## 🧪 測試

### 運行測試

```bash
# 運行所有測試
go test ./pkg/permission/...

# 運行特定模組測試
go test ./pkg/permission/usecase/
go test ./pkg/permission/repository/

# 運行測試並顯示覆蓋率
go test -cover ./pkg/permission/...

# 生成覆蓋率報告
go test -coverprofile=coverage.out ./pkg/permission/...
go tool cover -html=coverage.out
```

### 測試結構

- **UseCase Tests**: 業務邏輯測試，使用 Mock Repository
- **Repository Tests**: 資料存取測試，使用 MiniRedis
- **JWT Tests**: 權杖生成和解析測試
- **Integration Tests**: 整合測試

## 📊 API 參考

### TokenUseCase 介面

```go
type TokenUseCase interface {
    // 基本 Token 操作
    NewToken(ctx context.Context, req entity.AuthorizationReq) (entity.TokenResp, error)
    RefreshToken(ctx context.Context, req entity.RefreshTokenReq) (entity.RefreshTokenResp, error)
    ValidationToken(ctx context.Context, req entity.ValidationTokenReq) (entity.ValidationTokenResp, error)
    
    // Token 管理
    CancelToken(ctx context.Context, req entity.CancelTokenReq) error
    CancelTokens(ctx context.Context, req entity.DoTokenByUIDReq) error
    CancelTokenByDeviceID(ctx context.Context, req entity.DoTokenByDeviceIDReq) error
    
    // 查詢操作
    GetUserTokensByUID(ctx context.Context, req entity.QueryTokenByUIDReq) ([]*entity.TokenResp, error)
    GetUserTokensByDeviceID(ctx context.Context, req entity.DoTokenByDeviceIDReq) ([]*entity.TokenResp, error)
    
    // 一次性 Token
    NewOneTimeToken(ctx context.Context, req entity.CreateOneTimeTokenReq) (entity.CreateOneTimeTokenResp, error)
    CancelOneTimeToken(ctx context.Context, req entity.CancelOneTimeTokenReq) error
    
    // 黑名單操作
    BlacklistToken(ctx context.Context, token string, reason string) error
    IsTokenBlacklisted(ctx context.Context, jti string) (bool, error)
    BlacklistAllUserTokens(ctx context.Context, uid string, reason string) error
    
    // 工具方法
    ReadTokenBasicData(ctx context.Context, token string) (map[string]string, error)
}
```

### 主要實體

#### Token 實體
```go
type Token struct {
    ID                  string    // 權杖唯一標識
    UID                 string    // 用戶 ID
    DeviceID            string    // 設備 ID
    AccessToken         string    // Access Token
    RefreshToken        string    // Refresh Token
    ExpiresIn           int       // 過期時間（秒）
    AccessCreateAt      time.Time // Access Token 創建時間
    RefreshCreateAt     time.Time // Refresh Token 創建時間
    RefreshExpiresIn    int       // Refresh Token 過期時間（秒）
}
```

#### 黑名單實體
```go
type BlacklistEntry struct {
    JTI       string // JWT ID
    UID       string // 用戶 ID
    TokenID   string // Token ID
    Reason    string // 加入黑名單原因
    ExpiresAt int64  // 原始權杖過期時間
    CreatedAt int64  // 加入黑名單時間
}
```

## 🔧 配置參數

| 參數 | 類型 | 說明 | 預設值 |
|------|------|------|--------|
| `AccessSecret` | string | Access Token 簽名密鑰 | 必填 |
| `RefreshSecret` | string | Refresh Token 簽名密鑰 | 必填 |
| `AccessTokenExpiry` | Duration | Access Token 過期時間 | 15分鐘 |
| `RefreshTokenExpiry` | Duration | Refresh Token 過期時間 | 7天 |
| `OneTimeTokenExpiry` | Duration | 一次性 Token 過期時間 | 5分鐘 |
| `MaxTokensPerUser` | int | 每用戶最大 Token 數 | 10 |
| `MaxTokensPerDevice` | int | 每設備最大 Token 數 | 5 |

## 🚨 錯誤處理

模組定義了完整的錯誤類型：

```go
// Token 驗證錯誤
var (
    ErrInvalidTokenID     = errors.New("invalid token ID")
    ErrInvalidUID         = errors.New("invalid UID")
    ErrTokenExpired       = errors.New("token expired")
    ErrTokenNotFound      = errors.New("token not found")
)

// JWT 特定錯誤
var (
    ErrInvalidJWTToken    = errors.New("invalid JWT token")
    ErrJWTSigningFailed   = errors.New("JWT signing failed")
    ErrJWTParsingFailed   = errors.New("JWT parsing failed")
)

// 黑名單錯誤
var (
    ErrTokenBlacklisted   = errors.New("token is blacklisted")
    ErrBlacklistNotFound  = errors.New("blacklist entry not found")
)
```

## 🔒 安全考量

### 1. 密鑰管理
- 使用強密鑰（至少 256 位）
- Access Token 和 Refresh Token 使用不同密鑰
- 定期輪換密鑰

### 2. 權杖過期
- Access Token 使用較短過期時間（15分鐘）
- Refresh Token 使用較長過期時間（7天）
- 支援自定義過期時間

### 3. 黑名單機制
- 即時撤銷可疑權杖
- 支援批量撤銷
- 自動清理過期條目

### 4. 限制機制
- 每用戶權杖數量限制
- 每設備權杖數量限制
- 防止權杖濫用

## 📈 效能優化

### 1. Redis 優化
- 使用適當的 TTL 避免記憶體洩漏
- 批量操作減少網路往返
- 使用 Pipeline 提升效能

### 2. JWT 優化
- 最小化 Claims 數據大小
- 使用高效的序列化格式
- 快取常用的解析結果

### 3. 黑名單優化
- 使用 SCAN 而非 KEYS 遍歷
- 批量檢查黑名單狀態
- 定期清理過期條目

## 🤝 貢獻指南

1. Fork 本專案
2. 創建功能分支 (`git checkout -b feature/amazing-feature`)
3. 提交變更 (`git commit -m 'Add some amazing feature'`)
4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 開啟 Pull Request

### 開發規範

- 遵循 Go 編碼規範
- 保持測試覆蓋率 > 80%
- 添加適當的文檔註釋
- 使用有意義的提交訊息

## 📄 授權條款

本專案採用 MIT 授權條款 - 詳見 [LICENSE](LICENSE) 檔案

## 📞 聯絡資訊

如有問題或建議，請通過以下方式聯絡：

- 開啟 Issue
- 發送 Pull Request
- 聯絡維護團隊

---

**注意**: 本模組是 PlayOne Backend 專案的一部分，請確保與整體架構保持一致。