# Flows

流程以「API 自建 UI」與「會員中心統一 UI」兩種模式描述。

## F-01 註冊會員
- [API] 站點送出 `POST /auth/register`
- [API] 會員中心建立 user、寄送驗證信
- [UI] 導向會員中心註冊頁完成註冊
- [UI] 會員中心寄送驗證信

## F-02 登入（OAuth2 + OIDC）
- [UI] 使用 `usage=web_login` OAuth client，導向 `/oauth/authorize` 完成 Authorization Code + PKCE
- [UI] 若未登入，會員中心會導向 Web login，登入後回到原 authorize request
- [UI] 站點用 code + code_verifier 換 token
- [UI] `web_login` 可使用 public client，不要求 client secret；必須設定 redirect URI
- [UI] 若 Web 與 API 不同 origin，需設定 `Auth:WebLoginUrl`，且 Web 端需允許導回 `Auth:Issuer` 或 `Auth:AllowedLoginReturnUrlPrefixes`
- [UI] 若 Web 與 API 位於不同子網域，需設定 `Auth:CookieDomain`，讓 authorize endpoint 可讀取 Web login cookie
- [Auth] 不支援 password grant 或未註冊 client；refresh token 統一送至 `/oauth/token`
- [Auth] 新簽發 token 套用後台安全設定：access token `5–1440` 分鐘、refresh token `1–365` 天

## F-02a 登出（外站 direct logout）
- [UI] 外站將瀏覽器導向 Member Center Web `/account/logout`
- [UI] Member Center 清除 Web login cookie / session
- [UI] 若帶 `returnUrl`，Member Center 僅允許導回本站 local URL 或 allowlist 內的外站 URL
- [Auth] Login 與 Logout 使用各自 allowlist，並結構化比對 scheme、host、port 與 path segment，禁止單純字串 prefix
- [UI] 登出完成後導回外站 callback
- [UI] 外站 callback 清除本站 session / token

## F-02b 內容站台呼叫 Send Engine（Client Credentials + JWT 驗簽）
- [API] 內容站台以 `client_credentials` 呼叫 `POST /oauth/token` 取得 access_token（`usage=send_api`）
- [API] 內容站台帶 Bearer token 呼叫 Send Engine 建立發送任務
- [API] Send Engine 以 Member Center JWKS 驗簽 token
- [API] 驗證 `scope/tenant_id/exp` 通過後才受理任務

## F-02c 檔案上傳（A service -> File Space）
- [API] `A service` 以 `client_credentials` 向 Member Center 取得 access token
- [API] token 需包含 `files:upload.write`
- [API] token 必須包含有效 `tenant_id`，並以 `aud=file_access_api` 為目標資源；缺少或不符時拒絕
- [API] `A service` 帶 token 呼叫 access agent / file space 上傳檔案
- [API] access agent 以 JWKS 驗簽 JWT，並驗 `iss/aud/exp/scope/tenant_id`

## F-02d 檔案下載（A service -> client -> File Space）
- [API] client 向 `A service` 請求下載檔案
- [API] `A service` 以自己的商業邏輯驗證該 user 是否可讀取指定檔案
- [API] `A service` 以 `files:download.delegate` 呼叫 Member Center `POST /file-access/download-tokens` 取得短效 download token
- [API] download token 由 Member Center 簽發，至少綁定 `tenant_id + user_id + file_id/object_key + method=GET + exp`
- [UI/API] `A service` 將帶短效 token 的下載 URL 回給 client
- [UI/API] client 直接向 access agent / file space 請求檔案
- [API] access agent 以 `files:download.read` 呼叫 Member Center `POST /file-access/download-tokens/validate`，確認 token 與實際 GET request 邊界一致後放行
- [API] issuing 與 validation 都採 tenant fail-closed；Bearer token 缺少或帶錯 `tenant_id` 時不得簽發或驗證成功

註記：下載流程不直接暴露一般 S2S token 給 client。

## F-03 忘記密碼 / 重設密碼
- [API] 站點送出 `POST /auth/password/forgot`
- [UI] 會員中心頁提交 email 並發送重設信
- [API/UI] 使用 token 進入重設密碼頁

註記：目前 Web UI 已實作 forgot/reset 流程，但尚未串接 email 發送；開發階段會直接顯示 reset token 與 reset 連結。

## F-03b 已登入修改密碼
- [UI] 使用者登入後進入 `/account/changepassword`
- [UI] 輸入目前密碼與新密碼
- [UI] 會員中心驗證目前密碼後更新密碼
- [UI] 更新成功後刷新登入狀態

## F-04 訂閱電子報（未登入）
- [API] 站點以 `tenant_api` client credentials 取得含 `newsletter:subscriptions.write` 的 token
- [API] 站點帶 Bearer token 送出 `POST /newsletter/subscribe`；token tenant 必須擁有指定清單
- [API] 會員中心建立 pending 訂閱並發送驗證信
- [UI] 使用者點擊驗證信連結 `/newsletter/confirm?token=...`
- [UI] 訂閱改為 active，發出 event `subscription.activated`

## F-05 取消訂閱（單一清單）
- [API] 站點以 tenant Bearer token 與 `list_id + email` 呼叫 `POST /newsletter/unsubscribe-token` 取得 token；需 `newsletter:subscriptions.write`
- [UI] 使用者點擊退訂連結 `/newsletter/unsubscribe?token=...`
- [UI] 訂閱狀態改為 unsubscribed
- [API] 發出 event `subscription.unsubscribed`

