# 會員中心升級規劃

此文件整理會員中心的目標升級架構，包含設定畫面、Email 驗證與忘記密碼、帳號分級與角色管理、會員主資料、訂閱管理，以及作為外部服務 Token / Auth 中心的整體模型。此文件以一次到位的最終設計為準，不再以過渡方案或分階段落地作為主軸。

## 目標
- 增加管理者可操作的系統設定畫面，涵蓋 SMTP、Send Engine 與 Auth 資源設定。
- 補齊 Email 驗證與忘記密碼的完整寄信流程。
- 建立帳號分級規則，明確區分 `superuser`、`admin` 與一般會員。
- 確立會員認證狀態模型，以「已認證 / 未認證」為會員狀態基礎。
- 第三方登入以 Google 為唯一支援 provider。
- 將「會員中心作為 Token / Auth 中心」的外部服務授權模型文件化，納入 Send Engine 與 File Access。
- 擴充會員個人資料、地址簿與會員端訂閱管理能力，並同步定義可供其他服務使用的 profile scopes。
- 補齊帳號生命週期、審計紀錄、rate limit 與 MFA 的基礎治理規則。

## 實作進度（2026-04-17）

已完成：
- 建立 `user_profiles` / `user_addresses` 實體、DbContext 映射與 EF migration
- `users` 新增治理欄位：
  - `last_login_at`
  - `last_seen_at`
  - `disabled_at`
  - `disabled_by`
- 註冊與 external login 建立新帳號時，會同步建立空白 profile row
- 新增 current-user profile API：
  - `GET /user/profile`
  - `POST /user/profile`
  - `GET /user/addresses`
  - `POST /user/addresses`
  - `DELETE /user/addresses/{id}`
  - `GET /user/subscriptions`
  - `POST /user/subscriptions/{id}/unsubscribe`
- 新增會員端 Web UI：
  - `/profile`
  - `/profile/addresses`
  - `/profile/subscriptions`
- 新增會員端直接退訂流程，不需再透過 email token
- `profile:*` scopes 已註冊進 OpenIddict：
  - `profile:basic.read`
  - `profile:basic.write`
  - `profile:addresses.read`
  - `profile:addresses.write`
  - `profile:subscriptions.read`
  - `profile:subscriptions.write`
- API 已接上 profile scope policies；原有 by-email service API 已停用，Email 不作為跨會員查詢鍵。
- token resource 映射已將 `profile:*` 納入 member center audience
- `/admin/security` 已擴充 SMTP 設定欄位：
  - relay host / port
  - TLS / SSL
  - timeout
  - username / password
  - sender name / sender email
- `/admin/security` 已新增 SMTP 測試信功能，可輸入測試收件 Email 送出測試信
- SMTP 設定已收斂到 DB flags，密碼欄位留白時保留既有值
- 已建立共用帳號寄信服務，用於 Email 驗證信與密碼重設信
- 已抽出共用帳號 Email 模板服務：
  - 驗證信模板
  - 密碼重設信模板
- 註冊流程已改為寄送 Email 驗證信
- forgot password 已改為寄送重設信，不再直接回傳 reset token
- 已補 resend verification：
  - Web：`POST /account/resendverification`
  - API：`POST /auth/email/resend`
- `/admin/security` 已補 `PublicBaseUrl`，作為驗證信與重設信連結的基準 URL
- 已補 audit log：
  - `account.verification_email_sent`
  - `account.password_reset_email_sent`
  - `account.email_verified`
  - `account.password_reset_completed`
- 已啟用 Identity lockout 基礎策略：
  - `MaxFailedAccessAttempts = 5`
  - `DefaultLockoutTimeSpan = 15 分鐘`
  - `AllowedForNewUsers = true`
- 已落地公開入口 rate limit：
  - Web：login / register / forgot password / resend verification
  - API：register / forgot password / resend verification
  - Newsletter API：public subscribe / unsubscribe token
- password grant 與 anonymous OAuth client 已移除，使用者登入統一走已註冊 `web_login` client 的 Authorization Code + PKCE
- 已完成 `superuser` / `admin` 權限模型第一版落地：
  - `Admin` policy 已擴為接受 `admin` 與 `superuser`
  - 新增 `Superuser` policy
  - installer `init` / `add-superuser` 會建立或提升 `superuser`
  - 舊指令別名 `add-admin` / `reset-admin-password` 仍可用
