# 安裝流程（Installer + 管理者帳號）

此文件定義「首次安裝」與「既有環境管理」兩種情境，並以 EF Core Code First 為主。

完整環境變數、執行期 options、DB 管理設定與預設值請見 `docs/CONFIGURATION.md`。
現有 OAuth scopes、resource 與 client usage mapping 請見 `docs/SCOPES.md`。

## 目標
- 透過 EF Core migrations 建立/更新 DB schema
- 建立或更新 superuser 帳號
- 已設定 DB 連線時，可重複執行（不重建 schema）

## 安裝模式

### A. 首次安裝（Init）
1) 輸入 DB 連線資訊
2) 連線檢查與版本檢查
3) 建立/更新 schema（EF Core migrations）
4) 建立 superuser（預設帳號 admin，可自訂）
5) 建立必要 seed（roles 等）

### B. 既有環境（管理）
- 建立額外 superuser
- 強制重設 superuser 密碼
- 只執行 migration（差異安裝），不重建 schema

## CLI 指令規格（實作）

### 通用參數
- `--connection-string <string>`: DB 連線字串
- `--appsettings <path>`: 設定檔路徑（讀/寫 `ConnectionStrings:Default`）
- `--no-prompt`: 不使用互動輸入（CI/CD）
- `--verbose`: 詳細輸出

### 環境變數（建議用於部署）
- `ConnectionStrings__Default`: 主要連線字串（優先）
- `MEMBERCENTER_CONNECTION`: 備用連線字串
- API / Web 在非 Development 環境必須提供 `ConnectionStrings__Default`，缺少或空值時會拒絕啟動；localhost 預設值僅供 Development。

若在開發環境（`ASPNETCORE_ENVIRONMENT=Development` 或 `DOTNET_ENVIRONMENT=Development`），
可以建立 `.env` 檔案，installer 與 API 會在啟動時讀取（僅限開發環境）。
建議在 `.env` 內加入：

```
ASPNETCORE_ENVIRONMENT=Development
ConnectionStrings__Default=Host=localhost;Database=member_center;Username=postgres;Password=postgres
Auth__Issuer=http://localhost:7850/
Auth__WebLoginUrl=http://localhost:5080/account/login
Auth__AllowedLoginReturnUrlPrefixes=http://localhost:7850/
Auth__AllowedLogoutReturnUrlPrefixes=http://localhost:5243/
Auth__AllowInternalHttpTokenEndpoint=false
Auth__Certificates__Signing__Path=
Auth__Certificates__Signing__Password=
Auth__Certificates__Encryption__Path=
Auth__Certificates__Encryption__Password=
DataProtection__Certificate__Path=
DataProtection__Certificate__Password=
ReverseProxy__KnownProxies=
ReverseProxy__KnownNetworks=
ReverseProxy__ForwardLimit=1
Auth__Resources__MemberCenter__Audience=member_center_api
Auth__Resources__SendEngine__Audience=send_engine_api
Auth__Resources__FileAccess__Audience=file_access_api
SendEngine__BaseUrl=http://localhost:6060
SendEngine__WebhookSecret=change-me
```

相容性說明：
- 現行程式已優先使用 resource registry 與目標型態：
  - `Auth__Resources__MemberCenter__Audience`
  - `Auth__Resources__SendEngine__Audience`
  - `Auth__Resources__FileAccess__Audience`
- 舊 key 仍保留相容讀取：
  - `Auth__MemberCenterAudience`
  - `Auth__SendEngineAudience`
- 規劃上將收斂為 DB resource registry；`.env` 僅作為初始 seed / 部署覆寫來源，不應再為每個新服務新增平行 hardcoded key。
- `File Access` 已直接採用 resource registry 形式，不新增第三組硬編碼 audience 判斷。