## F-05b One-Click 退訂 Token（Send Engine 發信前）
- [API] Send Engine 以 `tenant_id + list_id + subscriber_id` 呼叫 `POST /newsletter/one-click-unsubscribe-token`
- [API] 或以 `tenant_id + list_id + subscriber_ids[]` 呼叫 `POST /newsletter/one-click-unsubscribe-tokens` 批次取得
- [API] Member Center 回傳 one-click `unsubscribe_token`
- [API] Send Engine 將 token 置入信件 `List-Unsubscribe` URL

## F-06 訂閱偏好管理（租戶站台 S2S）
- [API] 站點以 tenant Bearer token 與 `list_id + email` 讀取 `/newsletter/preferences`；需 `newsletter:subscriptions.read`
- [API] 站點以 tenant Bearer token 與 `list_id + email` 更新 `/newsletter/preferences`；需 `newsletter:subscriptions.write`
- [API] Member Center 驗證 token tenant 擁有指定清單，缺少或不符時拒絕
- [UI] 會員中心提供偏好頁（可選）

## F-06b 我的電子報訂閱管理（登入後）
- [UI] 使用者登入後進入 `/profile/subscriptions`
- [API/UI] 會員中心依目前登入 user 查出其已綁定的訂閱清單
- [UI] 顯示各租戶 / 清單 / 狀態 / 訂閱時間
- [UI] 使用者可直接按下取消訂閱
- [API/UI] 系統驗證該訂閱屬於目前登入 user 後直接退訂
- [API/UI] 不需再次 email token 驗證
- [API] 發出 event `subscription.unsubscribed`

## F-10 Send Engine 事件同步（Member Center → Send Engine）
- [API] Member Center 以 webhook 推送 `subscription.activated/unsubscribed/preferences.updated`（scope: `newsletter:events.write`）
- [API] Header 使用 `X-Signature` / `X-Timestamp` / `X-Nonce` / `X-Client-Id`
- [API] `X-Client-Id` 對應 Send Engine `auth_clients.id`（UUID）
- [API] Member Center 從 DB 的 tenant 設定讀取對應 webhook client id
- [API] Send Engine 驗證簽章 + timestamp + nonce（重放防護）後入庫
- [API] Send Engine 更新名單快照

## F-10b 訂閱名單讀取（Send Engine → Member Center）
- [API] tenant client 以 `newsletter:list.read` 呼叫 `GET /newsletter/subscriptions?list_id=...`
- [API] Member Center 驗證 token `tenant_id` 與清單 tenant 一致；claim 缺少、格式錯誤或不符時拒絕
- [API] 平台級跨 tenant 同步必須使用獨立的 `newsletter:list.read.global`

## F-11 黑名單回寫（Send Engine → Member Center）
- [API] Send Engine 依事件規則處理：
- [API] `hard_bounce` / `soft_bounce_threshold` / `suppression`：回寫後由 Member Center 取消該 email 的所有訂閱（跨租戶）並加入黑名單
- [API] `complaint`：回寫後由 Member Center 僅取消該筆訂閱，不加入黑名單
- [API] 呼叫 `POST /subscriptions/disable`：
- [API] tenant client 用 `newsletter:events.write`
- [API] 平台 client（SES 聚合事件）用 `newsletter:events.write.global`
- [API] body 需含 `tenant_id + subscriber_id + list_id + reason + disabled_by + occurred_at`
- [API] `hard_bounce` / `soft_bounce_threshold` / `suppression` 才會寫入 `email_blacklist`；`complaint` 不寫黑名單

## F-12 Webhook Client Mapping 回填（Send Engine → Member Center）
- [API] Send Engine 建立/更新 tenant 對應的 webhook client（`auth_clients.id`）
- [API] 呼叫 `POST /integrations/send-engine/webhook-clients/upsert`（scope: `newsletter:events.write.global`）
- [API] Member Center 更新 tenant 設定（DB）

## F-07 會員資料查看
- [API] 站點讀取 `/user/profile`
- [API] `/user/profile`、`/user/addresses`、`/user/subscriptions` 僅能存取 access token subject 對應的會員資料
- [API] Email 不是公開識別資料，不提供 by-email 跨會員查詢端點
- [UI] 會員中心提供個人資料頁

## F-07b 會員資料維護
- [UI] 使用者登入後進入 `/profile`
- [UI] 維護電話、公司、統編等基本資料
- [API/UI] 儲存會員基本資料

## F-07c 地址簿管理
- [UI] 使用者登入後進入 `/profile/addresses`
- [UI] 新增、編輯、刪除收貨地址
- [UI] 可設定預設地址
- [API/UI] 地址簿資料綁定目前登入 user
- [API/UI] 若只剩最後一筆地址，不允許刪除

## F-07d 其他服務讀取會員資料
- [API] 其他服務以 access token 呼叫會員中心 profile API
- [API] token 需帶對應 profile scopes
- [API] 會員中心依 scope 決定可讀取基本資料、地址簿或訂閱資料
- [API] service API 為主要模式，只要 client 具對應 scope 即可存取對應資料

## F-08 管理者管理租戶/清單/Client
- [UI] 會員中心管理後台進行 CRUD

## F-09 訂閱與會員綁定
- [API] 使用者完成註冊後，會員中心將訂閱資料與 user_id 綁定
- [API] 發送事件 `subscription.linked_to_user`