- 已新增管理後台帳號治理頁：
  - `/admin/accounts`
  - 支援查詢帳號、查看 email verified / role / disabled / last login
  - 只有 `superuser` 可授予或移除 `admin`
  - 只有 `superuser` 可停用或啟用帳號
- 已補帳號治理規則：
  - `superuser` 帳號不可在管理 UI 中被降權、停用、刪除或強制變更密碼
  - disabled 中的 `admin` 可保留 `admin` role
  - `superuser` 可在管理 UI 強制重設非 `superuser` 帳號密碼
  - 強制重設密碼後會更新 security stamp 並撤銷既有 OpenIddict authorization / token
- 已補帳號治理與生命週期 audit log：
  - `account.registered`
  - `account.external_login_linked`
  - `account.password_changed`
  - `account.role_changed`
  - `account.disabled`
  - `account.enabled`
  - `system.security_settings_updated`
  - `system.security_test_email_sent`
- 已補登入治理：
  - Web login / external login 成功後更新 `last_login_at` / `last_seen_at`
  - disabled 帳號無法透過 Web login、external login 取得登入
  - Web cookie 與 API authenticated request 會檢查 disabled 狀態
- 已補 redirect 型登入 `web_login`：
  - OAuth client usage 新增 `web_login`
  - 支援 Authorization Code + PKCE
  - `/oauth/authorize` 未登入時會導向 Web login，登入後回到原 authorize request
  - `/oauth/token` 已支援 authorization code exchange
  - API / Web 共用 DataProtection application name `MemberCenter`
  - 支援 `Auth:WebLoginUrl` 與 `Auth:AllowedLoginReturnUrlPrefixes` 處理 Web / API 不同 origin 的 redirect login
  - Login / Logout return URL 已拆分 allowlist 並改用結構化 URI 驗證，防止 host 與 path prefix 混淆

進行中：
- profile / addresses / subscriptions 的畫面目前為最小可用版本，尚未優化樣式與完整驗證提示

待續作：
- resource registry 管理 UI
- Email 驗證信 / 重設信的正式模板與文案優化
- rate limit 與 lockout 規則補齊：
  - one-click unsubscribe token 申請
  - 服務型 token flow 與人類登入 flow 的完整差異化治理
  - 更細的風控觀測與後台設定化
- audit log 與 rate limit 尚未全面覆蓋所有規劃入口與治理事件
- `superuser` / `admin` 第一版已完成，後續待細化：
  - 更細權限切分
  - 是否需要更多治理角色
- 管理後台帳號治理功能補強：
  - 更完整排序 / 篩選進一步細化
  - 更細的操作確認與保護規則擴充

## 現況盤點

### 已存在
- `MemberCenter.Web` 與 `MemberCenter.Api` 已有本地註冊、登入、忘記密碼、重設密碼、Email 驗證入口。
- `MemberCenter.Web/Controllers/AccountController.cs` 已有：
  - `ForgotPassword`
  - `ResetPassword`
  - `VerifyEmail`
  - `ExternalLogin` / `ExternalLoginCallback`
- `MemberCenter.Api/Controllers/AuthController.cs` 已有：
  - `POST /auth/register`
  - `POST /auth/password/forgot`
  - `POST /auth/password/reset`
  - `GET /auth/email/verify`
- Google external login 已在 `src/MemberCenter.Web/Program.cs` 接入，並已支援同 email auto-link。
- Installer 已可建立初始管理帳號，但角色模型仍需調整為文件定義的 `superuser`。
- 管理後台已有 `/admin/security` 畫面，目前僅提供 token 時效設定：
  - `AccessTokenMinutes`
  - `RefreshTokenDays`
- `MemberCenter.Web` 已有 `/profile` 頁面，但目前僅顯示：
  - Email
  - Email 驗證狀態
  - 建立時間
- `MemberCenter.Api` 已有 `GET /user/profile`，但目前只提供基礎欄位，不足以支撐其他服務查詢完整會員資料。
- `docs/DESIGN.md` / `docs/FLOWS.md` 已定義 File Access 流程：
  - upload: `client_credentials` 取得 upload token 後由外部服務直連 file space
  - download: 由業務服務驗權後，向 Member Center 申請短效 delegated download token，File Access agent 回打 Member Center 驗證
