# OpenAPI 草案(完整) 已補上完整端點與資料結構,並提供 `docs/openapi.yaml` 作為可直接擴充的版本。 ## 版本 - OpenAPI: 3.1.0 - 檔案:`docs/openapi.yaml` ## 核心資源 - OAuth2/OIDC:授權、token、discovery、JWKS - Auth:註冊、登入(password grant)、刷新、登出、忘記/重設密碼、Email 驗證 - User:個人資料 - Newsletter:訂閱/確認/退訂/偏好 - Admin:Tenants/Lists/OAuth Clients(MVP CRUD) ## Security Schemes - OAuth2 (Authorization Code + PKCE) - Bearer JWT(API 使用) ## 補充說明 - `/oauth/token`、`/auth/login`、`/auth/refresh` 使用 `application/x-www-form-urlencoded` - `/auth/email/verify` 需要 `token` + `email` - `/newsletter/subscribe` 會回傳 `confirm_token` - `/newsletter/unsubscribe-token` 需要 `list_id + email` 才能申請 `unsubscribe_token` - `/newsletter/preferences`(GET/POST)需要 `list_id + email`,避免跨租戶資料讀取/更新 ## 通用欄位 - `occurred_at`:RFC3339(例:`2026-02-10T09:30:00Z`) - `event_id`、`request_id`:UUID ## 通用錯誤格式 ```json { "error": "string_code", "message": "human readable message", "request_id": "uuid" } ``` ## 多租戶資料隔離原則 - 與訂閱者資料(preferences、unsubscribe token)相關的查詢與寫入,一律必須帶 `list_id + email` 做租戶邊界約束。 - 不提供僅靠 `email` 或單純 `subscription_id` 的公開查詢/操作端點。 ## Webhook Auth(Member Center -> Send Engine) - Header(對齊 Send Engine 規格): - `X-Signature` - `X-Timestamp` - `X-Nonce` - `X-Client-Id` - `X-Client-Id` 來源: - 由 Send Engine 的 `auth_clients.id`(UUID)提供 - Member Center 以 `SendEngine__TenantWebhookClientIds__{tenant_uuid}` 設定每個租戶的對應值 - 簽章建議: - `HMAC-SHA256(secret, "{raw_body}")`(對齊 Send Engine 驗證器) - 驗證規則: - timestamp 在允許時間窗(例如 ±5 分鐘) - nonce 不可重複(防重放) - `X-Client-Id` 必須存在且 active,且 `auth_clients.tenant_id` 與 payload `tenant_id` 一致 - 不使用 `X-Client-Id` fallback;缺少 tenant 對應 client 時應略過發送 - 預設拒絕 `auth_clients.tenant_id = NULL` 的通用 client(除非 Send Engine 明確開啟) - signature 必須匹配 ## OAuth Client 用途分離(強制) - `usage=tenant_api`: - 供租戶站台拿 token 呼叫 Member Center / Send Engine API - scope 僅給業務所需(如 `newsletter:send.write`、`newsletter:list.read`) - `usage=webhook_outbound`: - 供 Member Center 內部標記「對外 webhook 用」的租戶憑證用途 - 不可用於租戶 API 呼叫 - `X-Client-Id` 仍以 Send Engine `auth_clients.id` 為準 - 管理規則: - 每個 tenant 至少 2 組憑證(`tenant_api` / `webhook_outbound`) - secret 分開輪替,禁止共用 ## 整合 API / Auth(狀態) ### API - `GET /newsletter/subscriptions?list_id=...`:已實作(供發送引擎同步) - `POST /webhooks/subscriptions`:已實作(Member Center 發送;Send Engine 接收) - `POST /webhooks/lists/full-sync`:規格已定義(Member Center 發送;Send Engine 接收) - `POST /subscriptions/disable`:已實作(Send Engine 回寫黑名單) ### Auth / Scope - OAuth Client 需綁定 `tenant_id` - 新增 scope: - `newsletter:list.read` - `newsletter:events.read` - `newsletter:events.write` - 發送引擎僅能用上述 scope,禁止 admin 權限 - `POST /subscriptions/disable` 需 Bearer token 且包含 `newsletter:events.write` - 建議 Send Engine 使用 client credentials 取 token,不建議使用長效固定 token ### 回寫原因碼(Send Engine -> Member Center) - `hard_bounce` - `soft_bounce_threshold` - `complaint` - `suppression` ### `/subscriptions/disable` 請求欄位(Send Engine -> Member Center) - `tenant_id`(UUID) - `subscriber_id`(UUID) - `list_id`(UUID) - `reason`(`hard_bounce | soft_bounce_threshold | complaint | suppression`) - `disabled_by`(建議固定 `send_engine`) - `occurred_at`(RFC3339) Member Center 會用 `subscriber_id + list_id` 查詢訂閱,再驗證 `tenant_id` 邊界;驗證通過後才寫入全域 email 黑名單。