# File Access Agent

File Access Agent 是檔案存取代理服務，用來控管 bucket / file space 裡檔案的上傳、下載、metadata 讀取與刪除權限。

本專案已決定使用 C# / ASP.NET Core。目標與授權模型依 `../member_center` 最新文件整理，後續實作會以此為準落地。

## 已確認決策

- 部署模型採 single-tenant instance：一個 File Access Agent instance 只服務一個 tenant。
- `tenant_id` 仍是 token 與授權邊界的一部分，但服務端會驗證其必須等於本 instance 的固定 tenant。
- 服務預設為無 DB、stateless access gateway。
- 每個 request 只處理單檔（upload/download/metadata/delete），不提供結構化查詢。
- 版本管理由上游服務負責；不同版本在 bucket 中視為不同單檔（不同 `object_key` 或等價識別）。
- Agent 可只以 `object_key` 或 `file_id` 作為單檔識別，不保存完整 metadata。

## 專案目的

此服務位於業務服務與實際檔案儲存 bucket 之間，負責檔案存取的安全邊界。

核心目的：

- 驗證由 Member Center 簽發的 OAuth2/JWT access token。
- 依 `scope`、`tenant_id`、`aud`、檔案識別與 HTTP method 判斷是否允許存取。
- 讓後端服務可以用 S2S token 上傳檔案。
- 讓後端服務在完成自身商業規則檢查後，向 Member Center 取得可給 client 使用的短效下載授權。
- 避免把一般 S2S access token 直接暴露給 client。
- 將 bucket provider 的直接存取權限集中在此服務或其 storage adapter 內。

## Agent 角色定義

File Access Agent 是 access permission gateway，不是檔案主資料服務。

負責：

- 驗證 token 與 request 邊界（`tenant_id`、`scope`、`aud`、`method`、`object_key/file_id`）
- 代理單檔 upload/download/metadata/delete
- 呼叫 Member Center delegated token validation endpoint

不負責：

- delegated token 簽發（由 Member Center 負責）
- 商業規則與版本管理（由上游服務負責）
- 結構化查詢、報表或資料分析
- metadata 主資料治理（預設不落地 DB）

## 與 Member Center 的關係

Member Center 是 OAuth2/OIDC issuer 與 JWKS 提供者。File Access Agent 不負責會員登入、OAuth client 管理或長效 access token 簽發。

`../member_center` 目前已規劃並落地下列 File Access 相關授權設定：

- audience / resource：`file_access_api`
- OAuth client usage：`file_api`
- scopes：
  - `files:upload.write`
  - `files:download.read`
  - `files:download.delegate`
  - `files:metadata.read`
  - `files:delete`

最新版 `member_center` 文件已明確把 File Access 納入同一套租戶授權模型：`file_api` client 必須綁定 `tenant_id`，upload token 與 delegated download token 也都必須帶 `tenant_id`。Client Credentials 可核發的 scope 由 Member Center 的 OAuth application `usage` 對應 mapping 決定，不應依賴呼叫端用 `scope` 參數自行擴權。

File Access Agent 應驗證：

- upload 的 S2S JWT：透過 Member Center JWKS 驗 `iss`、`aud=file_access_api`、`exp`、`scope`/`scp`、`tenant_id`
- download 的 delegated token：以 Member Center validation endpoint 線上驗證
- token 中的 `tenant_id` 是否符合實際 request
- token 中的 `object_key` 或 `file_id` 是否符合實際 request
- token 中的 `method` 是否符合實際 request

待確認的檔案邊界 claim：

- `user_id`：delegated download token 目前由 Member Center 文件定義為必帶，用於對應業務服務已驗過的使用者身分。
- `owner_id` / `subject` / `client_id`：可用於紀錄或限定上傳者。
- `bucket` / `namespace` / `object_prefix`：若 storage 需要多 bucket 或 prefix 隔離，應優先明確化。
- `file_id` / `object_key`：下載 token 必須綁定其中一種檔案識別。

## 預期流程

### Upload: service to File Access Agent

1. 業務服務以 `client_credentials` 向 Member Center 取得 access token。
2. token 需包含 `aud=file_access_api`、`tenant_id` 與 `files:upload.write`。
3. 業務服務帶 Bearer token 呼叫 File Access Agent 上傳檔案。
4. File Access Agent 驗 token、`tenant_id` 與檔案邊界後，寫入 bucket / file space。

### Download: service to client to File Access Agent

1. client 向業務服務要求下載檔案。
2. 業務服務自行驗證商業規則與檔案權限。
3. 業務服務以 `files:download.delegate` 呼叫 Member Center `POST /file-access/download-tokens` 取得短效 download token。
4. download token 至少綁定 `tenant_id`、`user_id`、`object_key` 或 `file_id`、`method=GET`、短效 `exp`。
5. client 帶短效 token 直接向 File Access Agent 下載檔案。
6. File Access Agent 以 `files:download.read` 呼叫 Member Center `POST /file-access/download-tokens/validate`，帶上 token 與實際 GET request 邊界，驗證通過後放行。

責任邊界已確認：