- Auth resource registry 第一版已落地：
  - 新增 `auth_resources`、`auth_resource_scopes`、`auth_client_usage_permissions`
  - `TokenController` 已改由 scope 查 registry 決定 token audiences
  - OAuth client usage 可使用的 scopes 已改由 DB permission matrix 決定
  - 預設資源包含 `member_center_api`、`send_engine_api`、`file_access_api`

### 部分實作
- `SendEngine__BaseUrl`、`SendEngine__WebhookSecret` 仍停留在設定來源與 options binding，尚未進入可編輯的管理畫面。
- Auth 資源 / audience / scope registry 已完成 DB 驅動第一版，仍缺管理 UI。
- 帳號治理後台、角色模型與 disabled account 規則已完成第一版，但仍缺進一步細化與保護規則。
- profile / addresses / subscriptions 畫面與驗證目前為最小可用版本，尚未完成 UI refinement。

### 待補項
- File Access 的 OAuth client usage、scope、audience 已落地；delegated download token issuing / validation 已落在 Member Center。
- Token resource / audience 已抽象為 registry；後續需補 resource registry 管理畫面。
- Email 樣板正式文案與會員 / 後台 UI 細節仍待整理。
- one-click unsubscribe token 已有可設定的 rate limit（預設每 client/IP 每分鐘 60 次）；更細的風控觀測仍待補。

## 功能規劃

### 1. 系統設定畫面
狀態：`部分完成`

#### 1.1 主要新增項
- 新增獨立的「系統設定」或「整合設定」畫面。
- 第一批可編輯設定：
  - SMTP Host
  - SMTP Port
  - SMTP Username
  - SMTP Password
  - SMTP From Name
  - SMTP From Address
  - SMTP Enable SSL / TLS
  - `SendEngine__BaseUrl`
  - `SendEngine__WebhookSecret`

目前進度：
- 已完成 SMTP 與 token lifetime 設定 UI，沿用 `/admin/security`
- SMTP 密碼已改為版本化 Data Protection ciphertext，既有明碼會在首次讀取時自動轉換；Production key ring 憑證保護仍待補
- token lifetime 已套用至 OpenIddict 簽發流程，設定更新後的新 token 立即使用新時效
- 已完成 SMTP 測試信
- 已完成 `PublicBaseUrl`
- `SendEngine__BaseUrl` / `SendEngine__WebhookSecret` 尚未進管理畫面
- Auth 資源設定暫未實作，仍保留待 audience/scope 抽象化後處理

#### 1.2 可一併納入的既有設定
- 目前 `/admin/security` 已有的 token 時效設定可整併進同一個設定體系：
  - Access token 分鐘數
  - Refresh token 天數
- `SendEngineWebhookOptions.SubscriptionEventsPath` 目前已有預設值 `/webhooks/subscriptions`，若未來有多環境或反向代理差異，可評估是否也納入設定畫面。

#### 1.3 暫不建議放入設定畫面的項目
- `ConnectionStrings__Default`
- `Auth__Issuer`
- `PathBase`
- Google OAuth Client Secret

以上屬部署層級或高風險設定，建議仍以環境變數或部署設定管理，不在一般管理 UI 直接編輯。

#### 1.4 設定保存策略
- 所有可運行期調整的設定統一收斂到 DB，例如沿用 `system_flags` 或新增專用設定表。
- 敏感值至少需要：
  - UI 遮罩顯示
  - 審計紀錄
  - 更新權限限制
- Secret 類設定需明確區分：
  - 可明文顯示的設定
  - 僅能覆寫、不可回顯的 secret 類設定

### 2. Email 驗證與忘記密碼
狀態：`核心完成，文案待續作`

#### 2.1 目標狀態
- 註冊後自動寄送 Email 驗證信。
- 使用者可重新發送驗證信。
- 忘記密碼送出後寄送 reset password email。
- Web UI 與 API 共享同一套 token 與寄信服務邏輯。

#### 2.2 目前已有的基礎
- Identity token provider 已可產生：
  - email confirmation token
  - password reset token
- Web 與 API 已有 verify/reset endpoint。
- View 已存在：
  - `ForgotPassword`
  - `ResetPassword`
  - `VerifyEmailResult`

#### 2.3 待補實作
- Email 樣板正式文案整理：
  - 驗證信
  - 忘記密碼信
- 更完整的產品提示與畫面細節整理

