# OAuth Scope 現況

本文只記錄目前程式已註冊與 seed 的 scope。Client Credentials 與 Authorization Code 實際核發的 scope 由 OAuth application `usage` 對應的 DB mapping 決定，不接受呼叫端自行擴權。修改預設 mapping 後，既有環境需執行 `sync-oauth-clients`。

## 授權邊界

- `profile:*` 是 current-user scope，資料主體固定為 access token 的 `sub`，不能查詢其他會員。
- Email 不是跨會員 lookup key；目前沒有 by-email service API。
- Tenant scope 必須帶有效 `tenant_id`，且與 request/list tenant 相符；缺少或不符時 fail-closed。
- `.global` scope 不依賴 tenant claim，只應授予平台級 confidential client。
- Superuser/Admin 後台權限不使用 OAuth scopes，而使用 Web role + Admin Permission。

## Resource / Audience

| Resource | 預設 audience | Tenant | Scopes |
|---|---|---|---|
| `member_center_api` | `member_center_api` | scope 個別判定 | OIDC、`profile:*`、`newsletter:list.*`、`newsletter:subscriptions.*`、`newsletter:events.*` |
| `send_engine_api` | `send_engine_api` | 必須 | `newsletter:send.read`、`newsletter:send.write` |
| `file_access_api` | `file_access_api` | 必須 | `files:*` |

Token audience 由 scope → resource registry 計算。

## Scope Catalog

### OIDC

| Scope | 能力 |
|---|---|
| `openid` | OpenID Connect subject / ID token。 |
| `email` | Email claim。 |
| `profile` | 標準 OIDC profile scope；實際會員 API 仍需細分的 `profile:*`。 |

### Current-user Profile

| Scope | 能力 |
|---|---|
| `profile:basic.read` | 讀取目前登入會員基本資料。 |
| `profile:basic.write` | 修改目前登入會員基本資料。 |
| `profile:addresses.read` | 讀取目前會員地址簿。 |
| `profile:addresses.write` | 新增、修改、刪除目前會員地址。 |
| `profile:subscriptions.read` | 讀取目前會員的電子報訂閱。 |
| `profile:subscriptions.write` | 取消目前會員自己的訂閱。 |

### Newsletter / Member Center

| Scope | Tenant 規則 | 能力 |
|---|---|---|
| `newsletter:list.read` | token tenant 必須擁有 list | 依 list 讀取訂閱名單。 |
| `newsletter:list.read.global` | 平台級 | 跨 tenant 讀取訂閱名單。 |
| `newsletter:subscriptions.read` | token tenant 必須擁有 list | 依 list + email 讀取訂閱偏好。 |
| `newsletter:subscriptions.write` | token tenant 必須擁有 list | 建立訂閱、簽發一般退訂 token、更新偏好。 |
| `newsletter:events.read` | 依 resource service 規則 | 讀取電子報事件；目前 Member Center 無直接 endpoint。 |
| `newsletter:events.write` | `tenant_id` 必須匹配 | Tenant 事件回寫、one-click token、disable subscription。 |
| `newsletter:events.write.global` | 平台級 | 跨 tenant 事件回寫、one-click token、disable subscription、webhook client mapping。 |

### Send Engine

| Scope | 能力 |
|---|---|
| `newsletter:send.write` | 在 Send Engine 建立／執行發送工作。 |
| `newsletter:send.read` | 在 Send Engine 讀取發送狀態。 |

上述 scope 的資源是 Send Engine，不是 Member Center controller。

### File Access

| Scope | 能力 |
|---|---|
| `files:upload.write` | File Access service 上傳檔案。 |
| `files:download.read` | Access agent 呼叫 Member Center 驗證 delegated download token。 |
| `files:download.delegate` | 業務服務向 Member Center 簽發短效 download token。 |
| `files:metadata.read` | File Access service 讀取檔案 metadata。 |
| `files:delete` | File Access service 刪除檔案。 |

`files:upload.write`、`files:metadata.read`、`files:delete` 由 File Access service 驗證；Member Center 目前只提供 delegated token issuing / validation。

## Client Usage 預設 Mapping

| Usage | Client 型態 / Tenant | 預設 scopes |
|---|---|---|
| `web_login` | public 或 confidential；互動式登入 | `openid`、`email`、`profile`、全部 current-user `profile:*` |
| `tenant_api` | confidential；必須綁 tenant | `newsletter:events.write`、`newsletter:list.read`、`newsletter:subscriptions.read/write`、全部 `profile:*` |
| `platform_service` | confidential；可不綁 tenant | `newsletter:events.write.global`、`newsletter:list.read.global`、全部 `profile:*` |
| `send_api` | confidential；必須綁 tenant | `newsletter:send.read/write` |
| `file_api` | confidential；必須綁 tenant | 全部 `files:*` |
| `webhook_outbound` | 現有 webhook credential usage | `openid`、`email`、`profile`、`newsletter:events.write` |

Mapping seed 採完整集合同步：從預設 mapping 移除的 scope 會在 DB mapping 停用。個別 OAuth application permissions 由 Installer `sync-oauth-clients` 更新。

現有 `tenant_api` / `platform_service` mapping 雖包含 `profile:*`，但 current-user endpoint 仍要求 `sub` 對應實際會員；Client Credentials principal 不能用這些 endpoint 查詢其他會員。目前沒有 S2S 會員資料 endpoint。

## Member Center Endpoint 對照

| Endpoint | Scope |
|---|---|
| `GET /user/profile` | `profile:basic.read` |
| `POST /user/profile` | `profile:basic.write` |
| `GET /user/addresses` | `profile:addresses.read` |
| `POST /user/addresses`、`DELETE /user/addresses/{id}` | `profile:addresses.write` |
| `GET /user/subscriptions` | `profile:subscriptions.read` |
| `POST /user/subscriptions/{id}/unsubscribe` | `profile:subscriptions.write` |
| `POST /newsletter/subscribe` | `newsletter:subscriptions.write` |
| `POST /newsletter/unsubscribe-token` | `newsletter:subscriptions.write` |
| `GET /newsletter/preferences` | `newsletter:subscriptions.read` |
| `POST /newsletter/preferences` | `newsletter:subscriptions.write` |
| `GET /newsletter/subscriptions` | `newsletter:list.read` 或 `.global` |
| `POST /newsletter/one-click-unsubscribe-token(s)` | `newsletter:events.write` 或 `.global` |
| `POST /subscriptions/disable` | `newsletter:events.write` 或 `.global` |
| `POST /integrations/send-engine/webhook-clients/upsert` | `newsletter:events.write.global` |
| `POST /file-access/download-tokens` | `files:download.delegate` |
| `POST /file-access/download-tokens/validate` | `files:download.read` |

以下端點依一次性 token 操作，不要求 OAuth scope：`GET /newsletter/confirm`、`POST /newsletter/unsubscribe`。公開 auth endpoint（register、forgot/reset、email verify）亦不屬於 scope 授權模型。