OIDC / Redirect login 設定說明：
- `Auth__WebLoginUrl`: API `/oauth/authorize` 未登入時導向的 Web login URL。
- `Auth__AllowedLoginReturnUrlPrefixes`: Web login 成功後允許 redirect 回去的 URL prefix，通常填 API issuer/base URL。
- `Auth__AllowedLogoutReturnUrlPrefixes`: Web logout 後允許 redirect 的 URL prefix；不會同時成為 login allowlist。
- Return URL allowlist 會結構化比對 `scheme + host + port + path segment prefix`，設定值不得含 userinfo、query 或 fragment；例如 `https://example.com/app` 不會允許 `https://example.com.attacker.tld` 或 `/application`。
- Identity cookie 固定使用 `SameSite=None`、`Secure=Always`、`Path=/`，因此 stage/prod 必須使用 HTTPS。
- AWS 架構由 ALB / CloudFront 終止 TLS，並負責 HTTP→HTTPS redirect 與 HSTS；Member Center 私有 listener 使用 HTTP，不在應用程式層 redirect，避免私有網域、health check 與 S2S 呼叫形成循環。
- 非 Development 的 `Auth__Issuer` 為必填且必須使用 canonical HTTPS URL；缺少或使用 HTTP 時 API 拒絕啟動。
- 同 VPC 服務若需直接呼叫私有 HTTP `/oauth/token`，設定 `Auth__AllowInternalHttpTokenEndpoint=true`；此設定只放寬 OAuth endpoint transport，不放寬 issuer 或外部 return URL。
- 私有 HTTP listener 必須以 Security Group 限制來源；CMS 可使用內部 HTTP token endpoint，但驗證 token 時仍必須接受 canonical HTTPS issuer。
- 非 Development 若 `Auth__Issuer` 不是 HTTPS，API 會拒絕啟動；Login / Logout 的外部 HTTP return URL 也會被拒絕。
- TLS 終止於 reverse proxy 時，必須正確設定 trusted proxy，讓應用程式只接受 ALB / ingress 提供的 `X-Forwarded-Proto`。

Reverse proxy 信任設定：
- 未設定 `ReverseProxy__KnownProxies` / `ReverseProxy__KnownNetworks` 時，API 與 Web 完全忽略 `X-Forwarded-For`、`X-Forwarded-Proto`。
- `ReverseProxy__KnownProxies` 使用逗號分隔 IP，例如 `10.0.0.10,10.0.0.11`。
- `ReverseProxy__KnownNetworks` 使用逗號分隔 CIDR，例如 `10.0.0.0/24,fd00::/64`。
- `ReverseProxy__ForwardLimit` 預設為 `1`，只應設為實際 proxy hop 數，允許範圍 `1–5`。
- 不可填入 `0.0.0.0/0` 或 `::/0`；正式環境只信任 load balancer / ingress 的固定 IP 或內部網段。

Web security headers：
- Web 全域送出 self-only Content Security Policy，禁止 object、外部 frame 與 inline script/style。
- 同時送出 `X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`、`Referrer-Policy: no-referrer` 與受限 `Permissions-Policy`。
- 新增外部資產或第三方前端服務時，必須先明確調整 CSP，不可直接加入 `'unsafe-inline'` 或萬用來源。

SMTP 密碼儲存：
- `smtp_password` 以 `protected:v1:` 開頭的 Data Protection ciphertext 儲存，不需要新增 migration 或重建既有 DB。
- 舊版明碼會在第一次讀取 SMTP 設定時自動加密；開啟 `/admin/security`、寄信或送測試信都會觸發。
- `DataProtectionKeys` 是解密必要資料，不可任意清空；Production 應再以外部憑證保護 key ring，並將憑證納入備份與輪替程序。

Production 憑證：
- 非 Development 啟動 API 時必須提供 `Auth__Certificates__Signing__Path/Password` 與 `Auth__Certificates__Encryption__Path/Password`；不可再使用 development certificates。
- API 與 Web 都必須提供相同的 `DataProtection__Certificate__Path/Password`，用來保護共用 DB key ring。
- 憑證檔必須是含 private key、在有效期內的 PFX；密碼只可由 secret manager / environment 注入，不可提交到 repo。
- signing、encryption 與 Data Protection 憑證建議分離；輪替時需保留仍用於驗證舊 token／解密舊資料的前一代憑證，完成相容輪替後才能移除。
- 前代憑證以 `__Previous__0__Path/Password` 設定，可依序增加 `Previous__1__...`；目前憑證用於新簽發／新 key，前代憑證保留舊 token 驗證與 key 解密能力。
- 應監控憑證到期日並先在 Stage 驗證；路徑錯誤、缺 private key或已過期時應用程式會拒絕啟動。
- 此處監控的是 OpenIddict signing / encryption 與 Data Protection PFX；對外 TLS/SSL 憑證若由 AWS ACM 管理，續期與告警由 ACM / AWS 邊界負責。
- `Certificates__ExpirationWarningDays` 預設 `30`（允許 `1–180`）；進入期限後 API / Web 啟動會寫入 warning log，Production 應以 CloudWatch metric filter / alarm 監控該 warning。