#### 2.4 安全與產品規則
- 忘記密碼 API 與 UI 都應避免暴露帳號是否存在。
- Reset token 與 verify token 的 URL 應統一由設定的 public base URL 組出。
- 驗證信與重設密碼信都應記錄 audit log。
- 會員未驗證時是否允許登入與可操作範圍，需在實作時明確固定為單一規則，不保留模糊狀態。

### 3. 帳號分級與角色管理
狀態：`第一版完成，細化待續作`

#### 3.1 角色模型
- `superuser`
  - 只能透過 installer 建立或提升。
  - 可管理所有帳號角色。
  - 可授予或移除 `admin`。
- `admin`
  - 必須先以一般會員身分註冊。
  - 再由 `superuser` 手動授權。
  - 不可自行提升其他帳號為 `admin`，除非後續明確擴權。
- `member`
  - 預設註冊身分。
  - 依 Email 驗證狀態再分為：
    - `unverified`
    - `verified`

#### 3.2 會員分級原則
- 會員狀態以「已認證 / 未認證」為基礎。
- 若未來擴充，可在不破壞既有角色模型下新增：
  - VIP / 付費會員
  - 停權 / 凍結
  - 企業帳號等級

#### 3.3 對現行實作的調整方向
- 已完成：
  - Installer 建立的高權限帳號改為 `superuser`
  - 管理後台中的角色治理操作為 `superuser` 專屬
  - 已新增 `/admin/accounts`，可查詢帳號、查看驗證狀態、指派或移除 `admin`、顯示 `superuser`
  - 已補搜尋與篩選
- 待續作：
  - 更細的角色切分與保護規則

#### 3.4 權限規則
- 只有 `superuser` 可變更角色。
- `admin` 可進入既有管理後台，但不可升降他人權限。
- 一般會員不可看見 `/admin/*`。
- 未來若導入更多後台功能，建議區分：
  - 後台使用權限
  - 帳號治理權限

### 3.5 帳號生命週期
狀態：`部分完成`
- 帳號狀態至少包含：
  - `active`
  - `disabled`
  - `locked`
- Email 驗證狀態獨立於帳號狀態，維持：
  - `unverified`
  - `verified`
- Email 為對外主識別 key，不允許修改。
- 若使用者要更換 email，採重新註冊新帳號，不提供原帳號改 email 流程。
- 後台需保留帳號停用能力；停用後不得再登入與取得新 token。
- 建議記錄帳號治理欄位：
  - `last_login_at`
  - `last_seen_at`
  - `disabled_at`
  - `disabled_by`
- superuser 重設他人密碼、停用帳號、解除停用等治理規則，先記入規劃，細節待後續檢討。

目前進度：
- 已完成停用 / 啟用帳號
- 已完成 disabled 帳號不可登入與不可取得新 token
- 已完成 `last_login_at` / `last_seen_at` / `disabled_at` / `disabled_by`
- 已完成 superuser 重設非 superuser 帳號密碼，並強制讓既有 session / refresh token 失效
- superuser 本身的密碼治理仍限定走 installer

### 4. 第三方登入
狀態：`已完成本期範圍`
- 只支援 Google。
- 目前 Google external login 已存在，後續只補齊與整體權限模型的一致性。
- 若 Google 回傳 email 已驗證，可直接把會員標記為 `verified`；目前 `AccountProvisioningService` 已有依 external login provider 回填 `EmailConfirmed` 的基礎邏輯。

### 5. 會員基本資料與地址簿
狀態：`核心完成，UI refinement 待續作`

#### 5.1 會員基本資料
- 會員可自行維護的基本資料欄位包含：
  - `last_name`
  - `first_name`
  - `nick_name`
  - `mobile_phone`
  - `landline_phone`
  - `date_of_birth`
  - `gender`
  - 公司名稱
  - 部門
  - 職稱
  - 公司電話
  - 統一編號
  - 發票抬頭
  - `remark`
- Email 為固定欄位，不提供變更流程；如需更換 email，採重新註冊。
- Email 同時作為對外 API 的主 key。

建議必填欄位：
- `last_name`
- `first_name`

建議選填欄位：
- `nick_name`
- `mobile_phone`
- `landline_phone`
- `date_of_birth`
- `gender`
- `company_name`
- `department`
- `job_title`
- `company_phone`
- `tax_id`
- `invoice_title`
- `remark`

建議 enum：
- `gender`
  - `male`
  - `female`
  - `other`
  - `unspecified`

欄位驗證建議：
- `first_name`、`last_name`
  - 必填
  - 長度 `1-100`