- Member Center：簽發 upload 用的 S2S JWT、簽發 delegated short-lived download token、提供 download token validation endpoint。
- File Access Agent：驗證 upload JWT、呼叫 validation endpoint 驗 delegated download token、控管實際 bucket / file space 的讀寫。
- 業務服務：先驗商業規則與使用者檔案權限，再向 Member Center 申請 delegated download token。

## 初步 API 邊界

實際路由可在實作時調整，但能力邊界應維持清楚：

- `PUT /files/{object_key}`：上傳檔案，需 `files:upload.write`
- `GET /files/{object_key}`：下載檔案，需 `files:download.read`
- `HEAD /files/{object_key}` 或 `GET /files/metadata/{object_key}`：讀 metadata，需 `files:metadata.read`
- `DELETE /files/{object_key}`：刪除檔案，需 `files:delete`

本服務不負責提供 `POST /download-tokens`。該責任已在 Member Center：

- `POST /file-access/download-tokens`
- `POST /file-access/download-tokens/validate`

## 非目標

- 不負責會員註冊、登入、OAuth consent 或 OAuth client 管理。
- 不取代 Member Center 的 token issuer 職責。
- 不把 bucket credentials 發給 client。
- 不由 File Access Agent 判斷完整商業規則；業務服務仍需先判斷使用者是否可存取指定檔案。
- 不在語言與 storage provider 尚未決定前鎖死特定 SDK 或框架。

## 上傳命名與重複策略

- Agent 不做複雜命名規則；`object_key` 由介接服務決定。
- 相同 `object_key` 再次上傳時，Agent 視為覆蓋（overwrite），不自動版本化。
- 若要避免檔名重複，介接服務應自行把 `timestamp/uuid/業務主鍵` 放入 `object_key`。
- 若要版本管理，介接服務需為每個版本使用不同 `object_key`（例如 `v0001`, `v0002`）。
- 詳細命名建議請見 `docs/API.md` 的 Upload 區段。

## 待決策

- bucket / object storage provider，例如 S3、GCS、Cloudflare R2、MinIO 或本機檔案系統。
- 是否需要「可選」持久化：token `jti`、審計紀錄或特殊索引（預設不啟用）。
- object key 命名規則與 bucket / namespace / prefix 隔離策略。
- 檔案大小限制、content-type allowlist、病毒掃描與保留期限。
- 是否支援 presigned URL，或所有流量都經由 access agent proxy。

## Storage 設定

API 支援兩種 provider：

- `Storage:Provider=local`：使用本機檔案系統（`Instance:StorageRoot`）
- `Storage:Provider=minio`：使用 MinIO（S3 compatible）

MinIO 設定（`src/FileAccessAgent.Api/appsettings.json`）：

- `Minio:Endpoint`：例如 `localhost:9000`
- `Minio:AccessKey`
- `Minio:SecretKey`
- `Minio:BucketName`
- `Minio:UseSsl`：`true|false`
- `Minio:Region`：可空

## Docker

API Dockerfile 位於 `src/FileAccessAgent.Api/Dockerfile`，建置時請使用 repo root 作為 build context，讓 Docker 可以存取 Api、Application、Domain、Infrastructure project references。

建置 image：

```bash
docker build -f src/FileAccessAgent.Api/Dockerfile -t file-access-agent-api .
```

啟動 API（container 內 listen `8080`）：

```bash
docker run --rm -p 8080:8080 \
  -e Instance__TenantId=d8107508-e6b9-4630-b982-c7bbb0cb228f \
  -e Auth__Issuer=http://host.docker.internal:7850/ \
  -e Auth__Audience=file_access_api \
  -e Auth__RequireHttpsMetadata=false \
  -e MemberCenter__BaseUrl=http://host.docker.internal:7850/ \
  file-access-agent-api
```

若 production 的 token `iss` 是公開 HTTPS issuer，但 Agent 需要透過內網網址讀取 OIDC metadata/JWKS，可保留 `Auth__Issuer` 為公開 issuer，並另外設定 `Auth__MetadataAddress`，例如：

```bash
-e Auth__Issuer=https://member.example.com/ \
-e Auth__MetadataAddress=http://member-center-api/.well-known/openid-configuration
```

`MemberCenter__BaseUrl` 則是 Agent 呼叫 `/oauth/token` 與 delegated download token validation endpoint 的 server-to-server base URL，可依部署網路使用內網位址。

若 `MemberCenter__BaseUrl` 含有 path base（例如 `https://member.example.com/api/`），`MemberCenter__TokenEndpointPath` 與 `MemberCenter__DownloadTokenValidationPath` 請使用不以 `/` 開頭的相對路徑，例如 `oauth/token`、`file-access/download-tokens/validate`，避免 HttpClient 組 URL 時把 `/api/` 丟掉。

若使用 local storage，建議掛載 volume，避免 container 移除後檔案遺失：

```bash
docker run --rm -p 8080:8080 \
  -e Storage__Provider=local \
  -e Instance__StorageRoot=/app/data/storage \
  -v "$(pwd)/data/storage:/app/data/storage" \
  file-access-agent-api
```

