# Member Service

# Member Service — SRS/SDD Document

| Version | Version Date | Editor | Memo |
|---------|--------------|--------|------|
| 1.0.0 | 2026/05/21 | Gateway Team | 初版：Gateway Member 模組 SDD |

---

## 1. Introduction

### 1.1 Purpose

Member 模組為 TianTing Gateway 的**會員核心領域層**，管理多租戶下的 Tenant（租戶）、Member（會員 profile）、Identity（外部身份對映），以及 UID 生成、業務 Email/Phone OTP 驗證、TOTP step-up MFA、重發／每日配額等原子業務原語。

### 1.2 Scope

**範圍內：**

- 租戶建立與 slug 解析
- 會員生命週期（unverified → active → suspended → deleted）
- 外部身份 Provision（OIDC / LDAP / SCIM）
- Profile 讀寫與業務 contact 驗證標記
- OTP challenge（bcrypt + Redis）
- TOTP 綁定／驗證／備援碼（AES-GCM 保護 secret）
- 可讀 UID 序號生成

**範圍外：**

- 密碼驗證、JWT 簽發 → Auth 模組 + ZITADEL
- 通知寄送 → Notification 模組（logic 層編排）
- RBAC → Permission 模組

**架構原則：** usecase **不可**呼叫其他 usecase；多步流程由 logic 層編排。

### 1.3 Definitions, Acronyms, and Abbreviation

| 縮寫 | 說明 |
|------|------|
| **Tenant** | 租戶，B2B 客戶組織單位 |
| **Member** | 租戶範圍內的會員 profile |
| **Identity** | 外部 ID（LDAP/SCIM external_id 或 zitadel_sub）→ UID 對映 |
| **UID** | 可讀主鍵，格式 `{UIDPrefix}-{Sequence}`（如 `ACME-10000003`） |
| **OTP** | 一次性數字驗證碼（Email/Phone 業務驗證） |
| **TOTP** | RFC 6238 時間型 OTP（Google Authenticator） |
| **Origin** | 會員來源：platform_native / oidc / ldap / scim |

### 1.4 Technologies to be used

| 項目 | 技術 |
|------|------|
| Application Language | Go 1.22+ |
| Framework | go-zero |
| Cache | Redis（OTP、TOTP enroll、UID seq、verify rate） |
| Database | MongoDB |
| Crypto | bcrypt（OTP）、AES-GCM（TOTP secret）、RFC 6238 TOTP |
| API Codegen | goctl / `.api` 檔 |

### 1.5 Overview

Member 模組提供 7 個 atomic UseCase，由 `NewModuleFromParam` 依 Mongo / Redis / TOTP KEK 條件組裝：

| UseCase | 條件 |
|---------|------|
| OTP、VerifyRate | Redis 必填 |
| Profile、Lifecycle、Tenant、Provisioning | Mongo 設定後 |
| TOTP | Mongo + `TOTP.SecretKEK` 設定後 |

---

## 2. System Overview

Member 模組是多租戶身份系統的資料核心：

- 每個 Member 必屬一個 Tenant
- UID 以 `{TenantUIDPrefix}-{Sequence}` 生成，序號從 10,000,000 起跳
- 平台註冊走 `unverified → active`；OIDC/LDAP/SCIM 首登直接 `active`
- 業務 Email/Phone 驗證與 TOTP 為 step-up MFA 能力

對外透過 `/api/v1/members/*` REST API 暴露（profile、verification、TOTP）。

---

## 3. System Architecture

### 3.1 System Architecture