- `nick_name`
  - 長度 `0-100`
- `mobile_phone`、`landline_phone`、`company_phone`
  - 儲存標準化字串
  - 盡量接近 E.164，市話可接受本地格式輸入後正規化
- `company_name`、`department`、`job_title`
  - 長度 `0-200`
- `tax_id`
  - 長度 `0-32`
  - 驗證以台灣統編規則為主，但 schema 保留國際延展空間
- `invoice_title`
  - 長度 `0-200`
- `remark`
  - 長度 `0-1000`
- `date_of_birth`
  - 使用 `date` 型別，不使用 datetime

資料使用原則：
- `first_name + last_name` 為正式姓名來源
- `nick_name` 為顯示用途，不作為唯一鍵
- `mobile_phone` 優先於 `landline_phone` 作為主要聯絡電話

目前進度：
- 上述欄位與資料模型、API、Web profile 編輯已完成第一版
- 欄位驗證與畫面體驗仍待整理

#### 5.2 地址簿 / 收貨地址清單
- 新增會員地址簿概念，支援一位會員維護多筆地址。
- 地址欄位包含：
  - `label`
  - 收件人姓名
  - 收件人電話
  - `country_code`
  - `postal_code`
  - `state_region`
  - `city`
  - `district`
  - `address_line1`
  - `address_line2`
  - 公司名稱（可選）
  - 是否預設地址
  - 地址用途
  - `address_meta_json`
- 地址用途建議先預留列舉：
  - `shipping`
  - `billing`
  - `both`
- 資料格式規則：
  - 以台灣使用情境為主
  - 國碼、電話、郵遞區號、國家碼等欄位盡量符合國際格式
  - 統編以台灣統編規則為主
  - 地址採「結構化欄位 + `address_meta_json` 補充資料」混合設計，不採純 JSON
- 刪除規則：
  - 若會員只剩最後一筆地址，不允許刪除
  - 若刪除的是預設地址，系統需自動補選新的預設地址

建議必填欄位：
- `user_id`
- `label`
- `recipient_name`
- `recipient_phone`
- `country_code`
- `address_line1`
- `usage`
- `is_default`

建議選填欄位：
- `postal_code`
- `state_region`
- `city`
- `district`
- `address_line2`
- `company_name`
- `address_meta_json`

建議 enum：
- `usage`
  - `shipping`
  - `billing`
  - `both`

欄位驗證建議：
- `label`
  - 長度 `1-100`
- `recipient_name`
  - 必填
  - 長度 `1-100`
- `recipient_phone`
  - 必填
  - 儲存標準化字串
- `country_code`
  - 必填
  - 使用 ISO 3166-1 alpha-2
  - 固定 2 碼大寫
- `postal_code`
  - 長度 `0-20`
- `state_region`、`city`、`district`
  - 長度各 `0-100`
- `address_line1`
  - 必填
  - 長度 `1-255`
- `address_line2`
  - 長度 `0-255`
- `company_name`
  - 長度 `0-200`
- `address_meta_json`
  - 可為 `null`

資料規則：
- 同一個 `user_id + usage` 最好限制只能有一筆 `is_default = true`
- `label` 可為自由文字，不先做 enum
- 地址至少保留一筆，因此最後一筆不可刪除

目前進度：
- 地址簿資料模型、API、Web UI 已完成第一版
- `最後一筆不可刪除` 已實作
- 預設地址切換與資料結構已完成
- 畫面提示與驗證細節仍待整理

#### 5.3 資料治理原則
- 會員基本資料與地址簿屬於會員中心主資料，可供其他服務查詢，但寫入權限需嚴格控管。
- 會員本人可編輯自己的基本資料與地址簿。
- 其他服務預設只有讀取權限。
- 若其他服務需要代會員寫入，必須有額外 scope 與審計規則。

### 6. 會員資料 API 與 Auth Scope 規範
狀態：`current-user scope 已落地，公開資料與服務整合 scope 待拆分`

#### 6.1 規劃目的
- 讓其他服務可透過 API 取得會員中心的基本資料與地址資料。
- 在一開始就把 scope 邊界定清楚，避免未來 profile API 無限制外放。

#### 6.2 建議 scopes
- `profile:basic.read`
  - 讀取會員基本資料
- `profile:basic.write`
  - 更新會員基本資料
- `profile:addresses.read`
  - 讀取會員地址簿
