# 系統設計草案(Member Center) 日期:2026-01-30 ## 1. 需求摘要(已確認) 1) 多個網站共用的會員登入中心(SSO) 2) 電子報訂閱管理(發送另建) 3) 未註冊會員可訂閱;註冊後沿用訂閱資料 4) 未註冊會員可取消訂閱(單一清單退訂) 5) 登入需支援 API 與 Redirect 兩種方式(OAuth2 + OIDC) ## 2. 架構原則 - OAuth2 + OIDC(Authorization Code + PKCE) - 會員中心只管理 Email 與訂閱狀態 - Double Opt-in - 各站自行設計 UI,主要走 API;少數狀況使用 redirect - 多租戶為邏輯隔離,但會員資料跨站共享 - 訂閱狀態同步採 event/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, created_at - roles / user_roles (Identity) - id, name, created_at - OpenIddictApplications - 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_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 ## 6. 核心流程 ### 6.1 OAuth2/OIDC Redirect 登入(Authorization Code + PKCE) 1) 站點導向 `/oauth/authorize`,帶 `client_id`, `redirect_uri`, `code_challenge`, `scope=openid email` 2) 使用者於會員中心登入 3) 成功後導回 `redirect_uri` 並附 `code` 4) 站點以 `code` + `code_verifier` 向 `/oauth/token` 換取 token + `id_token` ### 6.2 OAuth2 API 使用(站點自行 UI) 1) 站點以 API 驗證使用者登入(會員中心提供 login API) 2) 成功後取得 token(含 ID Token 可選) 3) 站點以 access_token 呼叫其他會員中心 API ### 6.3 未登入狀態的訂閱流程(在獨立平台) 1) 使用者在各站點輸入 Email 並選擇訂閱清單 2) 站點呼叫 `POST /newsletter/subscribe` 3) 會員中心建立 `pending` 訂閱並發送驗證信(透過外部發信系統) 4) 使用者點擊信件連結 `/newsletter/confirm?token=...` 5) 訂閱狀態改為 `active` 6) 會員中心發出事件 `subscription.activated` 到 event/queue ### 6.4 未登入退訂(單一清單) 1) 信件提供「一鍵退訂」連結 `/newsletter/unsubscribe?token=...` 2) 驗證 token 後將該訂閱標記為 `unsubscribed` 3) 會員中心發出事件 `subscription.unsubscribed` 到 event/queue ### 6.5 註冊後銜接 1) 使用者完成註冊 2) 系統搜尋 `newsletter_subscriptions.email` 3) 將 `user_id` 補上並保留偏好 4) 可選:發出事件 `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` - GET `/newsletter/preferences` - POST `/newsletter/preferences` ## 8. 安全與合規 - 密碼強度與防暴力破解(rate limit + lockout) - Token rotation + refresh token revoke - Redirect URI 白名單 + PKCE - Double opt-in(可配置) - Audit log - GDPR/CCPA:資料匯出與刪除(規劃中) ## 9. 其他文件 - `docs/UI.md` - `docs/USE_CASES.md` - `docs/FLOWS.md` - `docs/OPENAPI.md` - `docs/SCHEMA.sql` - `docs/TECH_STACK.md`