# 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`。 File Access Agent 應驗證: - upload 的 S2S JWT:透過 Member Center JWKS 驗 `iss`、`aud=file_access_api`、`exp`、`scope`、`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 ``` 若使用 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`) - `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/-`) - 上傳成功後會保留回傳的 `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。 - 目前目錄中的 .NET skeleton 可作為正式起點,但仍需依最新流程補上 Member Center validation 串接。