- `profile:addresses.write`
  - 新增、修改、刪除會員地址簿
- `profile:subscriptions.read`
  - 讀取會員已訂閱電子報清單
- `profile:subscriptions.write`
  - 透過已登入會員介面取消訂閱或調整會員自己的訂閱狀態

目前進度：
- `profile:*` scopes 已註冊並接上 policy
- current-user API 已完成第一版，且一律從 access token subject 解析目前會員
- by-email service API 已停用，Email 不可用來查詢或修改其他會員資料
- audience / resource registry DB 驅動第一版已完成

#### 6.3 API 邊界建議
- 其他服務 API：
  - 不得沿用 current-user profile scopes 查詢其他會員
  - 不得以 Email 作為跨會員 lookup key
  - 公開社群資料應使用獨立 endpoint、公開 DTO 與 scope
  - 未來有限個資整合應使用 S2S 專用 scope、穩定 user id、欄位投影與 audit log
  - 預設以最小權限授權，不因 client 類型自動放寬資料邊界
- 會員本人 UI / API：
  - 可讀寫自己的 profile 與地址
  - 可查看自己的訂閱清單
  - 可對自己的訂閱直接退訂，不需再次 email token 確認
- 管理後台：
  - 可查詢會員資料，但是否可編輯應另行定義，不與一般會員本人編輯權混用

#### 6.4 與 OIDC 標準 scope 的關係
- 目前 `openid email profile` 中的 `profile` scope 太寬泛，不足以承載業務上需要的個資與地址簿授權控制。
- 建議保留 OIDC `profile` 作為基本 claims 用途，但業務資料改由自訂 scope 控制。

### 7. 會員端訂閱管理（我的訂閱）
狀態：`核心完成，newsletter 補強待續作`

#### 7.1 目標
- 會員登入後，可集中查看自己目前已訂閱的電子報清單。
- 會員可直接從此介面取消訂閱，不需要再次透過 email token 驗證。
- 此流程與 email 內的一鍵退訂屬不同入口：
  - email 退訂：適用未登入或直接從信件操作
  - 會員中心退訂：適用已登入且已確認是本人

#### 7.2 UI 能力
- 顯示已訂閱清單：
  - 所屬 tenant / 站台
  - 電子報清單名稱
  - 訂閱狀態
  - 訂閱建立時間
  - 最後更新時間
- 可執行：
  - 直接取消訂閱
  - 未來可擴充為偏好調整或重新訂閱

目前進度：
- 我的訂閱頁與 current-user 訂閱 API 已完成
- 會員登入後可直接退訂，不需再次 email token
- 進一步的 newsletter UI / 行為補強本輪刻意先跳過

#### 7.3 流程規則
- 已登入會員對自己的訂閱執行退訂時，不需 email token 二次確認。
- 系統仍需：
  - 驗證 subscription 確實屬於目前登入會員
  - 寫入 audit log
  - 發送 `subscription.unsubscribed` 事件
- 若該 email 已在黑名單中，行為需與既有退訂規則一致。
- 即使已綁定 `user_id`，仍保留既有以 `list_id + email` 進行訂閱 / 退訂的 public 流程。
- 會員可重新訂閱已退訂的電子報，規則與既有 public subscribe 流程保持一致。

#### 7.4 與現行訂閱 API 的差異
- 現行 `/newsletter/preferences` 偏向以 `list_id + email` 操作，主要照顧跨站 API 邊界。
- 新的「我的訂閱」介面應改以登入者身份為主體，不再要求使用者自行輸入 email。
- 建議新增一組以 current user 為主體的 API，而不是把既有未登入流程硬改成同一支。

### 7.5 訂閱識別原則
- Email 是訂閱領域的主 key。
- `user_id` 為已註冊會員與訂閱資料的關聯鍵，不取代 email 在訂閱流程中的角色。
- 因此系統同時保留：
  - public flow：`list_id + email`
  - member flow：`current user`
- 兩種入口最終都回到同一組 subscription 資料。

### 8. 會員中心作為外部服務的 Token / Auth 中心
狀態：`Auth 基礎已完成，管理 UI 待補`

#### 8.1 共通模型
- Send Engine 與 File Access 本質上是同一套模型：
  - 由 Member Center 簽發 access token
  - 外部服務以 Member Center JWKS 驗簽
  - 依 `iss/aud/exp/scope/tenant_id` 做授權與租戶邊界控制