安全回歸檢查：
```bash
dotnet test tests/MemberCenter.Security.Tests/MemberCenter.Security.Tests.csproj
```
此標準 xUnit test project 目前涵蓋 return URL 邊界、HTTPS 限制、trusted proxy fail-closed / CIDR、憑證必要設定與 security options 預設/驗證。

`SendEngine` 設定說明：
- `SendEngine__BaseUrl`: Send Engine API base URL
- `SendEngine__WebhookSecret`: 與 Send Engine `Webhook:Secrets:member_center` 一致
- tenant 對應 `X-Client-Id` 改由 DB 管理（Tenant 設定）
- 可透過管理 UI 設定，或由 Send Engine 呼叫 `POST /integrations/send-engine/webhook-clients/upsert` 自動回填

測試旗標（TEST-ONLY）：
- `Testing__DisableSubscriptionDryRunNoDb=true`
  - 作用：`POST /subscriptions/disable` 完全不做 DB read/write，只寫 log（含預計執行的 DB 動作）
  - 用途：SES/Send Engine 串接初次測試，避免測試資料污染
  - 測試結束請改回 `false`

### 1) `installer init`
用途：首次安裝（含 migrations + seed + superuser）

參數：
- `--admin-email <email>`: 預設 `admin@example.com`
- `--admin-password <string>`: 預設需互動輸入
- `--admin-display-name <string>`: 可選
- `--force`: 若偵測已初始化，仍強制執行

流程：
1) 解析連線字串（參數或 appsettings）
   - 若提供 `--connection-string`，會寫入 appsettings
   - 若 appsettings 中缺少連線字串，會互動式詢問並寫入
   - 若設定環境變數，會優先使用環境變數（不寫入 appsettings）
2) 執行 migrations（不 Drop）
3) 建立 roles（superuser, admin, support）
4) 建立使用者（不存在才建立）並加入 `superuser` 角色
5) 寫入安裝鎖定（DB flag: `system_flags` / `installed=true`）

### 2) `installer add-superuser`
用途：新增或提升 superuser

參數：
- `--admin-email <email>`
- `--admin-password <string>`
- `--admin-display-name <string>`

流程：
1) 解析連線字串
2) 建立使用者並指派 `superuser` 角色

相容性：
- 舊指令 `installer add-admin` 仍保留為 alias，目前語意等同 `installer add-superuser`

### 3) `installer reset-superuser-password`
用途：重設指定 superuser 密碼

參數：
- `--admin-email <email>`
- `--admin-password <string>`

流程：
1) 解析連線字串
2) 更新密碼（強制）

相容性：
- 舊指令 `installer reset-admin-password` 仍保留為 alias，目前語意等同 `installer reset-superuser-password`

### 4) `installer migrate`
用途：只執行 migrations

參數：
- `--target <migration>`: 指定遷移（可選）

流程：
1) 解析連線字串
2) 執行 migrations（可指定 target）

## 安全注意
- 密碼必須符合強度規則
- 初次安裝完成後，禁用安裝入口或限內網
- 安裝過程需紀錄 audit log

## Docker / 部署建議
- 建議用環境變數提供連線字串（避免重建 container 後設定遺失）
- 範例：

```yaml
environment:
  ConnectionStrings__Default: "Host=postgres;Database=member_center;Username=postgres;Password=postgres"
```

- 若仍要用 `appsettings.json`，請用 volume 維持設定檔