若使用 MinIO 且 MinIO 也在 Docker network 中，`Minio__Endpoint` 應使用 service/container DNS 名稱，例如 `minio:9000`；不要使用 container 內的 `localhost:9000`。

## 實作語言

本專案已決定使用 C# / ASP.NET Core。

### C# / ASP.NET Core

- 與 `member_center` 同語言，跨專案概念與維運一致。
- ASP.NET Core 對大檔串流、middleware、auth policy、DI、設定管理很成熟。
- 編譯期檢查較強，適合安全邊界服務。
- 可直接沿用 `member_center` 的設定、OpenAPI、JWT / OAuth 與部署習慣。

目前實作方向會以 ASP.NET Core Web API 為主，並保留 storage adapter 抽象，避免過早綁死特定 bucket provider。

## 文件維護規則

每次改動功能、流程、API、scope、設定或部署方式時，都要同步更新文件。

優先更新順序：

1. `README.md`：專案目的、目前決策、執行方式與重要限制。
2. `docs/`：若後續建立更細文件，放設計、API、流程、部署、決策紀錄。
3. API 規格：一旦路由與 payload 穩定，補 OpenAPI 或等價規格。

如果程式碼與文件不一致，應先修正 README 或在 README 標示尚未決定，避免讓文件變成過期假設。

目前文件：

- `docs/API.md`：File Access Agent API 規格與 Member Center 整合契約
- `docs/openapi.yaml`：本專案 OpenAPI 草案
- `docs/DATA_MODEL.md`：可選持久化擴充（非預設）

## 測試專案

- `src/FileAccessAgent.TestSite`：Redirect login 測試站（OIDC Authorization Code + PKCE）
- 目的：透過 Member Center `web_login` client 登入，取得並顯示 token/claims，並直接測試 Agent API（upload / download / head / metadata / delete / health）
- 測試站採雙 token 模式：
  - `web_login` token：只用於身份展示（claims / user context）
  - `file_api` service token（client credentials）：用於呼叫 Agent 與 Member Center delegated token API

最小設定（`src/FileAccessAgent.TestSite/appsettings.json`）：

- `MemberCenter:Authority`
- `MemberCenter:WebBaseUrl`（Member Center Web，預設 `http://localhost:5080`，供 redirect logout 使用）
- `MemberCenter:ClientId`（web_login）
- `MemberCenter:ClientSecret`（web_login public client 可留空）
- `MemberCenter:CallbackPath`（需與 Member Center OAuth client redirect URI 一致）
- `MemberCenter:SignedOutCallbackPath`（OIDC middleware callback，建議保留預設 `/signout-callback-oidc`，不要與 direct logout callback 共用）
- `MemberCenter:Scopes`（建議 `openid email profile`）
- `MemberCenter:ServiceClientId`（file_api）
- `MemberCenter:ServiceClientSecret`（file_api）
- `MemberCenter:ServiceScopes`（至少 `files:upload.write files:metadata.read files:delete files:download.delegate`）
- `MemberCenter:ServiceTokenEndpointPath`（預設 `oauth/token`；若 `Authority` 含 `/api/` 這類 path base，請使用不以 `/` 開頭的相對路徑）
- `MemberCenter:DelegatedDownloadTokenEndpointPath`（預設 `file-access/download-tokens`；同上，建議使用相對路徑）
- `FileAccessAgent:BaseUrl`
- `FileAccessAgent:TenantId`

啟動：

- `dotnet run --project src/FileAccessAgent.TestSite/FileAccessAgent.TestSite.csproj`
- 進入首頁後點 `Login`，會導到 Member Center，再 redirect 回測試站
- `Logout` 會走 Member Center Web `GET /account/logout?returnUrl=...`，再 callback 回 TestSite `/auth/logout-callback`
- 若 logout 後未回到 TestSite，而停在 Member Center，需在 Member Center Web 設定：
  - `Auth__AllowedLogoutReturnUrlPrefixes=http://localhost:5091/`
- 登入後可在同一頁直接執行 `Upload`、`Download`、`Head`、`Get Metadata`、`Delete`、`Health`
- `Upload` 改為檔案挑選上傳；`Object Key` 可選填（留空會自動產生 `demo/uploads/<timestamp>-<filename>`）
- 上傳成功後會保留回傳的 `object_key` 供後續 `Metadata/Head/Download/Delete` 直接測試
- 另提供 `Download (Invalid Token)` 測試，故意帶錯 token 驗證 Agent 拒絕邏輯
- `Download` 會先呼叫 Member Center `POST /file-access/download-tokens` 申請 delegated token，再帶 token 呼叫 Agent 下載
- `Download File` 走 sample 的直連模式：TestSite 僅向 Member Center 取 delegated token，接著把 client 轉址到 Agent 下載 URL（含短效 `access_token`），下載流量不經 TestSite

## 目前狀態

- 專案目標已依 `../member_center` 文件整理。
- 已確認使用 C# / ASP.NET Core。
- `tenant_id` 與 delegated download token 分工已依最新版 `member_center` 文件校正。
- API 已提供 Dockerfile，可用 .NET 8 多階段建置產生 container image。
- API 已實作 Member Center JWT 驗證、delegated download token validation 串接，以及 local / MinIO storage adapter。