- 差異只在資源類型不同：
  - Send Engine 偏向 service-to-service API 呼叫
  - File Access 除了 upload 的 S2S token 外，download 還需要 delegated short-lived token

#### 8.2 File Access 納入規劃
- 新增 OAuth client usage，建議至少區分：
  - `file_api`
  - 或更明確拆成 `file_upload_api` / `file_download_delegate`
- 新增 scopes：
  - `files:upload.write`
  - `files:download.read`
  - `files:download.delegate`
  - `files:delete`
  - `files:metadata.read`
- 新增 audience：
  - `file_access_api`
- Member Center 需實作 delegated download token 規則：
  - 下載 token 必須短效
  - 必須綁定 `tenant_id`
  - 必須綁定 `user_id`
  - 必須綁定 `file_id` 或 `object_key`
  - 必須綁定 `method`
  - 不可直接重用一般 S2S access token 給 client

#### 8.3 目標設計
- Auth 資源設定採 resource registry，不再以一組一組 `Auth__XAudience` 擴充。
- 每個外部資源服務都以統一結構註冊：
  - resource name
  - audience
  - scopes
  - client usages
  - 是否需要 `tenant_id`
  - 是否允許 delegated token（供 Member Center 判斷該 resource 是否可簽發短效下載 token）
- 新增資源服務時，只擴充 registry 與授權規則，不再修改硬編碼 audience 分支。
- 所有外部資料存取均以 scope 作為唯一授權依據。

#### 8.4 授權模型
- OAuth client usage 與 resource 綁定：
  - `tenant_api` -> `member_center_api`
  - `send_api` -> `send_engine_api`
  - `file_api` -> `file_access_api`
- scope 用於細粒度權限控制。
- `TokenController` 依 scope 與 usage 對照 resource registry 計算 `resources/audiences`。
- delegated download token issuing / validation 屬於 Member Center 責任；File Access agent 負責把 client GET request 的 token 與 `tenant_id + file_id/object_key + method` 帶回 Member Center 驗證。

目前進度：
- Send Engine、Member Center profile/newsletter scopes、File Access scopes 已進 registry
- 訂閱名單讀取已拆分 tenant scope `newsletter:list.read` 與平台 scope `newsletter:list.read.global`；tenant claim 採 fail-closed
- 建立訂閱與申請退訂 token 已要求 tenant S2S scope `newsletter:subscriptions.write`；確認與實際退訂仍以一次性 token 匿名完成
- Email 型訂閱偏好 API 已要求 tenant S2S：讀取使用 `newsletter:subscriptions.read`，更新使用 `newsletter:subscriptions.write`
- `TokenController` 已以 registry 解析 audiences
- OAuth client usage-scope matrix 已以 `auth_client_usage_permissions` 驅動
- File Access delegated token issuing / validation API 已落在 Member Center；File Access agent 尚需串接 validation endpoint

### 9. 審計紀錄
狀態：`大致完成，少數治理事件待續作`
- 下列事件必須寫入 audit log：
  - 註冊
  - Email 驗證成功
  - 忘記密碼申請
  - 密碼重設成功
  - 已登入修改密碼
  - Google external login 綁定
  - 角色變更
  - 帳號停用 / 啟用
  - profile 修改
  - 地址簿新增 / 編輯 / 刪除 / 預設地址切換
  - 會員端直接退訂
  - 系統設定修改
  - OAuth client 建立
  - OAuth client secret 旋轉
- OAuth client secret 顯示一次、旋轉與治理能力視為既有基線；細節之後再檢討。

目前進度：
- 帳號寄信、驗證、重設密碼、修改密碼、註冊、external login 綁定、角色變更、帳號停用 / 啟用、profile、地址、會員端退訂、系統設定修改均已有實作
- Member Center Web 已對不安全 HTTP method 啟用全域 antiforgery 驗證，Admin POST 表單均明確帶 token
- API / Web forwarded headers 已改為 trusted proxy allowlist；未設定 IP/CIDR 時 fail-closed
- HTTPS redirect / HSTS 已明確交由 AWS ALB / CloudFront；API / Web 不再對私有 HTTP listener 做應用程式層 redirect
- Production OpenIddict signing / encryption 與 Data Protection key-protection PFX 載入已完成；缺少、過期或無 private key 時拒絕啟動
- PFX 到期預警已加入啟動 log（預設提前 30 天），CloudWatch alarm 由部署環境設定
- API / Web 在非 Development 缺少 `ConnectionStrings:Default` 時會拒絕啟動，localhost fallback 僅保留給 Development
- Production `Auth:Issuer` 已改為必填 HTTPS；私有 HTTP OAuth 呼叫改由獨立的 `Auth:AllowInternalHttpTokenEndpoint` 控制
- Web security headers 已完成：self-only CSP、frame deny、nosniff、no-referrer 與 Permissions-Policy
- 已加入標準 xUnit security tests，涵蓋 return URL、HTTPS、trusted proxy、Production 憑證與 security options
- OAuth client 建立與 secret 旋轉等治理細節仍待續作