```mermaid
flowchart TB
    subgraph Client["Client"]
        App[Frontend / API Client]
    end

    subgraph Gateway["Gateway"]
        Handler["handler/member"]
        Logic["logic/member<br/>(orchestration)"]
        subgraph MemberModule["internal/model/member"]
            UC["usecase/ (7 atomic)"]
            Repo["repository/"]
            TOTP["totp/ (RFC 6238)"]
        end
    end

    subgraph Storage["Storage"]
        Mongo[(MongoDB)]
        Redis[(Redis)]
    end

    subgraph Sibling["Sibling Modules"]
        Auth[auth]
        Notif[notification]
    end

    App --> Handler
    Handler --> Logic
    Logic --> UC
    Logic --> Notif
    Auth --> UC
    UC --> Repo
    UC --> TOTP
    Repo --> Mongo
    Repo --> Redis
```

### 3.2 Decomposition Description

| 套件 | 職責 |
|------|------|
| `config/` | OTP / TOTP / Registration 設定 |
| `domain/entity/` | Member、Tenant、Identity Mongo doc |
| `domain/enum/` | MemberStatus、MemberOrigin、OTPPurpose、TenantStatus |
| `domain/repository/` | 7 個 repository 介面 |
| `domain/usecase/` | 7 個 usecase 介面 + DTO |
| `domain/redis.go` | Redis key helper（禁止字串拼接） |
| `repository/` | Mongo + Redis 實作 |
| `totp/` | RFC 6238 純函式（secret、verify、otpauth URL） |
| `usecase/` | 實作 + module factory + mapper |

### 3.3 Member Lifecycle

```mermaid
stateDiagram-v2
    [*] --> unverified: Lifecycle.CreateUnverified<br/>(platform 註冊)
    [*] --> active: Provisioning.Ensure*<br/>(OIDC/LDAP/SCIM)

    unverified --> active: Activate (OTP 通過)
    unverified --> deleted: AbortPending (註冊逾時)

    active --> suspended: Suspend
    suspended --> active: Reactivate
    active --> deleted: SoftDelete
    suspended --> deleted: SoftDelete
    deleted --> [*]
```

### 3.4 UID Generation

```mermaid
sequenceDiagram
    participant UC as Lifecycle / Provisioning
    participant Gen as UIDGenerator
    participant R as Redis

    UC->>Gen: Next(tenant, uidPrefix)
    Gen->>R: INCR member:seq:{tenant}
    alt seq == 1 (首次)
        Gen->>R: INCRBY (UIDSequenceStart - 1)
    end
    Gen-->>UC: "{PREFIX}-{seq}"
```

- `UIDSequenceStart = 10_000_000`
- `UIDPrefix` 限制 2~4 個大寫字母

### 3.5 OTP / TOTP

**OTP：** bcrypt hash 存 Redis challenge；Verify 成功後立即刪除（一次性）。

**TOTP：** secret 以 AES-GCM cipher 存 Mongo；enroll 階段 staged secret 存 Redis（TTL 600s）；VerifyCode 含 replay 保護（timestep 去重）。

---

## 4. Data Design

### 4.1 Data Dictionary

#### tenants（MongoDB）

| Field | Type | Comment | Index |
|-------|------|---------|-------|
| _id | ObjectId | PK | PK |
| tenant_id | String | 租戶 ID | — |
| slug | String | URL slug | Unique |
| name | String | 顯示名稱 | — |
| uid_prefix | String | UID 前綴（2-4 大寫） | Unique |
| status | String | active / suspended | — |
| org_id | String | 外部 org ID | — |
| create_at | Int64 | 建立時間 ms | — |
| update_at | Int64 | 更新時間 ms | — |

#### members（MongoDB）

