1263 lines
45 KiB
Markdown
1263 lines
45 KiB
Markdown
|
|
# Identity / Member / Permission 模組設計草稿
|
|||
|
|
|
|||
|
|
> **狀態**:Draft(待 Review)
|
|||
|
|
> **適用專案**:Portal API Gateway(PGW)
|
|||
|
|
> **參考實作**:[app-cloudep-permission-server](https://code.30cm.net/digimon/app-cloudep-permission-server)(Casbin RBAC、Permission Tree、Role/RolePermission)
|
|||
|
|
> **最後更新**:2026-05-19
|
|||
|
|
> **前提**:全新 Gateway module,不考慮舊版 member-server 遷移。
|
|||
|
|
|
|||
|
|
本文件描述 Gateway 內 **auth**、**member**、**permission** 三個業務模組的目標架構,整合 **ZITADEL**(身份)、**LDAP**(企業目錄)、**SCIM 2.0**(企業 provisioning),支援 **多租戶** 與 **百萬級會員**(含單租戶 50 萬)。
|
|||
|
|
|
|||
|
|
模組分層與程式碼撰寫規範見 [model.md](./model.md)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目錄
|
|||
|
|
|
|||
|
|
1. [設計目標與原則](#1-設計目標與原則)
|
|||
|
|
2. [模組全景](#2-模組全景)
|
|||
|
|
3. [外部系統分工](#3-外部系統分工)
|
|||
|
|
4. [auth 模組](#4-auth-模組)
|
|||
|
|
5. [member 模組](#5-member-模組)
|
|||
|
|
6. [permission 模組(B2B 自定義)](#6-permission-模組b2b-自定義)
|
|||
|
|
7. [API 規劃](#7-api-規劃)
|
|||
|
|
8. [Middleware 鏈](#8-middleware-鏈)
|
|||
|
|
9. [核心流程](#9-核心流程)
|
|||
|
|
10. [LDAP 與 SCIM](#10-ldap-與-scim)
|
|||
|
|
11. [可讀 UID 設計](#11-可讀-uid-設計)
|
|||
|
|
12. [資料模型與索引](#12-資料模型與索引)
|
|||
|
|
13. [Redis Key 命名](#13-redis-key-命名)
|
|||
|
|
14. [規模與性能(100 萬+ / 單租戶 50 萬)](#14-規模與性能100-萬--單租戶-50-萬)
|
|||
|
|
15. [目錄結構](#15-目錄結構)
|
|||
|
|
16. [設定檔](#16-設定檔)
|
|||
|
|
17. [實施順序](#17-實施順序)
|
|||
|
|
18. [待決策事項](#18-待決策事項)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 設計目標與原則
|
|||
|
|
|
|||
|
|
### 1.1 目標
|
|||
|
|
|
|||
|
|
| 目標 | 說明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 統一身份 | ZITADEL 作為 IdP(含 LDAP IdP、Social Login) |
|
|||
|
|
| 業務會員 | Gateway `member` 模組管理 tenant-scoped profile |
|
|||
|
|
| 細粒度授權 | Gateway `permission` 模組(**Casbin RBAC + Permission Tree**);**每個 B2B 租戶可自定義 Role 並勾選 Permission** |
|
|||
|
|
| Token | go-zero JWT 驗證 + Redis 黑名單(只黑名單 JWT) |
|
|||
|
|
| 企業整合 | SCIM 2.0 + LDAP Directory Sync(AD + OpenLDAP) |
|
|||
|
|
| 規模 | 全平台 100 萬+ 會員;單租戶可達 50 萬 |
|
|||
|
|
| UID | 人類可讀、可口述,非 UUID / 亂碼 |
|
|||
|
|
|
|||
|
|
### 1.2 核心原則
|
|||
|
|
|
|||
|
|
1. **職責分離**
|
|||
|
|
- `auth`:你是誰(Authentication)
|
|||
|
|
- `member`:你的業務資料是什麼(Profile)
|
|||
|
|
- `permission`:你能做什麼(Authorization)
|
|||
|
|
|
|||
|
|
2. **LDAP 不做登入 bind**
|
|||
|
|
- 登入驗證由 ZITADEL LDAP IdP 處理
|
|||
|
|
- Gateway 的 LDAP client 僅供 Directory Sync(read-only)
|
|||
|
|
|
|||
|
|
3. **Token Exchange**
|
|||
|
|
- 對外 API 只接受 Gateway 簽發的 CloudEP JWT
|
|||
|
|
- ZITADEL OIDC token 僅在 `/auth/token/exchange` 使用一次
|
|||
|
|
|
|||
|
|
4. **租戶隔離**
|
|||
|
|
- 所有持久化資料以 `tenant_id` 為邊界
|
|||
|
|
- JWT `tenant_id` 與請求資源必須一致
|
|||
|
|
|
|||
|
|
5. **B2B 權限自定義**(參考 [app-cloudep-permission-server](https://code.30cm.net/digimon/app-cloudep-permission-server))
|
|||
|
|
- 平台 seed 全局 Permission Tree(含 `http_path` / `http_method`)
|
|||
|
|
- 租戶建立自訂 Role,從 Tree **勾選** Permission(`RolePermission` + 自動補 parent)
|
|||
|
|
- API 授權由 **Casbin** 比對 `(role.Name, path, method)`
|
|||
|
|
- B2C 租戶仅用 seed 模板
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 模組全景
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ Portal API Gateway (go-zero) │
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ generate/api/ │
|
|||
|
|
│ auth.api · member.api · permission.api · tenant.api · scim.api│
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ internal/middleware/ │
|
|||
|
|
│ jwt_revoke · casbin_rbac · scim_auth · tenant_context │
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ internal/model/ │
|
|||
|
|
│ auth/ → Token 簽發、換票、登出、黑名單、auth_gen │
|
|||
|
|
│ member/ → Profile、Identity、Tenant、UID、Directory Sync │
|
|||
|
|
│ permission/ → Casbin RBAC、Permission Tree、Role(B2B 自定義)│
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ internal/library/ │
|
|||
|
|
│ zitadel/ · ldap/ · uid/ · casbin/(RBAC enforcer 封裝) │
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ internal/worker/ │
|
|||
|
|
│ directory_sync/ │
|
|||
|
|
└─────────────────────────────────────────────────────────────────┘
|
|||
|
|
│ │ │
|
|||
|
|
▼ ▼ ▼
|
|||
|
|
MongoDB Redis ZITADEL
|
|||
|
|
(profile/role) (cache/blacklist) (identity/LDAP IdP)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.1 模組依賴方向
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
handler → logic → model/{auth|member|permission}/usecase(interface)
|
|||
|
|
↓
|
|||
|
|
repository → MongoDB / Redis
|
|||
|
|
|
|||
|
|
logic 不 import entity / repository(見 model.md)
|
|||
|
|
|
|||
|
|
auth → member(EnsureMember)
|
|||
|
|
auth → permission(SyncRolesFromClaims)
|
|||
|
|
member → auth(停權時 RevokeAllForUser)
|
|||
|
|
permission → member(可選:驗證 uid 存在)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 外部系統分工
|
|||
|
|
|
|||
|
|
| 能力 | ZITADEL | Gateway auth | Gateway member | Gateway permission |
|
|||
|
|
|------|---------|--------------|----------------|-------------------|
|
|||
|
|
| 註冊 / 登入 | ✅ | 換票 | EnsureMember | SyncRoles |
|
|||
|
|
| 密碼 / MFA / 忘記密碼 | ✅ | — | — | — |
|
|||
|
|
| Google / LINE / Apple | ✅ IdP | — | — | — |
|
|||
|
|
| LDAP 登入 | ✅ LDAP IdP | — | — | Group→Role 映射 |
|
|||
|
|
| Access / Refresh Token(對外) | — | ✅ CloudEP JWT | — | — |
|
|||
|
|
| JWT 黑名單 | — | ✅ Redis | — | — |
|
|||
|
|
| 業務 UID | — | — | ✅ | — |
|
|||
|
|
| Profile | — | — | ✅ | — |
|
|||
|
|
| 會員列表 / 狀態 | — | — | ✅ | 需授權 |
|
|||
|
|
| API 細粒度權限 | 粗粒度 Role | — | — | **Casbin RBAC**(path + method) |
|
|||
|
|
| SCIM Users/Groups | 可同步 | — | ✅ 業務寫入 | ✅ Group→Role |
|
|||
|
|
| LDAP Directory Sync | — | — | ✅ Worker | ✅ Group→Role |
|
|||
|
|
|
|||
|
|
### 3.1 多租戶對應
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1 CloudEP Tenant = 1 ZITADEL Organization = 1 資料隔離邊界
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 欄位 | 來源 | 用途 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `tenant_id` | ZITADEL `org_id` | 分片鍵、授權邊界 |
|
|||
|
|
| `identity_id` | ZITADEL `sub` | 身份映射 |
|
|||
|
|
| `uid` | Member 模組產生 | 業務會員 ID(如 `ACME-ODWXGYBK`) |
|
|||
|
|
|
|||
|
|
### 3.2 租戶類型
|
|||
|
|
|
|||
|
|
| 類型 | 登入 | LDAP | 權限 |
|
|||
|
|
|------|------|------|------|
|
|||
|
|
| **B2C** | Email / Social | 無 | 系統預設 Role(不可或不常自定義) |
|
|||
|
|
| **B2B** | ZITADEL → LDAP IdP | 有 | **完全自定義 Role + Permission** |
|
|||
|
|
| **Hybrid** | Social + LDAP | 有 | B2B 自定義 + 外部客戶用預設 Role |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. auth 模組
|
|||
|
|
|
|||
|
|
路徑:`internal/model/auth/`
|
|||
|
|
|
|||
|
|
### 4.1 職責
|
|||
|
|
|
|||
|
|
- 驗證 ZITADEL OIDC token(id_token / authorization_code + PKCE)
|
|||
|
|
- 編排 `member.EnsureMember` 與 `permission.SyncRolesFromClaims`
|
|||
|
|
- 簽發 CloudEP JWT(access + refresh)
|
|||
|
|
- 登出:jti 黑名單
|
|||
|
|
- 批量失效:`auth_gen`(停權 / 改密碼 / 權限強制刷新)
|
|||
|
|
|
|||
|
|
### 4.2 UseCase 介面
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type TokenUseCase interface {
|
|||
|
|
Exchange(ctx context.Context, req *ExchangeRequest) (*TokenPair, error)
|
|||
|
|
Refresh(ctx context.Context, req *RefreshRequest) (*TokenPair, error)
|
|||
|
|
Logout(ctx context.Context, req *LogoutRequest) error
|
|||
|
|
RevokeAllForUser(ctx context.Context, tenantID, uid string) error
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 CloudEP JWT Claims
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Claims struct {
|
|||
|
|
jwt.RegisteredClaims // 含 jti, exp, iat
|
|||
|
|
TenantID string `json:"tenant_id"`
|
|||
|
|
UID string `json:"uid"`
|
|||
|
|
Roles string `json:"roles"` // 逗號分隔 Role.Name,Casbin enforce 用
|
|||
|
|
Typ string `json:"typ"` // access | refresh
|
|||
|
|
AuthGen int64 `json:"auth_gen"` // 批量失效代號
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.4 JWT 設定(go-zero)
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
Auth:
|
|||
|
|
AccessSecret: ${JWT_ACCESS_SECRET}
|
|||
|
|
AccessExpire: 900 # 15 分鐘
|
|||
|
|
|
|||
|
|
RefreshAuth:
|
|||
|
|
AccessSecret: ${JWT_REFRESH_SECRET}
|
|||
|
|
AccessExpire: 604800 # 7 天
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`.api` 受保護路由:
|
|||
|
|
|
|||
|
|
```api
|
|||
|
|
@server(jwt: Auth, middleware: JwtRevokeMiddleware)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.5 黑名單策略(只黑名單 JWT)
|
|||
|
|
|
|||
|
|
#### 單 Token 撤銷(登出)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Key: auth:jwt:bl:{jti}
|
|||
|
|
Value: 1
|
|||
|
|
TTL: token 剩餘有效時間(exp - now)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
登出時同時黑名單 access + refresh 的 jti。
|
|||
|
|
|
|||
|
|
#### 批量失效(停權 / 改密碼 / SCIM deactivate / 角色強刷)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Key: auth:gen:{tenant_id}:{uid}
|
|||
|
|
Value: 整數,預設 1;事件發生時 INCR
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Middleware 檢查:`token.auth_gen >= redis.auth_gen`,否則 401。
|
|||
|
|
|
|||
|
|
> JWT 內不放全部 permission(避免 token 過大);批量失效用 `auth_gen`,單次登出用 jti 黑名單。
|
|||
|
|
|
|||
|
|
### 4.6 Middleware 檢查順序
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. go-zero JWT 驗簽 + exp
|
|||
|
|
2. typ == "access"(受保護 API)
|
|||
|
|
3. NOT EXISTS auth:jwt:bl:{jti}
|
|||
|
|
4. claims.auth_gen >= redis auth:gen:{tenant}:{uid}
|
|||
|
|
5. 注入 context:tenant_id, uid, roles
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. member 模組
|
|||
|
|
|
|||
|
|
路徑:`internal/model/member/`
|
|||
|
|
|
|||
|
|
### 5.1 職責
|
|||
|
|
|
|||
|
|
- 會員 Profile CRUD(tenant-scoped)
|
|||
|
|
- Identity 映射(`zitadel_sub` ↔ `uid`)
|
|||
|
|
- Tenant metadata 與 LDAP 同步設定
|
|||
|
|
- UID 產生(可讀格式)
|
|||
|
|
- SCIM 業務寫入(User + externalId = uid)
|
|||
|
|
- Directory Sync Worker(AD + OpenLDAP)
|
|||
|
|
- 會員狀態(active / suspended)→ 通知 auth 撤銷 token
|
|||
|
|
|
|||
|
|
### 5.2 UseCase 介面
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ProvisioningUseCase interface {
|
|||
|
|
EnsureMember(ctx context.Context, req *EnsureMemberRequest) (*MemberDTO, error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ProfileUseCase interface {
|
|||
|
|
GetByUID(ctx context.Context, req *GetMemberRequest) (*MemberDTO, error)
|
|||
|
|
Update(ctx context.Context, req *UpdateMemberRequest) (*MemberDTO, error)
|
|||
|
|
List(ctx context.Context, req *ListMembersRequest) (*ListMembersResponse, error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type AdminUseCase interface {
|
|||
|
|
UpdateStatus(ctx context.Context, req *UpdateStatusRequest) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type TenantUseCase interface {
|
|||
|
|
Create(ctx context.Context, req *CreateTenantRequest) (*TenantDTO, error)
|
|||
|
|
ResolveBySlug(ctx context.Context, slug string) (*TenantDTO, error)
|
|||
|
|
ConfigureLDAP(ctx context.Context, req *ConfigureLDAPRequest) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ScimUseCase interface {
|
|||
|
|
CreateUser(ctx context.Context, req *ScimCreateUserRequest) (*ScimUserDTO, error)
|
|||
|
|
GetUser(ctx context.Context, req *ScimGetUserRequest) (*ScimUserDTO, error)
|
|||
|
|
PatchUser(ctx context.Context, req *ScimPatchUserRequest) (*ScimUserDTO, error)
|
|||
|
|
DeleteUser(ctx context.Context, req *ScimDeleteUserRequest) error
|
|||
|
|
// Groups(供 SCIM Group → Role 映射)
|
|||
|
|
PatchGroup(ctx context.Context, req *ScimPatchGroupRequest) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type DirectorySyncUseCase interface {
|
|||
|
|
SyncTenant(ctx context.Context, tenantID string) (*SyncResult, error)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 會員狀態
|
|||
|
|
|
|||
|
|
| 狀態 | 語意 | 副作用 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| `unverified` | 尚未完成業務驗證 | — |
|
|||
|
|
| `active` | 正常使用 | — |
|
|||
|
|
| `suspended` | 停權 | `auth.RevokeAllForUser` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. permission 模組(B2B 自定義,參考 permission-server)
|
|||
|
|
|
|||
|
|
路徑:`internal/model/permission/`
|
|||
|
|
|
|||
|
|
> 本節吸收 [app-cloudep-permission-server](https://code.30cm.net/digimon/app-cloudep-permission-server) 已驗證的設計:**Casbin + Redis RBAC**、**Permission Tree(父子繼承)**、**HTTP Path/Method 綁定**。
|
|||
|
|
> **與舊 permission-server 的差異**:Token 簽發/驗證/黑名單移至 Gateway `auth` 模組;`ClientID` 改為 `tenant_id`;支援多租戶 B2B 各自定義 Role。
|
|||
|
|
|
|||
|
|
### 6.1 設計目標
|
|||
|
|
|
|||
|
|
| 能力 | 說明 |
|
|||
|
|
|------|------|
|
|||
|
|
| **Permission Tree** | 全局權限樹(平台 seed),父子節點繼承;父節點關閉則子節點不可用 |
|
|||
|
|
| **Casbin RBAC** | 以 `(role, http_path, http_method)` 做 API 授權;path 支援 `keyMatch2` 萬用字元 |
|
|||
|
|
| **B2B 自定義 Role** | 每個租戶建立自訂 Role,從全局 Catalog **勾選** Permission(不可自創 Permission 字串) |
|
|||
|
|
| **UserRole** | 租戶 + uid + role;支援多角色(JWT 帶 role names,Casbin 逐一檢查) |
|
|||
|
|
| **RolePermission** | 勾選子權限時自動補齊父權限 ID(沿用 permission-server 的 `getFullParentPermissionIDs`) |
|
|||
|
|
| **Policy 同步** | MongoDB → Casbin Policy → Redis;定時 `LoadPolicy` + 變更時觸發 reload |
|
|||
|
|
| **外部映射** | ZITADEL Role / LDAP Group / SCIM Group → 租戶內部 Role |
|
|||
|
|
| **細粒度擴展** | 同一 API 可掛 `.plain_code` 子權限(如明碼查詢),沿用舊設計 |
|
|||
|
|
|
|||
|
|
### 6.2 與 app-cloudep-permission-server 對照
|
|||
|
|
|
|||
|
|
| permission-server | Gateway permission 模組 | 備註 |
|
|||
|
|
|-------------------|---------------------------|------|
|
|||
|
|
| `TokenService` | **`auth` 模組** | JWT 不再放 permission-server |
|
|||
|
|
| `PermissionService`(空) | 完整 HTTP API | 直接在 Gateway 暴露 |
|
|||
|
|
| `entity.Permission` | 沿用 + `tenant_id` 不適用(全局 Catalog) | Permission 為平台級 |
|
|||
|
|
| `entity.Role.ClientID` | `Role.TenantID` | 租戶隔離 |
|
|||
|
|
| `entity.Role.UID` | `Role.CreatorUID` | 建立者,可選 |
|
|||
|
|
| `entity.Role.Name` | `Role.Name` | **Casbin policy 的 role 欄位** |
|
|||
|
|
| `Casbin Enforcer` | `RBACUseCase` + Redis Adapter | 沿用 |
|
|||
|
|
| `PermissionTree` | `usecase/permission_tree.go` | 沿用 |
|
|||
|
|
| `AdminRoleUID` / `GodDog` | `PlatformSuperAdminUID` | 平台超級管理員 bypass |
|
|||
|
|
| `permission.Type` | `enum.PermissionType` | `BackendUser` / `FrontendUser` |
|
|||
|
|
|
|||
|
|
### 6.3 核心概念
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Permission(全局樹) 平台定義,含 name / http_path / http_method / parent / status / type
|
|||
|
|
Role(租戶自定義) 租戶建立的命名角色,如 sales_supervisor、tenant_admin
|
|||
|
|
RolePermission Role ↔ Permission ID 多對多;勾選時自動補父節點
|
|||
|
|
UserRole uid ↔ Role;一 user 可多 role
|
|||
|
|
RoleMapping 外部 Group/Role → 內部 Role.Name
|
|||
|
|
Casbin Policy p, {role.Name}, {http_path}, {http_method}, {permission.Name}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.4 Permission Entity(全局 Catalog)
|
|||
|
|
|
|||
|
|
沿用 permission-server 的 `entity.Permission` 結構,MongoDB collection:`permission`。
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Permission struct {
|
|||
|
|
ID primitive.ObjectID
|
|||
|
|
Parent string // 父權限 ID(ObjectID hex);空 = 掛 root
|
|||
|
|
Name string // 唯一語意名,dot notation,如 member.info.select
|
|||
|
|
HTTPMethod string // GET / POST / PATCH / DELETE / PUT;分類節點可為空
|
|||
|
|
HTTPPath string // 如 /api/v1/members/*;分類節點可為空
|
|||
|
|
Status enum.Status // open | close
|
|||
|
|
Type enum.PermissionType // backend_user | frontend_user(後台 / 前台菜單)
|
|||
|
|
CreateAt int64
|
|||
|
|
UpdateAt int64
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 命名規則(dot notation,與 permission-server 一致)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
{domain}.{module}.{action}
|
|||
|
|
{domain}.{module}.{action}.{variant} # 如 .plain_code
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Permission Tree 範例(seed 草案)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
member.info.management # 一級:會員資訊管理(分類,無 HTTP)
|
|||
|
|
├── member.basic.info # 二級:基礎資訊
|
|||
|
|
│ ├── member.info.select # GET /api/v1/members/me
|
|||
|
|
│ ├── member.info.update # PATCH /api/v1/members/me
|
|||
|
|
│ └── member.info.select.plain_code # GET /api/v1/members(明碼欄位)
|
|||
|
|
├── member.admin.list # GET /api/v1/members
|
|||
|
|
├── member.admin.read # GET /api/v1/members/:uid
|
|||
|
|
├── member.admin.update # PATCH /api/v1/members/:uid
|
|||
|
|
└── member.admin.status # PATCH /api/v1/members/:uid/status
|
|||
|
|
|
|||
|
|
permission.role.management # 一級:角色權限管理
|
|||
|
|
├── permission.role.read # GET /api/v1/permissions/roles
|
|||
|
|
├── permission.role.write # POST/PUT/DELETE roles
|
|||
|
|
├── permission.assign.write # POST/DELETE user roles
|
|||
|
|
└── permission.catalog.read # GET /api/v1/permissions/catalog
|
|||
|
|
|
|||
|
|
tenant.management
|
|||
|
|
├── tenant.read
|
|||
|
|
├── tenant.ldap.write
|
|||
|
|
└── tenant.sync.trigger
|
|||
|
|
|
|||
|
|
scim.management
|
|||
|
|
├── scim.users.write
|
|||
|
|
└── scim.groups.write
|
|||
|
|
|
|||
|
|
system.management # 平台級
|
|||
|
|
└── system.tenant.create
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **分類節點**(無 `http_path`)供 UI 樹狀勾選;**葉節點**才寫入 Casbin Policy。
|
|||
|
|
> 新增 Permission 走平台 seed migration;租戶**不可**自行新增 Permission 名稱。
|
|||
|
|
|
|||
|
|
#### Permission Tree 行為(沿用 permission-server)
|
|||
|
|
|
|||
|
|
1. **`filterOpenNodes`**:父節點 `status=close` → 整棵子樹不可用
|
|||
|
|
2. **`getFullParentPermissionIDs`**:勾選子權限 → 自動加入所有父節點 ID
|
|||
|
|
3. **`getFullParentPermission`**:查 Role 權限 → 回傳含父節點的完整 permission name → status map(供前端 UI)
|
|||
|
|
|
|||
|
|
### 6.5 Role Entity(B2B 租戶自定義)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Role struct {
|
|||
|
|
ID primitive.ObjectID
|
|||
|
|
TenantID string // 租戶 ID(= 舊 ClientID)
|
|||
|
|
Name string // 角色名稱,租戶內唯一;Casbin enforce 用此值
|
|||
|
|
CreatorUID string // 建立者 uid(= 舊 Role.UID,可選)
|
|||
|
|
Status enum.Status // open | close
|
|||
|
|
IsSystem bool // 系統 seed 的預設角色,B2B 可改 Permission 但不可刪除 Owner
|
|||
|
|
CreateAt int64
|
|||
|
|
UpdateAt int64
|
|||
|
|
}
|
|||
|
|
// Index: { tenant_id, name } unique
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### B2B 自定義規則
|
|||
|
|
|
|||
|
|
1. 租戶可 **CRUD** 自訂 Role(`is_system=false`)
|
|||
|
|
2. 系統 seed 的預設 Role(`is_system=true`)可修改 Permission 集合,**tenant_owner 不可刪**
|
|||
|
|
3. Role 綁定的 Permission 必須是全局 Catalog 中 `status=open` 的節點
|
|||
|
|
4. 租戶**不可**勾選 `system.*` 權限(除非平台另行開啟)
|
|||
|
|
5. 至少保留一個 Role 含 `permission.role.write`,避免租戶自鎖
|
|||
|
|
|
|||
|
|
#### 預設 Role 模板(建立 B2B tenant 時 seed)
|
|||
|
|
|
|||
|
|
| Name | 說明 | 預設勾選(Permission Name) |
|
|||
|
|
|------|------|----------------------------|
|
|||
|
|
| `tenant_owner` | 租戶擁有者 | 除 `system.*` 外全部 open 節點 |
|
|||
|
|
| `tenant_admin` | 租戶管理員 | member.*, permission.*, tenant.*, scim.* |
|
|||
|
|
| `member_manager` | 會員管理 | member.admin.list, member.admin.read, member.admin.status |
|
|||
|
|
| `member` | 一般會員 | member.info.select, member.info.update |
|
|||
|
|
| `viewer` | 唯讀 | member.info.select |
|
|||
|
|
|
|||
|
|
B2B 管理員範例:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
建立 Role:sales_supervisor
|
|||
|
|
勾選:member.admin.list, member.admin.read
|
|||
|
|
指派:POST /permissions/users/{uid}/roles { "role_name": "sales_supervisor" }
|
|||
|
|
→ RolePermission.Create → getFullParentPermissionIDs 自動補 parent
|
|||
|
|
→ LoadPolicy 刷新 Casbin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.6 UserRole / RolePermission
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type UserRole struct {
|
|||
|
|
TenantID string
|
|||
|
|
UID string
|
|||
|
|
RoleID string // Role._id hex
|
|||
|
|
Source enum.RoleSource // manual | zitadel | ldap | scim
|
|||
|
|
CreateAt int64
|
|||
|
|
UpdateAt int64
|
|||
|
|
}
|
|||
|
|
// Index: { tenant_id, uid, role_id } unique
|
|||
|
|
// Index: { tenant_id, uid }
|
|||
|
|
|
|||
|
|
type RolePermission struct {
|
|||
|
|
RoleID string
|
|||
|
|
PermissionID string
|
|||
|
|
CreateAt int64
|
|||
|
|
UpdateAt int64
|
|||
|
|
}
|
|||
|
|
// Index: { role_id, permission_id } unique
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> 舊 permission-server 的 UserRole 為一 user 一 role(Update 覆蓋);新設計**支援多角色**,Middleware 對每個 role name 做 Casbin enforce,任一 allow 即通過。
|
|||
|
|
|
|||
|
|
### 6.7 Casbin RBAC(核心授權引擎)
|
|||
|
|
|
|||
|
|
#### 模型檔 `etc/rbac.conf`(沿用 permission-server)
|
|||
|
|
|
|||
|
|
```ini
|
|||
|
|
[request_definition]
|
|||
|
|
r = role, path, method
|
|||
|
|
|
|||
|
|
[policy_definition]
|
|||
|
|
p = role, path, methods, name
|
|||
|
|
|
|||
|
|
[policy_effect]
|
|||
|
|
e = some(where (p.eft == allow))
|
|||
|
|
|
|||
|
|
[role_definition]
|
|||
|
|
g = _, _
|
|||
|
|
|
|||
|
|
[matchers]
|
|||
|
|
m = g(r.role, p.role) && keyMatch2(r.path, p.path) && regexMatch(r.method, p.methods) \
|
|||
|
|
|| r.role == "${PlatformSuperAdminUID}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **`keyMatch2`**:支援 `/api/v1/members/*` 萬用 path
|
|||
|
|
- **`regexMatch`**:支援 `GET|POST` 多 method 寫在同一 policy
|
|||
|
|
- **SuperAdmin bypass**:平台維運 uid 全放行(對應舊 `GodDog`)
|
|||
|
|
|
|||
|
|
#### Policy 載入(`RBACUseCase.LoadPolicy`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. permissionRepo.GetAll → GeneratePermissionTree → filterOpenNodes
|
|||
|
|
2. roleRepo.All(tenant_id) → 每個 role 取 rolePermissionRepo.Get
|
|||
|
|
3. 對每個 (role, permission) 若 http_path + http_method 非空:
|
|||
|
|
enforcer.AddPolicy(role.Name, permission.HTTPPath, permission.HTTPMethod, permission.Name)
|
|||
|
|
4. adapter.SavePolicy → Redis List(casbin rules)
|
|||
|
|
5. enforcer.LoadPolicy()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 授權檢查(`RBACUseCase.Check`)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 輸入:roleName, requestPath, requestMethod
|
|||
|
|
ok, policy, err := enforcer.EnforceEx(roleName, path, method)
|
|||
|
|
|
|||
|
|
// 回傳 CheckRolePermissionStatus:
|
|||
|
|
// Allow: bool
|
|||
|
|
// PermissionName: string // 命中的 permission.Name
|
|||
|
|
// PlainCode: bool // 是否有 .plain_code 子權限(GET 時額外查)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Policy 同步策略
|
|||
|
|
|
|||
|
|
| 觸發 | 動作 |
|
|||
|
|
|------|------|
|
|||
|
|
| RolePermission 變更 | 該 tenant `LoadPolicy` |
|
|||
|
|
| Permission status 變更(平台) | 全局 `LoadPolicy` |
|
|||
|
|
| 定時 cron(如 5min) | `SyncPolicy` 兜底 |
|
|||
|
|
| Gateway 啟動 | 初始 `LoadPolicy` |
|
|||
|
|
|
|||
|
|
Redis 儲存 Casbin rules:`permission:casbin:rules`(List of JSON `rbac.Rule`)
|
|||
|
|
|
|||
|
|
### 6.8 UseCase 介面
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// --- Casbin 授權(核心)---
|
|||
|
|
type RBACUseCase interface {
|
|||
|
|
Check(ctx context.Context, req *CheckRequest) (*CheckResult, error)
|
|||
|
|
LoadPolicy(ctx context.Context, tenantID string) error
|
|||
|
|
LoadAllPolicies(ctx context.Context) error
|
|||
|
|
SyncPolicy(ctx context.Context, interval time.Duration)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type CheckRequest struct {
|
|||
|
|
TenantID string
|
|||
|
|
UID string
|
|||
|
|
Path string // 實際請求 path
|
|||
|
|
Method string // 實際 HTTP method
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type CheckResult struct {
|
|||
|
|
Allow bool
|
|||
|
|
PermissionName string
|
|||
|
|
PlainCode bool
|
|||
|
|
MatchedRole string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Permission Catalog(平台級)---
|
|||
|
|
type PermissionUseCase interface {
|
|||
|
|
All(ctx context.Context, status *enum.Status) ([]PermissionDTO, error)
|
|||
|
|
FilterAll(ctx context.Context) ([]PermissionDTO, error) // 樹狀過濾 open 節點
|
|||
|
|
Insert(ctx context.Context, req *CreatePermissionRequest) error // 平台 Admin
|
|||
|
|
Update(ctx context.Context, id string, req *UpdatePermissionRequest) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Role(租戶級,B2B 自定義)---
|
|||
|
|
type RoleUseCase interface {
|
|||
|
|
List(ctx context.Context, req *ListRolesRequest) ([]RoleDTO, int64, error)
|
|||
|
|
All(ctx context.Context, tenantID string) ([]RoleDTO, error)
|
|||
|
|
GetByID(ctx context.Context, tenantID, id string) (*RoleDTO, error)
|
|||
|
|
Create(ctx context.Context, req *CreateRoleRequest) error
|
|||
|
|
Update(ctx context.Context, id string, req *UpdateRoleRequest) error
|
|||
|
|
Delete(ctx context.Context, tenantID, id string) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- RolePermission ---
|
|||
|
|
type RolePermissionUseCase interface {
|
|||
|
|
Get(ctx context.Context, roleID string) (enum.Permissions, error) // name → open/close
|
|||
|
|
Create(ctx context.Context, roleID string, perms enum.Permissions) error
|
|||
|
|
Delete(ctx context.Context, roleID string, perms enum.Permissions) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- UserRole ---
|
|||
|
|
type UserRoleUseCase interface {
|
|||
|
|
GetByUID(ctx context.Context, tenantID, uid string) ([]UserRoleDTO, error)
|
|||
|
|
Assign(ctx context.Context, tenantID, uid, roleID string) error
|
|||
|
|
Revoke(ctx context.Context, tenantID, uid, roleID string) error
|
|||
|
|
Replace(ctx context.Context, tenantID, uid string, roleIDs []string) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 外部映射 ---
|
|||
|
|
type RoleMappingUseCase interface {
|
|||
|
|
List(ctx context.Context, tenantID string) ([]RoleMappingDTO, error)
|
|||
|
|
Upsert(ctx context.Context, req *UpsertRoleMappingRequest) error
|
|||
|
|
Delete(ctx context.Context, tenantID, id string) error
|
|||
|
|
SyncFromZitadelClaims(ctx context.Context, req *SyncFromZitadelRequest) error
|
|||
|
|
SyncFromScimGroup(ctx context.Context, req *SyncFromScimGroupRequest) error
|
|||
|
|
SyncFromLDAPGroups(ctx context.Context, req *SyncFromLDAPGroupsRequest) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 聚合查詢(前端菜单)---
|
|||
|
|
type AuthorizationQueryUseCase interface {
|
|||
|
|
GetMyPermissions(ctx context.Context, tenantID, uid string) (enum.Permissions, error)
|
|||
|
|
GetMyRoles(ctx context.Context, tenantID, uid string) ([]string, error)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.9 Middleware 授權流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Request(JWT 已驗證)
|
|||
|
|
1. 取 ctx.tenant_id, ctx.uid
|
|||
|
|
2. userRoleUC.GetByUID → []Role.Name
|
|||
|
|
3. 對每個 roleName:
|
|||
|
|
rbacUC.Check(tenantID, uid, roleName, r.URL.Path, r.Method)
|
|||
|
|
4. 任一 Allow=true → 通過,注入 ctx.permission_name, ctx.plain_code
|
|||
|
|
5. 全否 → 403 Forbidden
|
|||
|
|
6. SuperAdmin uid → 直接通過
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Logic 層可讀 `ctx.plain_code` 決定是否回傳明碼欄位(沿用 permission-server 的 `PlainCode` 模式)。
|
|||
|
|
|
|||
|
|
### 6.10 外部 Group / Role 映射
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type RoleMapping struct {
|
|||
|
|
TenantID string
|
|||
|
|
ExternalSource enum.RoleSource // zitadel | ldap | scim
|
|||
|
|
ExternalKey string // ZITADEL role / LDAP group DN / SCIM group id
|
|||
|
|
InternalRole string // 租戶 Role.Name
|
|||
|
|
}
|
|||
|
|
// Index: { tenant_id, external_source, external_key } unique
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 來源 | ExternalKey 範例 | 映射到 |
|
|||
|
|
|------|------------------|--------|
|
|||
|
|
| ZITADEL | `org_admin` | `tenant_admin` |
|
|||
|
|
| LDAP (AD) | `CN=CloudEP-Admins,OU=Groups,DC=acme,DC=com` | 租戶自訂 Role.Name |
|
|||
|
|
| LDAP (OpenLDAP) | `cn=admins,ou=groups,dc=acme,dc=com` | 租戶自訂 Role.Name |
|
|||
|
|
| SCIM Group | `group-uuid-xxx` | 租戶自訂 Role.Name |
|
|||
|
|
|
|||
|
|
由 B2B 租戶管理員在後台設定(需命中 `permission.role.write` 對應 API)。
|
|||
|
|
|
|||
|
|
### 6.11 權限變更生效
|
|||
|
|
|
|||
|
|
| 事件 | 動作 |
|
|||
|
|
|------|------|
|
|||
|
|
| RolePermission Create/Delete | `LoadPolicy(tenant_id)` |
|
|||
|
|
| Role Create/Update/Delete | `LoadPolicy(tenant_id)` |
|
|||
|
|
| UserRole Assign/Revoke | 可選 `INCR auth:gen`(立即踢下线) |
|
|||
|
|
| SCIM / LDAP Group 變更 | 更新 user_roles → `LoadPolicy` + `INCR auth_gen` |
|
|||
|
|
| Permission status 變更(平台) | `LoadAllPolicies()` |
|
|||
|
|
|
|||
|
|
### 6.12 B2C vs B2B 權限策略
|
|||
|
|
|
|||
|
|
| 租戶類型 | Role 自定義 | Permission 勾選 |
|
|||
|
|
|----------|-------------|-----------------|
|
|||
|
|
| **B2C** | 不可(只用 seed 模板) | 固定 |
|
|||
|
|
| **B2B** | **完全自定義** | 從全局 Catalog 自由勾選 |
|
|||
|
|
| **Hybrid** | B2B 部分可自定義 | 依 tenant 設定 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. API 規劃
|
|||
|
|
|
|||
|
|
檔案:`generate/api/`
|
|||
|
|
|
|||
|
|
### 7.1 auth.api(公開)
|
|||
|
|
|
|||
|
|
| Method | Path | 說明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| POST | `/api/v1/auth/token/exchange` | ZITADEL token → CloudEP JWT |
|
|||
|
|
| POST | `/api/v1/auth/token/refresh` | 刷新 JWT |
|
|||
|
|
| POST | `/api/v1/auth/logout` | 登出(jti 黑名單) |
|
|||
|
|
|
|||
|
|
### 7.2 member.api(需 JWT + Casbin)
|
|||
|
|
|
|||
|
|
| Method | Path | Casbin 命中 Permission(示例) |
|
|||
|
|
|--------|------|-------------------------------|
|
|||
|
|
| GET | `/api/v1/members/me` | `member.info.select` |
|
|||
|
|
| PATCH | `/api/v1/members/me` | `member.info.update` |
|
|||
|
|
| GET | `/api/v1/members` | `member.admin.list` |
|
|||
|
|
| GET | `/api/v1/members/:uid` | `member.admin.read` |
|
|||
|
|
| PATCH | `/api/v1/members/:uid` | `member.admin.update` |
|
|||
|
|
| PATCH | `/api/v1/members/:uid/status` | `member.admin.status` |
|
|||
|
|
|
|||
|
|
> 授權由 **Casbin 比對實際 path + method** 決定,非硬編碼 permission 字串。
|
|||
|
|
|
|||
|
|
### 7.3 permission.api(需 JWT + Casbin)
|
|||
|
|
|
|||
|
|
| Method | Path | 說明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| GET | `/api/v1/permissions/catalog` | 全局 Permission Tree(open 節點) |
|
|||
|
|
| GET | `/api/v1/permissions/me` | 當前用户的 permission name → status map |
|
|||
|
|
| GET | `/api/v1/permissions/roles` | 列出租戶 Role |
|
|||
|
|
| POST | `/api/v1/permissions/roles` | 建立 Role(B2B) |
|
|||
|
|
| PUT | `/api/v1/permissions/roles/:id` | 更新 Role |
|
|||
|
|
| DELETE | `/api/v1/permissions/roles/:id` | 刪除 Role |
|
|||
|
|
| GET | `/api/v1/permissions/roles/:id/permissions` | 取得 Role 勾選的 Permission |
|
|||
|
|
| PUT | `/api/v1/permissions/roles/:id/permissions` | 更新 Role 勾選(PermissionTree 驗證 + 補 parent) |
|
|||
|
|
| GET | `/api/v1/permissions/users/:uid/roles` | 查用户角色 |
|
|||
|
|
| POST | `/api/v1/permissions/users/:uid/roles` | 指派 Role `{ "role_id": "..." }` |
|
|||
|
|
| DELETE | `/api/v1/permissions/users/:uid/roles/:role_id` | 撤銷 Role |
|
|||
|
|
| GET | `/api/v1/permissions/role-mappings` | 外部 Group 映射列表 |
|
|||
|
|
| PUT | `/api/v1/permissions/role-mappings` | 新增/更新映射 |
|
|||
|
|
| POST | `/api/v1/permissions/policy/reload` | 手動觸發 LoadPolicy(平台 Admin) |
|
|||
|
|
|
|||
|
|
### 7.4 tenant.api(平台 / 租戶 Admin)
|
|||
|
|
|
|||
|
|
| Method | Path | Casbin 命中 Permission(示例) |
|
|||
|
|
|--------|------|-------------------------------|
|
|||
|
|
| POST | `/api/v1/admin/tenants` | `system.tenant.create` |
|
|||
|
|
| GET | `/api/v1/admin/tenants/:tenant_id` | `tenant.read` |
|
|||
|
|
| PUT | `/api/v1/admin/tenants/:tenant_id/ldap` | `tenant.ldap.write` |
|
|||
|
|
| POST | `/api/v1/admin/tenants/:tenant_id/directory-sync` | `tenant.sync.trigger` |
|
|||
|
|
|
|||
|
|
### 7.5 scim.api(SCIM Bearer Token,非 JWT)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
/scim/v2/tenants/{tenant_id}/Users
|
|||
|
|
/scim/v2/tenants/{tenant_id}/Groups
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
認證:`Authorization: Bearer {tenant_scim_token}`(hash 存於 tenant 設定)
|
|||
|
|
|
|||
|
|
SCIM 請求授權可透過 tenant 級 token 隱含 `scim:*`,或細分 scim users/groups permission。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Middleware 鏈
|
|||
|
|
|
|||
|
|
### 8.1 一般受保護 API
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Request
|
|||
|
|
→ go-zero JWT 驗簽
|
|||
|
|
→ JwtRevokeMiddleware(jti 黑名單 + auth_gen)
|
|||
|
|
→ TenantContextMiddleware(校驗 tenant_id 一致)
|
|||
|
|
→ CasbinRBACMiddleware(role names × path × method → Allow)
|
|||
|
|
→ handler → logic → usecase
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 CasbinRBACMiddleware
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 伪代码
|
|||
|
|
roles := userRoleUC.GetRoleNames(ctx, tenantID, uid)
|
|||
|
|
for _, roleName := range roles {
|
|||
|
|
result, _ := rbacUC.Check(ctx, &CheckRequest{
|
|||
|
|
TenantID: tenantID, UID: uid,
|
|||
|
|
RoleName: roleName, Path: r.URL.Path, Method: r.Method,
|
|||
|
|
})
|
|||
|
|
if result.Allow {
|
|||
|
|
ctx = withPermissionName(ctx, result.PermissionName)
|
|||
|
|
ctx = withPlainCode(ctx, result.PlainCode)
|
|||
|
|
next(w, r)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// SuperAdmin bypass
|
|||
|
|
if uid == cfg.PlatformSuperAdminUID { next(w, r); return }
|
|||
|
|
httpx.Error(w, forbidden)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.3 SCIM API
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Request
|
|||
|
|
→ ScimAuthMiddleware(tenant_scim_token)
|
|||
|
|
→ TenantContextMiddleware
|
|||
|
|
→ handler
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.4 Logic 層補充授權
|
|||
|
|
|
|||
|
|
Casbin 處理 **API 級** 授權。Logic 內可追加 **資源級** 判斷:
|
|||
|
|
|
|||
|
|
- `member.info.select` vs 查他人:若 path 含 `:uid` 且 uid ≠ caller,需命中 `member.admin.read`
|
|||
|
|
- `PlainCode`:Logic 讀 `ctx.plain_code`,決定是否回傳明碼欄位
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 核心流程
|
|||
|
|
|
|||
|
|
### 9.1 登入 / 換票
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Client → ZITADEL OIDC Login(含 LDAP IdP)
|
|||
|
|
Client → POST /auth/token/exchange { tenant_slug, id_token }
|
|||
|
|
1. zitadel.VerifyIDToken
|
|||
|
|
2. tenant.ResolveBySlug → 校驗 org_id
|
|||
|
|
3. member.EnsureMember → uid(如 ACME-ODWXGYBK)
|
|||
|
|
4. permission.SyncFromZitadelClaims → user_roles
|
|||
|
|
5. auth.IssueTokenPair(role names 逗號分隔, auth_gen)
|
|||
|
|
Client ← { access_token, refresh_token, uid }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.2 受保護 API
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Client → GET /api/v1/members/me (Bearer access_jwt)
|
|||
|
|
1. JWT + 黑名單 + auth_gen
|
|||
|
|
2. CasbinRBACMiddleware → Check(role, "/api/v1/members/me", "GET")
|
|||
|
|
3. member.GetByUID
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.3 B2B 自定義 Role + 勾選 Permission
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Tenant Admin → PUT /permissions/roles/{id}/permissions
|
|||
|
|
{ "member.admin.list": "open", "member.admin.read": "open" }
|
|||
|
|
→ RolePermissionUC.Create
|
|||
|
|
→ PermissionTree.getFullParentPermissionIDs(自動補 parent)
|
|||
|
|
→ RBACUC.LoadPolicy(tenant_id)
|
|||
|
|
|
|||
|
|
Tenant Admin → POST /permissions/users/{uid}/roles
|
|||
|
|
{ "role_id": "..." }
|
|||
|
|
→ UserRoleUC.Assign
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.4 停權
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Admin → PATCH /members/:uid/status { status: "suspended" }
|
|||
|
|
→ member.UpdateStatus
|
|||
|
|
→ auth.RevokeAllForUser(INCR auth_gen)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. LDAP 與 SCIM
|
|||
|
|
|
|||
|
|
### 10.1 三條 Provisioning 路徑
|
|||
|
|
|
|||
|
|
| 路徑 | 適用 | 說明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| **SCIM → ZITADEL → Gateway** | 有 HR / Entra ID / Okta | 企業推送用户 |
|
|||
|
|
| **ZITADEL LDAP IdP** | 用戶登入時 JIT | 首次登入建立 member |
|
|||
|
|
| **Directory Sync Worker** | 無 SCIM 的 AD / OpenLDAP | 定時同步 + 離職偵測 |
|
|||
|
|
|
|||
|
|
### 10.2 LDAP 設定(AD + OpenLDAP)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type TenantLDAPConfig struct {
|
|||
|
|
TenantID string
|
|||
|
|
Type string // "ad" | "openldap"
|
|||
|
|
Host string
|
|||
|
|
Port int
|
|||
|
|
UseTLS bool
|
|||
|
|
BaseDN string
|
|||
|
|
BindDN string // encrypted
|
|||
|
|
BindPassword string // encrypted
|
|||
|
|
UserFilter string
|
|||
|
|
GroupFilter string
|
|||
|
|
AttrMap LDAPAttrMap
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type LDAPAttrMap struct {
|
|||
|
|
Username string // AD: sAMAccountName / LDAP: uid
|
|||
|
|
Email string // mail
|
|||
|
|
DisplayName string // displayName / cn
|
|||
|
|
Phone string // telephoneNumber
|
|||
|
|
ExternalID string // objectGUID / entryUUID
|
|||
|
|
Groups string // memberOf
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 10.3 SCIM
|
|||
|
|
|
|||
|
|
- `externalId` = Member UID(`ACME-ODWXGYBK`)
|
|||
|
|
- SCIM Groups PATCH → `permission.SyncFromScimGroup`
|
|||
|
|
- SCIM deactivate → `member.suspended` + `auth.RevokeAllForUser`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11. 可讀 UID 設計
|
|||
|
|
|
|||
|
|
### 11.1 格式
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
{TenantPrefix}-{Body}
|
|||
|
|
|
|||
|
|
範例:ACME-ODWXGYBK
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `TenantPrefix`:2~4 位大寫(來自 tenant.UIDPrefix / slug 縮寫)
|
|||
|
|
- `Body`:6~8 位大寫字母(自訂字母表,排除易混淆字元)
|
|||
|
|
- **不要** UUID、不要純數字、不要 base64 亂碼
|
|||
|
|
|
|||
|
|
### 11.2 字母表(沿用 invited_code 風格)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
O D W X Y G B C H E F A Q I J L M N Z K P V R S T
|
|||
|
|
(25 字符,無 0/O/1/I 混淆問題)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 11.3 產生(Bucket 取號,支援單租戶 50 萬)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Redis: member:seq:{tenant_id}
|
|||
|
|
每次 INCRBY 500 取一個 bucket
|
|||
|
|
bucket 內 sequential 編碼 → Body
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
唯一索引:`{ tenant_id, uid }` unique
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 12. 資料模型與索引
|
|||
|
|
|
|||
|
|
### 12.1 Collections
|
|||
|
|
|
|||
|
|
| Collection | 模組 | 說明 |
|
|||
|
|
|------------|------|------|
|
|||
|
|
| `members` | member | Profile |
|
|||
|
|
| `identities` | member | zitadel_sub ↔ uid |
|
|||
|
|
| `tenants` | member | 租戶 metadata |
|
|||
|
|
| `tenant_ldap_configs` | member | LDAP 同步設定(加密) |
|
|||
|
|
| `permissions` | permission | 全局 Permission Tree(平台 seed) |
|
|||
|
|
| `roles` | permission | 租戶 Role(`tenant_id` + `name`) |
|
|||
|
|
| `role_permissions` | permission | Role ↔ Permission ID |
|
|||
|
|
| `user_roles` | permission | uid ↔ Role(支援多角色) |
|
|||
|
|
| `role_mappings` | permission | 外部 Group ↔ Role.Name |
|
|||
|
|
|
|||
|
|
### 12.2 主要索引
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// members
|
|||
|
|
{ tenant_id: 1, uid: 1 } // unique
|
|||
|
|
{ tenant_id: 1, zitadel_user_id: 1 } // unique
|
|||
|
|
{ tenant_id: 1, member_status: 1, create_at: -1 }
|
|||
|
|
|
|||
|
|
// identities
|
|||
|
|
{ tenant_id: 1, zitadel_user_id: 1 } // unique
|
|||
|
|
{ tenant_id: 1, uid: 1 }
|
|||
|
|
{ tenant_id: 1, external_id: 1 }
|
|||
|
|
|
|||
|
|
// permissions(全局)
|
|||
|
|
{ name: 1 } // unique
|
|||
|
|
{ parent: 1, status: 1 }
|
|||
|
|
{ http_path: 1, http_method: 1 } // sparse
|
|||
|
|
|
|||
|
|
// roles
|
|||
|
|
{ tenant_id: 1, name: 1 } // unique
|
|||
|
|
{ tenant_id: 1, status: 1 }
|
|||
|
|
|
|||
|
|
// role_permissions
|
|||
|
|
{ role_id: 1, permission_id: 1 } // unique
|
|||
|
|
|
|||
|
|
// user_roles
|
|||
|
|
{ tenant_id: 1, uid: 1, role_id: 1 } // unique
|
|||
|
|
{ tenant_id: 1, uid: 1 }
|
|||
|
|
|
|||
|
|
// role_mappings
|
|||
|
|
{ tenant_id: 1, external_source: 1, external_key: 1 } // unique
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 12.3 分片鍵(100 萬+)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Shard Key: { tenant_id: 1, uid: 1 }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
單租戶 50 萬會集中在同一 chunk,MongoDB 仍可承受;若預期單租戶千萬級再評估 hash 二次分片。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 13. Redis Key 命名
|
|||
|
|
|
|||
|
|
### auth(`internal/model/auth/redis.go`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
auth:jwt:bl:{jti} # 單 token 黑名單,TTL = 剩餘壽命
|
|||
|
|
auth:gen:{tenant_id}:{uid} # 批量失效代號
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### member(`internal/model/member/redis.go`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
member:profile:{tenant_id}:{uid} # profile cache,TTL 5~15min
|
|||
|
|
member:sub:{tenant_id}:{sub} # zitadel_sub → uid,TTL 1h
|
|||
|
|
member:seq:{tenant_id} # UID bucket counter
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### permission(`internal/model/permission/redis.go`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
permission:casbin:rules # Casbin policy rules(List of JSON)
|
|||
|
|
permission:tree:open # 可選:open 節點 cache
|
|||
|
|
perm:role_perms:{tenant_id}:{role_id} # role → permission names,TTL 30min
|
|||
|
|
perm:user_roles:{tenant_id}:{uid} # uid → role names,TTL 5min
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 14. 規模與性能(100 萬+ / 單租戶 50 萬)
|
|||
|
|
|
|||
|
|
| 項目 | 策略 |
|
|||
|
|
|------|------|
|
|||
|
|
| Gateway | 無狀態,水平擴展 |
|
|||
|
|
| MongoDB | Sharding + Replica Set,讀走 secondary |
|
|||
|
|
| ListMembers | Cursor 分頁,禁止 deep offset |
|
|||
|
|
| Authorize | Casbin EnforceEx(内存 + Redis policy) |
|
|||
|
|
| LoadPolicy | 变更时增量;cron 5min 全量兜底 |
|
|||
|
|
| JWT → UID | Redis cache 1h |
|
|||
|
|
| Directory Sync | 500 users / batch,rate limit ZITADEL API |
|
|||
|
|
| Access Token TTL | 15min(降低撤銷窗口) |
|
|||
|
|
|
|||
|
|
### 容量粗估
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
100 萬 members × ~2KB ≈ 2GB(不含 index)
|
|||
|
|
indexes ≈ 1~2GB
|
|||
|
|
→ 單集群可承受,建議 3 node replica set 起跳
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 15. 目錄結構
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
gateway/
|
|||
|
|
├── generate/api/
|
|||
|
|
│ ├── auth.api
|
|||
|
|
│ ├── member.api
|
|||
|
|
│ ├── permission.api
|
|||
|
|
│ ├── tenant.api
|
|||
|
|
│ └── scim.api
|
|||
|
|
│
|
|||
|
|
├── internal/
|
|||
|
|
│ ├── middleware/
|
|||
|
|
│ │ ├── jwt_revoke.go
|
|||
|
|
│ │ ├── tenant_context.go
|
|||
|
|
│ │ ├── require_permission.go
|
|||
|
|
│ │ └── scim_auth.go
|
|||
|
|
│ │
|
|||
|
|
│ ├── library/
|
|||
|
|
│ │ ├── zitadel/
|
|||
|
|
│ │ │ ├── oidc.go
|
|||
|
|
│ │ │ └── management.go
|
|||
|
|
│ │ ├── ldap/
|
|||
|
|
│ │ │ ├── client.go
|
|||
|
|
│ │ │ └── attrmap.go
|
|||
|
|
│ │ ├── casbin/ # Enforcer 初始化 helper
|
|||
|
|
│ │ └── uid/
|
|||
|
|
│ │ ├── encode.go
|
|||
|
|
│ │ └── generator.go
|
|||
|
|
│ │
|
|||
|
|
│ ├── model/
|
|||
|
|
│ │ ├── auth/
|
|||
|
|
│ │ │ └── ...
|
|||
|
|
│ │ ├── member/
|
|||
|
|
│ │ │ └── ...
|
|||
|
|
│ │ └── permission/
|
|||
|
|
│ │ ├── entity/
|
|||
|
|
│ │ │ ├── permission.go
|
|||
|
|
│ │ │ ├── role.go
|
|||
|
|
│ │ │ ├── user_role.go
|
|||
|
|
│ │ │ ├── role_permission.go
|
|||
|
|
│ │ │ └── role_mapping.go
|
|||
|
|
│ │ ├── enum/
|
|||
|
|
│ │ │ ├── status.go
|
|||
|
|
│ │ │ └── permission_type.go
|
|||
|
|
│ │ ├── repository/
|
|||
|
|
│ │ │ ├── permission.go
|
|||
|
|
│ │ │ ├── role.go
|
|||
|
|
│ │ │ ├── user_role.go
|
|||
|
|
│ │ │ ├── role_permission.go
|
|||
|
|
│ │ │ ├── role_mapping.go
|
|||
|
|
│ │ │ └── casbin_redis_adapter.go # 沿用 permission-server
|
|||
|
|
│ │ ├── usecase/
|
|||
|
|
│ │ │ ├── permission_tree.go # 沿用 permission-server
|
|||
|
|
│ │ │ ├── rbac.go # Casbin LoadPolicy / Check
|
|||
|
|
│ │ │ ├── permission.go
|
|||
|
|
│ │ │ ├── role.go
|
|||
|
|
│ │ │ ├── role_permission.go
|
|||
|
|
│ │ │ ├── user_role.go
|
|||
|
|
│ │ │ ├── role_mapping.go
|
|||
|
|
│ │ │ └── authorization_query.go
|
|||
|
|
│ │ ├── rbac/
|
|||
|
|
│ │ │ └── rule.go # Casbin Rule struct
|
|||
|
|
│ │ ├── config/
|
|||
|
|
│ │ ├── errors.go
|
|||
|
|
│ │ ├── redis.go
|
|||
|
|
│ │ └── mock/
|
|||
|
|
│ │
|
|||
|
|
│ └── worker/
|
|||
|
|
│ ├── directory_sync/
|
|||
|
|
│ └── policy_sync/ # 可選:定時 LoadPolicy
|
|||
|
|
│
|
|||
|
|
├── etc/
|
|||
|
|
│ ├── gateway.yaml
|
|||
|
|
│ └── rbac.conf # Casbin 模型(沿用 permission-server)
|
|||
|
|
│
|
|||
|
|
└── docs/
|
|||
|
|
├── model.md
|
|||
|
|
└── identity-member-design.md # 本文件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 16. 設定檔
|
|||
|
|
|
|||
|
|
`etc/gateway.yaml` 擴充草案:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
Name: gateway
|
|||
|
|
Host: 0.0.0.0
|
|||
|
|
Port: 8888
|
|||
|
|
|
|||
|
|
Auth:
|
|||
|
|
AccessSecret: ${JWT_ACCESS_SECRET}
|
|||
|
|
AccessExpire: 900
|
|||
|
|
|
|||
|
|
RefreshAuth:
|
|||
|
|
AccessSecret: ${JWT_REFRESH_SECRET}
|
|||
|
|
AccessExpire: 604800
|
|||
|
|
|
|||
|
|
Zitadel:
|
|||
|
|
Issuer: https://id.example.com
|
|||
|
|
ClientID: ${ZITADEL_CLIENT_ID}
|
|||
|
|
JWKSUrl: https://id.example.com/oauth/v2/keys
|
|||
|
|
MgmtURL: https://id.example.com/management/v1
|
|||
|
|
MgmtToken: ${ZITADEL_MGMT_TOKEN}
|
|||
|
|
|
|||
|
|
Mongo:
|
|||
|
|
# 見 internal/library/mongo 設定
|
|||
|
|
|
|||
|
|
Redis:
|
|||
|
|
Host: 127.0.0.1:6379
|
|||
|
|
Type: node
|
|||
|
|
|
|||
|
|
Member:
|
|||
|
|
DefaultLanguage: zh-tw
|
|||
|
|
DefaultCurrency: TWD
|
|||
|
|
|
|||
|
|
Permission:
|
|||
|
|
RBACModelPath: etc/rbac.conf
|
|||
|
|
PolicySyncInterval: 5m
|
|||
|
|
PlatformSuperAdminUID: ${PLATFORM_SUPER_ADMIN_UID} # 對應舊 GodDog bypass
|
|||
|
|
CacheTTLSeconds: 300
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 17. 實施順序
|
|||
|
|
|
|||
|
|
| 階段 | 內容 | 產出 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| **P0** | 目錄骨架、entity、redis key、config | 可啟動、可連 Mongo/Redis |
|
|||
|
|
| **P1** | UID generator + EnsureMember + token exchange | 可登入取得 JWT + 可讀 UID |
|
|||
|
|
| **P2** | JWT middleware + jti 黑名單 + auth_gen + logout/refresh | 完整 Token 生命週期 |
|
|||
|
|
| **P3** | Permission seed + PermissionTree + Casbin RBAC + Redis Adapter | 可 LoadPolicy / Check |
|
|||
|
|
| **P4** | member profile API + 預設 Role seed + CasbinRBACMiddleware | `/members/me` + API 授權生效 |
|
|||
|
|
| **P5** | RolePermission + UserRole + B2B Role CRUD + Permission 勾選 API | 租戶完全自定義 |
|
|||
|
|
| **P6** | Tenant 建立 + ZITADEL CreateOrg + LDAP 設定 | 多租戶 |
|
|||
|
|
| **P7** | Directory Sync Worker(AD + OpenLDAP) | 企業目錄同步 |
|
|||
|
|
| **P8** | SCIM 2.0 endpoint + Group 映射 | 企業 provisioning |
|
|||
|
|
| **P9** | 壓測(100 萬 seed)、sharding、調優 | 上線準備 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 18. 待決策事項
|
|||
|
|
|
|||
|
|
| # | 議題 | 選項 | 備註 |
|
|||
|
|
|---|------|------|------|
|
|||
|
|
| 1 | UID 格式 | `ACME-ODWXGYBK` vs 純 `ODWXGYBK` | 建議带前缀 |
|
|||
|
|
| 2 | SCIM 路由 | `/scim/v2/tenants/{id}` vs 獨立子域名 | |
|
|||
|
|
| 3 | ZITADEL 部署 | Self-hosted vs Cloud | 影響 LDAP 網路 |
|
|||
|
|
| 4 | 權限變更生效 | 仅清 cache vs 强制 INCR auth_gen | 建议重要變更 INCR |
|
|||
|
|
| 5 | B2C 租戶 | 是否允許自定義 Role | 建议 B2C 只用 seed 模板,B2B 完全自定義 |
|
|||
|
|
| 6 | Refresh Token | 是否輪換 + 舊 jti 黑名單 | 建议輪換 |
|
|||
|
|
| 7 | UserRole | 一 user 多 role vs 單 role | 建议多 role(Casbin 逐一 Check) |
|
|||
|
|
| 8 | PlatformSuperAdminUID | 固定 uid vs 平台 Role | 建议保留 bypass uid + 后续可移除 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附錄 A:ServiceContext 組裝草案
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ServiceContext struct {
|
|||
|
|
Config config.Config
|
|||
|
|
Validator validate.Validate
|
|||
|
|
|
|||
|
|
// library clients
|
|||
|
|
Zitadel *zitadel.Client
|
|||
|
|
|
|||
|
|
// usecases
|
|||
|
|
AuthUC authusecase.TokenUseCase
|
|||
|
|
MemberProvUC memberusecase.ProvisioningUseCase
|
|||
|
|
MemberProfileUC memberusecase.ProfileUseCase
|
|||
|
|
MemberAdminUC memberusecase.AdminUseCase
|
|||
|
|
TenantUC memberusecase.TenantUseCase
|
|||
|
|
ScimUC memberusecase.ScimUseCase
|
|||
|
|
|
|||
|
|
// permission usecases(對齊 permission-server 拆分)
|
|||
|
|
PermRBACUC permusecase.RBACUseCase
|
|||
|
|
PermUC permusecase.PermissionUseCase
|
|||
|
|
RoleUC permusecase.RoleUseCase
|
|||
|
|
RolePermUC permusecase.RolePermissionUseCase
|
|||
|
|
UserRoleUC permusecase.UserRoleUseCase
|
|||
|
|
RoleMappingUC permusecase.RoleMappingUseCase
|
|||
|
|
AuthQueryUC permusecase.AuthorizationQueryUseCase
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附錄 C:permission-server 遷移對照(程式碼級)
|
|||
|
|
|
|||
|
|
| permission-server 檔案 | Gateway 目標 | 遷移方式 |
|
|||
|
|
|------------------------|--------------|----------|
|
|||
|
|
| `pkg/usecase/permission_tree.go` | `model/permission/usecase/permission_tree.go` | 幾乎原樣搬移 |
|
|||
|
|
| `pkg/usecase/casbin_redis_rbac.go` | `model/permission/usecase/rbac.go` | 加 `tenant_id` 過濾 |
|
|||
|
|
| `pkg/repository/casbin_redis_adapter.go` | `model/permission/repository/casbin_redis_adapter.go` | 原樣搬移 |
|
|||
|
|
| `pkg/domain/rbac/rule.go` | `model/permission/rbac/rule.go` | 原樣搬移 |
|
|||
|
|
| `etc/rbac.conf` | `etc/rbac.conf` | 原樣搬移 |
|
|||
|
|
| `pkg/usecase/role.go` | `model/permission/usecase/role.go` | `ClientID`→`TenantID` |
|
|||
|
|
| `pkg/usecase/role_permission.go` | `model/permission/usecase/role_permission.go` | 原樣搬移 |
|
|||
|
|
| `pkg/usecase/user_role.go` | `model/permission/usecase/user_role.go` | 改支援多角色 |
|
|||
|
|
| `pkg/usecase/token.go` | **`model/auth/usecase/token.go`** | 不在 permission 模組 |
|
|||
|
|
| `generate/database/seeders/*_permission*` | `generate/database/seeders/` 或 Mongo seed | 改為 Gateway seed job |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附錄 B:與 model.md 的關係
|
|||
|
|
|
|||
|
|
- 本文件:**做什麼**(架構、流程、API、權限模型)
|
|||
|
|
- [model.md](./model.md):**怎麼寫**(entity / repository / usecase 程式碼規範)
|
|||
|
|
|
|||
|
|
實作時兩份文件搭配使用。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 修訂紀錄
|
|||
|
|
|
|||
|
|
| 日期 | 版本 | 說明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 2026-05-19 | 0.1.0 | 初稿:auth + member + permission(B2B 自定義)+ ZITADEL/LDAP/SCIM |
|
|||
|
|
| 2026-05-19 | 0.2.0 | 對齊 app-cloudep-permission-server:Casbin RBAC、Permission Tree、Role/RolePermission |
|