### 10. Rate Limit 與防濫用
狀態：`部分完成`
- 下列入口必須有 rate limit：
  - login
  - forgot password
  - resend verification
  - register
  - public subscribe
  - unsubscribe token 申請
  - one-click unsubscribe token 申請
- lockout 與 rate limit 需能區分：
  - 人類使用者登入
  - service API token 申請

目前進度：
- 已完成 login / forgot password / resend verification / register / public subscribe / unsubscribe token 申請
- `one-click unsubscribe token` 申請已補每 client/IP rate limit
- `/oauth/token` 已補可設定 rate limit（預設每來源 IP 每分鐘 30 次）；Production 分散式限制由 AWS WAF 負責
- API / Web password reset 與 Email verify 已補可設定 rate limit（預設每來源 IP 每 10 分鐘 10 次）
- 人類登入 flow 已有 lockout；service API token flow 與更細觀測仍待續作

### 11. MFA 與非本期項目
狀態：`待續作`
- 在 Email 寄送能力完成後，可規劃 Email-based MFA / challenge 驗證。
- 其餘先明確列為未來可補項：
  - consent / terms acceptance
  - 資料匯出 / 刪除
  - login history / device management
  - 通知偏好中心

## 一次到位的實作範圍
1. 設定中心：
   - SMTP
   - Send Engine
   - Auth 資源設定
   - token lifetime
2. Identity 與通知：
   - Email 驗證寄信
   - 忘記密碼寄信
   - 重送驗證信
3. 會員主資料：
   - 基本資料
   - 地址簿
   - Profile UI / API
4. 訂閱管理：
   - 我的訂閱頁
   - current-user 型訂閱 API
   - 直接退訂流程
5. 權限與角色：
   - `superuser`
   - `admin`
   - `member` + verified / unverified
   - 帳號管理與角色指派
6. Auth 中心：
  - resource registry
  - profile scopes
  - file access scopes
  - delegated token
7. 安全治理：
   - audit log
   - rate limit
   - MFA 預留
8. 文件、測試與 installer 同步調整

目前整體狀態：
- `1`：部分完成
- `2`：核心完成，文案待續作
- `3`：核心完成
- `4`：核心完成，但本輪不再補強 newsletter refinement
- `5`：第一版完成
- `6`：先跳過 audience / scope 抽象化
- `7`：大致完成，仍有少量補強
- `8`：部分完成

## 影響範圍

### Web
- 新增設定畫面與表單。
- 新增帳號管理畫面。
- 調整註冊成功後導引與驗證提醒文案。
- 新增會員 profile 編輯頁、地址簿頁與我的訂閱頁。

### API
- 若設定畫面走 API，需新增設定讀寫端點。
- 若帳號管理後台走 API，需新增角色管理與帳號查詢端點。
- File Access 的 resource / audience mapping、file scopes、delegated token issuing / validation API 已完成；File Access agent 串接與流程測試仍待補。
- 需新增 current-user 型 profile / addresses / subscriptions API 與對應 scopes。

### Infrastructure
- 新增 SMTP sender 與設定存取服務。
- 調整帳號 provisioning 與角色管理服務。
- token resource resolver 已抽到 registry service；File Access delegated token issuer / validator 已落在 Member Center。
- 新增會員基本資料與地址簿的資料模型與服務層。

### Installer
- 建立或提升 `superuser`。
- 視命名策略決定是否保留 `add-admin`，或改名為更貼近語意的指令。

## 文件與實作同步原則
- 文件中的「已存在」代表已有 controller / route / view / 基礎邏輯，不代表整體功能已完成產品化。
- 本規劃完成後，應同步回寫：
  - `docs/UI.md`
  - `docs/FLOWS.md`
  - `docs/INSTALL.md`
  - `README.md`