| Field | Type | Comment | Index |
|-------|------|---------|-------|
| _id | ObjectId | PK | PK |
| tenant_id | String | 租戶 ID | Unique(tenant_id, uid) |
| uid | String | 可讀 UID | Unique(tenant_id, uid) |
| zitadel_user_id | String | ZITADEL sub | Unique(tenant_id, zitadel_user_id) sparse |
| zitadel_email | String | IdP email | — |
| display_name | String | 顯示名稱 | — |
| avatar | String | 頭像 URL | — |
| phone | String | 聯絡電話 | — |
| language | String | 語系 | — |
| currency | String | 幣別 | — |
| member_status | String | unverified/active/suspended/deleted | — |
| origin | String | platform_native/oidc/ldap/scim | — |
| business_email | String | 業務 email | — |
| business_email_verified | Bool | 業務 email 已驗證 | — |
| business_phone | String | 業務 phone | — |
| business_phone_verified | Bool | 業務 phone 已驗證 | — |
| totp_enrolled | Bool | TOTP 已綁定 | — |
| totp_secret_cipher | Binary | AES-GCM 加密 secret | — |
| totp_backup_codes_hash | Array | bcrypt 備援碼 hash | — |
| suspend_reason | String | 停權原因 | — |
| create_at | Int64 | 建立時間 ms | — |
| update_at | Int64 | 更新時間 ms | — |
| deleted_at | Int64 | 軟刪時間 ms | — |

#### identities（MongoDB）

| Field | Type | Comment | Index |
|-------|------|---------|-------|
| _id | ObjectId | PK | PK |
| tenant_id | String | 租戶 ID | Unique(tenant_id, external_id) |
| external_id | String | LDAP/SCIM 外部 ID | Unique(tenant_id, external_id) |
| zitadel_user_id | String | ZITADEL sub | Unique(tenant_id, zitadel_user_id) |
| uid | String | 對應 Member UID | — |
| create_at | Int64 | 建立時間 ms | — |

#### Redis Keys

| Key Pattern | 用途 | TTL |
|-------------|------|-----|
| member:otp:challenge:{id} | OTP challenge（bcrypt hash） | 300s |
| member:otp:challenge:{id}:attempts | OTP 錯誤次數 | 同 challenge |
| member:verify:rate:{tenant}:{uid}:{kind} | 重發冷卻 | 60s |
| member:verify:daily:{tenant}:{uid}:{kind} | 每日上限計數 | 24h |
| member:totp:enroll:{tenant}:{uid} | TOTP 綁定 staged secret | 600s |
| member:totp:used:{tenant}:{uid}:{step} | TOTP 重放保護 | 90s |
| member:seq:{tenant} | UID 序號 INCR | 永久 |

---

## 5. API Design

**Base Path：** `/api/v1/members`

### 5.1 Profile

| Method | Path | 說明 |
|--------|------|------|
| GET | `/me` | 取得當前會員 profile |
| PATCH | `/me` | 更新 display_name / avatar / language 等 |

### 5.2 Verification（OTP）

| Method | Path | 說明 |
|--------|------|------|
| POST | `/me/verifications/email/start` | 發起業務 email OTP |
| POST | `/me/verifications/email/confirm` | 確認 email OTP |
| POST | `/me/verifications/phone/start` | 發起業務 phone OTP |
| POST | `/me/verifications/phone/confirm` | 確認 phone OTP |

### 5.3 TOTP

| Method | Path | 說明 |
|--------|------|------|
| GET | `/me/totp/status` | TOTP 綁定狀態 |
| POST | `/me/totp/enroll` | 開始綁定（回 otpauth_url） |
| POST | `/me/totp/enroll/confirm` | 確認綁定（回 backup codes） |
| POST | `/me/totp/verify` | Step-up 驗碼 |
| POST | `/me/totp/disable` | 解除綁定 |
| POST | `/me/totp/backup-codes/regenerate` | 重產備援碼 |

完整 schema 見 `generate/api/member.api`。

---

## 6. Resource

| 資源 | 路徑 |
|------|------|
| API 定義 | `generate/api/member.api` |
| Logic 編排 | `internal/logic/member/` |
| 模組原始碼 | `internal/model/member/` |
| 開發 README | `internal/model/member/README.md` |
| 設定範例 | `etc/gateway.dev.example.yaml` → `Member` 區塊 |
| 設計參考 | `docs/identity-member-design.md`、`docs/model.md` §6.1 |
| Seed CLI | `cmd/member-seed` |
