- Updated OpenAPI documentation to include new OAuth2 usage types: `send_api`. - Added endpoints for issuing one-click unsubscribe tokens in both single and batch requests. - Modified OAuth client creation and management to enforce new usage types and redirect URI requirements. - Implemented logic in the Newsletter service to handle one-click unsubscribe token issuance. - Updated UI to reflect changes in OAuth client usage options and redirect URI handling. - Enhanced token generation logic to support new scopes and audience settings for Send Engine.
8.4 KiB
8.4 KiB
系統設計草案(Member Center)
日期:2026-01-30
1. 需求摘要(已確認)
- 多個網站共用的會員登入中心(SSO)
- 電子報訂閱管理(發送另建)
- 未註冊會員可訂閱;註冊後沿用訂閱資料
- 未註冊會員可取消訂閱(單一清單退訂)
- 登入需支援 API 與 Redirect 兩種方式(OAuth2 + OIDC)
2. 架構原則
- OAuth2 + OIDC(Authorization Code + PKCE)
- 會員中心只管理 Email 與訂閱狀態
- Double Opt-in
- 各站自行設計 UI,主要走 API;少數狀況使用 redirect
- 多租戶為邏輯隔離,但會員資料跨站共享
- 公開訂閱端點必須使用
list_id + email做資料邊界,禁止僅以email查詢或操作 - 訂閱狀態同步目前採 webhook(event payload);queue 為後續可選擴充
- PostgreSQL
- 實作:C# .NET Core + MVC + OpenIddict
3. 使用者故事(精簡)
- 訪客:在任一站點未登入狀態下輸入 Email 訂閱電子報
- 訪客:在信件中點擊連結完成 double opt-in
- 訪客:點擊取消訂閱連結即可退訂(單一清單)
- 會員:在任一站點登入後,其他站點可無痛登入(SSO)
- 站點後台:可管理站點資訊、訂閱清單、會員基本資料
4. 核心模組
- Identity Service:註冊、登入、密碼重設、Email 驗證
- OAuth2/OIDC Service:授權流程、token 發放、ID Token
- Subscription Service:訂閱/退訂/偏好管理
- Admin Console:租戶與清單管理
- Mailer Integration:驗證信/退訂信/確認信的發送介面(外部系統)
- Event Publisher:訂閱事件發佈(供發信系統或數據系統消費)
5. 資料模型(概念)
- tenants
- id, name, domains, status, created_at
- users (ASP.NET Core Identity)
- id, user_name, email, password_hash, email_confirmed, lockout, is_blacklisted, blacklisted_at, blacklisted_by, created_at
- roles / user_roles (Identity)
- id, name, created_at
- OpenIddictApplications
- id, client_id, client_secret, display_name, permissions, redirect_uris, properties(含
tenant_id,usage=tenant_api|send_api|webhook_outbound|platform_service)
- id, client_id, client_secret, display_name, permissions, redirect_uris, properties(含
- OpenIddictAuthorizations
- id, application_id, status, subject, type, scopes
- OpenIddictTokens
- id, application_id, authorization_id, subject, type, status, expiration_date
- OpenIddictScopes
- id, name, display_name, resources
- newsletter_lists
- id, tenant_id, name, status, created_at
- newsletter_subscriptions
- id, list_id, email, user_id (nullable), status, preferences, created_at
- email_blacklist
- id, email, reason, blacklisted_at, blacklisted_by
- email_verifications
- id, email, tenant_id, token_hash, purpose, expires_at, consumed_at
- unsubscribe_tokens
- id, subscription_id, token_hash, expires_at, consumed_at
- audit_logs
- id, actor_type, actor_id, action, payload, created_at
- system_flags
- id, key, value, updated_at
關聯說明:
- newsletter_subscriptions.email 與 users.email 維持唯一性關聯
- 使用者註冊時,如 email 存在訂閱紀錄,補上 user_id
- 單一清單退訂:unsubscribe token 綁定 subscription_id
- blacklist 記錄於 email_blacklist(全租戶共用)
6. 核心流程
6.1 OAuth2/OIDC Redirect 登入(Authorization Code + PKCE)
- 站點導向
/oauth/authorize,帶client_id,redirect_uri,code_challenge,scope=openid email - 使用者於會員中心登入
- 成功後導回
redirect_uri並附code - 站點以
code+code_verifier向/oauth/token換取 token +id_token
6.2 OAuth2 API 使用(站點自行 UI)
- 站點以 API 驗證使用者登入(會員中心提供 login API)
- 成功後取得 token(含 ID Token 可選)
- 站點以 access_token 呼叫其他會員中心 API
6.3 未登入狀態的訂閱流程(在獨立平台)
- 使用者在各站點輸入 Email 並選擇訂閱清單
- 站點呼叫
POST /newsletter/subscribe - 會員中心建立
pending訂閱並發送驗證信(透過外部發信系統) - 使用者點擊信件連結
/newsletter/confirm?token=... - 訂閱狀態改為
active - 會員中心發出事件
subscription.activated到 event/queue
6.4 未登入退訂(單一清單)
- 信件提供「一鍵退訂」連結
/newsletter/unsubscribe?token=... - 驗證 token 後將該訂閱標記為
unsubscribed - 會員中心發出事件
subscription.unsubscribed到 event/queue
6.4b Send Engine 發信前申請 One-Click Token
- Send Engine 依收件者呼叫
POST /newsletter/one-click-unsubscribe-token,或批次呼叫POST /newsletter/one-click-unsubscribe-tokens - body 帶
tenant_id + list_id + subscriber_id(批次版為subscriber_ids[]) - 會員中心簽發 token(與手動退訂 token 分離 purpose)
- Send Engine 將 token 寫入
List-Unsubscribe連結
6.6 Send Engine 事件同步(Member Center → Send Engine)
- Member Center 發出事件(
subscription.activated/subscription.unsubscribed/preferences.updated) - 以 webhook 推送至 Send Engine(簽章與重放防護)
X-Client-Id使用 Send Engineauth_clients.id(可按 tenant 做設定覆蓋)- Send Engine 驗證 tenant scope,更新本地名單快照
6.7 Send Engine 退信/黑名單回寫(選用)
- Send Engine 依事件類型決定回寫時機與原因碼:
hard_bounce/soft_bounce_threshold/suppression:設黑名單後回寫complaint:先取消訂閱,再回寫黑名單- 呼叫 Member Center API 將 email 加入
email_blacklist - Member Center 對該 email 的訂閱事件全部忽略且不再推送
- 回寫請求需帶
tenant_id + subscriber_id + list_id,Member Center 端做租戶邊界驗證 - 回寫授權可用:
- tenant client scope:
newsletter:events.write - platform client scope:
newsletter:events.write.global(SES 聚合事件)
- tenant client scope:
6.5 註冊後銜接
- 使用者完成註冊
- 系統搜尋
newsletter_subscriptions.email - 將
user_id補上並保留偏好 - 可選:發出事件
subscription.linked_to_user
7. API 介面(草案)
- GET
/oauth/authorize - POST
/oauth/token - GET
/.well-known/openid-configuration - POST
/auth/login(API-only login) - POST
/auth/refresh - POST
/newsletter/subscribe - GET
/newsletter/confirm - POST
/newsletter/unsubscribe - POST
/newsletter/unsubscribe-token - POST
/newsletter/one-click-unsubscribe-token - POST
/newsletter/one-click-unsubscribe-tokens - GET
/newsletter/preferences - POST
/newsletter/preferences - POST
/webhooks/subscriptions(Send Engine 端點,Member Center 呼叫) - POST
/webhooks/lists/full-sync(Send Engine 端點,Member Center 呼叫) - POST
/subscriptions/disable(Member Center 端點,Send Engine 呼叫) - POST
/integrations/send-engine/webhook-clients/upsert(Member Center 端點,Send Engine 呼叫)
7.1 進度狀態(2026-02)
API
GET /newsletter/subscriptions?list_id=...:已實作POST /webhooks/subscriptions:已實作(Member Center 發送)POST /subscriptions/disable:已實作POST /integrations/send-engine/webhook-clients/upsert:已實作POST /webhooks/lists/full-sync:尚未實作(保留規格)
Auth / Scope
tenant_api/send_api/webhook_outboundOAuth Client 綁定tenant_id,所有清單/事件 API 需驗證租戶邊界- OAuth Client 需區分用途:
tenant_api/send_api/webhook_outbound/platform_service(禁止混用) - 新增 scope:
newsletter:list.read、newsletter:send.write、newsletter:send.read、newsletter:events.read - 新增 scope:
newsletter:events.write - 新增 scope:
newsletter:events.write.global - JWT Access Token 已改為 JWS(
DisableAccessTokenEncryption),供 Send Engine 以 JWKS 驗簽
租戶端取 Token(Client Credentials)
- 租戶使用 OAuth Client Credentials 向 Member Center 取得 access token
- token 內含
tenant_id與 scope - Send Engine 收到租戶請求後以 JWKS 驗簽 JWT(JWS)
- 驗簽通過後將
tenant_id固定在 request context,不接受 body 覆寫
8. 安全與合規
- 密碼強度與防暴力破解(rate limit + lockout)
- Token rotation + refresh token revoke
- Redirect URI 白名單 + PKCE
- Double opt-in(可配置)
- Audit log
- GDPR/CCPA:資料匯出與刪除(規劃中)
9. 其他文件
docs/UI.mddocs/USE_CASES.mddocs/FLOWS.mddocs/OPENAPI.mddocs/SCHEMA.sqldocs/TECH_STACK.md