Skip to Content
概念服務帳號

服務帳號

部門服務帳號(service account,SA)是由平台擁有的虛擬使用者,部門 Worker 以它的身分登入,讓 handler 程式碼能以該帳號的權限呼叫 TeamSync API。它是 Worker 的對外身分;誰可以呼叫某個操作(對內存取)是另一個設定:逐操作存取。本頁所述皆以後端 5.21.0 為準。

服務帳號是什麼

SA 是一筆一般的 TeamSync 使用者資料列,標記為 auth_source = "service_account":恰好屬於一個部門的 role-0 成員,部門在建立時即固定。建立時不帶 body,呼叫端無從選擇任何值:

欄位值
username、nicknameEdgeWorkerSA-<UTC YYYYMMDDTHHMMSSZ>;若該名稱在公司內已被使用,依序加上 -2 … -99
emailsa-<user id>@service-account.invalid
role_id一律 0
auth_source / is_service_account"service_account" / true
enabled、is_verifiedtrue(沒有停用路由)
部門建立它的部門;永不移動

沒有更名、停用或「顯示機密」的路由。SA 不計入公司的使用者上限,也不計入公司或部門的使用者人數。每個部門或公司的 SA 數量沒有上限。一般的使用者與聊天室成員資料會以 is_service_account: true 標示它們。

誰可以管理

呼叫者路由
部門管理者(role ≥ CAN_MANAGE_DEPARTMENT,預設 2)自己目前所屬部門的 /service-accounts…
公司管理者(role ≥ CAN_MANAGE_COMPANY,預設 3)公司內任何存活部門的 /company/departments/{department_id}/service-accounts…
低於管理者門檻的部門成員無:連兩個 GET 路由也僅限管理者

兩種範圍都有同樣的七個路由:建立、列出、讀取、重設憑證、刪除、變更聊天室、列出候選聊天室(服務帳號流程、端點)。沒有綁定路由:SA 透過 Worker 的操作設定綁定到操作(見下文)。

限制

限制值
每個部門/公司的 SA 數無上限
每個 UTC 秒可產生的新 SA 名稱99 個(EdgeWorkerSA-<ts>,接著 -2 … -99);第 100 個 → 409 Service account name is unavailable
每個部門 Worker 已交付的 SA 憑證256 個;第 257 個不同的 SA → 409 service_account_limit_exceeded
列表與候選聊天室的每頁筆數limit 1–100,預設 100;cursor 為最多 7 位數的十進位位移量,≤ 1 000 000(否則 422)
SA 詳細資料顯示的聊天室前 500 個,超過時 rooms_truncated: true
每次變更聊天室add ≤ 100 個 id、remove ≤ 100 個 id,兩者不可重疊
bound_operations / unbound_operations≤ 64(每個 Worker 的操作上限)
候選搜尋 q≤ 128 個字元

憑證生命週期

SA 恰好有一個憑證:平台產生的 256 位元機密(43 個 URL 安全字元,無前綴)。任何人都看不到它:沒有 API 會回傳它,稽核資料列、outbox 資料列與回執只保留加鹽的 SHA-256 摘要。只有部門 Worker 的 runtime 在其 Durable Object 內持有它,並用它換取一般的 TeamSync JWT。後端只保存以 JWT_SECRET_KEY 為金鑰的 HMAC 驗證值。

  1. 建立:SA 已存在但無法登入。它的初始密碼是一個無人持有之隨機值的雜湊,而 SA 登入只接受已交付的驗證值。
  2. 首次綁定:當操作設定寫入或部署暫存引用了 Worker 尚未持有的 SA,後端會產生機密,在設定表變更之前推送進 Worker,再提交驗證值。這是首次建立憑證的唯一途徑。
  3. 使用:handler 程式碼要求標頭時,runtime 以它登入。
  4. 重設:新機密取代舊機密,舊機密在每一種結果下都會失效。回應的 delivery 欄位說明發生了什麼(見下表)。
  5. 刪除:軟刪除;SA 的所有 JWT 立即無法解析(見下一節)。
delivery意義
deliveredWorker 已確認收到新機密。
not_deployed沒有線上 release(沒有 Worker、Worker 不是 active/paused、沒有目前修訂版,或修訂版不是 deployed);舊機密僅被撤銷。
unsupported_release線上 release 早於 SA 支援;舊機密僅被撤銷。
not_boundWorker 既未持有也未綁定此 SA;舊機密僅被撤銷。
failed無法讀取 Worker 的憑證狀態,或交付失敗、結果不確定;無論哪種情況舊機密都已撤銷(只要曾嘗試交付新機密,新機密即已提交)。

得到 failed 之後,綁定此 SA 的操作可能登入失敗,直到之後某次重設回應 delivered 為止。設定寫入只會交付 Worker 尚未持有的憑證,因此無法修復這種情況。

重設與刪除都會撤銷 SA 的所有 refresh token。重設後,已核發的 access JWT 在到期前仍然有效(JWT_EXPIRES_MINUTES,預設 15)。部署永遠不會輪替 SA 機密:輪替就是重設。

把服務帳號綁定到操作

一個操作的對外身分依此順序決定:它自己的 service_account_id,否則 Worker 的 default_service_account_id,否則無。兩者都存在 Worker 的操作設定中,依 release 分別保存,與逐操作存取設定並列。以 PUT /worker/operation-settings 或 POST /worker/deployments 的 operation_settings 區塊設定(管理逐操作存取)。

  • 省略欄位即保留目前的綁定。service_account_id 設為 null 會清除操作自己的綁定,使其繼承預設;default_service_account_id 設為 null 會清除預設,沒有自己綁定的操作就拿不到憑證。
  • 指名的 SA 必須是 Worker 所屬部門的存活 SA,否則 → 422 service_account_not_found(service_account_id is not a live service account of this department);不存在、已刪除、他公司與他部門的 id 都得到同樣的回應。
  • 綁定需要線上 release 以 builder 政策 department-worker-project-v2.9 或更新版本建置,否則 409 service_account_credentials_require_supported_release。請先重新上傳專案(會以 v2.9 重建)並部署(能力對照表)。
  • 部署環境的 HOST 必須是單純的 HTTPS origin,否則 503 api_base_unconfigured(見「維運需求」)。
  • 部署會自動延續兩個 release 都有的操作之綁定,以及預設。目標 manifest 中不存在的操作連同其綁定一起移除。缺少的憑證在暫存時交付;Worker 已持有的憑證保持不動。
  • Worker 仍持有已交付的憑證時,部署不支援 SA 的 release → 409 service_account_credentials_require_supported_release(Unbind every service account (PUT operation-settings) before deploying a release without service-account support)。
  • 解除綁定後,一旦沒有任何保留的 release 再引用該憑證,它(連同快取的 token)就會從 Worker 移除。解除綁定不會輪替已儲存的驗證值;只有重設或刪除會。

刪除 SA 不會移除它的綁定。 刪除回應的 unbound_operations 列出線上 release 中有效綁定為此 SA 的操作(沒有支援 SA 的線上 release,或無法讀取其綁定時為 [])。這些操作從此失敗即關閉(fail closed):authHeaders() 擲出 service_account_unavailable(若 Worker 中的副本無法移除,則為 service_account_sign_in_failed),直到管理者重新綁定為止;任何部署都不會把它們改到預設。若被刪除的 SA 是部門預設,所有沒有自己綁定的操作都會以同樣方式失敗。GET /worker/operation-settings 會把這種綁定顯示為 username: null, live: false。重新送出已失效的 id → 422 service_account_not_found;省略該欄位則保留失效的綁定。

綁定可在以下位置看到:GET /worker/operation-settings(default_service_account、每個操作的 service_account_id 與 effective_service_account = {id, username, source, live},source 為 operation 或 default)、SA 詳細資料的 bound_operations,以及設定已 reconciled 後 GET /worker/openapi.json 中每個操作的 x-teamsync-service-account({id, username, source} 或 null)。

handler 程式碼中的 context.teamsync

以 builder 政策 v2.9 或更新版本建置的 handler 會收到 context.teamsync,一個凍結物件:

成員值
apiBase此 release 的操作設定為 reconciled 且帶有 apiBase 時,為部署環境的 HTTPS origin(無結尾斜線);否則 null
authHeaders()非同步;解析為恰好 { authorization: "Bearer <access JWT>" },對應此操作的 SA(自己的綁定,否則預設)
export async function syncProfile({ teamsync }) { const res = await fetch(`${teamsync.apiBase}/private/user/me`, { headers: await teamsync.authHeaders(), }) return Response.json(await res.json(), { status: res.status }) }

請在每個請求呼叫 authHeaders(),不要自行快取標頭:runtime 會快取並更新 token。Worker 程式碼無法指定其他操作或 SA,看不到機密,也無法透過 context.storage 或 context.secrets 讀取憑證或 token 資料表。沒有 SDK;使用一般的 fetch。CLI 產生的 HandlerInput 型別(.teamsync/generated.d.ts)列出 secrets,但沒有 teamsync。使用 context.teamsync 的 TypeScript handler 仍可建置與部署(CLI 與建置器不做型別檢查),但嚴格的 tsc 檢查會找不到它的型別。

失敗時 authHeaders() 擲出的錯誤:name 為 TeamSyncServiceAccountError、message 為 TeamSync service account is unavailable: <code>,code 為下列其中之一。這個類別無法 import,請檢查 error.name 與 error.code。未捕捉的錯誤會讓請求以 500 worker_execution_failed 結束。

code何時
service_account_not_boundWorker 從未儲存過操作設定,或操作沒有自己的綁定且沒有預設。
operation_settings_pending設定存在,但沒有此 release 的設定(例如設定寫入後,某台邊緣機器仍在服務前一個 release)。
service_account_unavailableWorker 已退役、綁定之 SA 的憑證不在 Worker 中(已刪除或從未交付)、apiBase 為 null,或發生非預期的 runtime 錯誤。
service_account_sign_in_failed密碼登入失敗(更新失敗時會先改用登入):網路錯誤、5 秒逾時、200/401/429 以外的狀態、格式錯誤的回應,或重試後仍是 401。
service_account_rate_limited最後一次登入嘗試回應 429。

登入機制。 由 Durable Object 登入,handler 程式碼從不登入:POST {apiBase}/public/auth/signin(form 編碼的 grant_type=password、SA 的 username、機密、client_id = 公司 id),5 秒逾時,不跟隨重新導向,回應 ≤ 65 536 位元組。token 依 SA 快取,剩餘效期超過 60 秒時重複使用;之後 runtime 以 POST {apiBase}/public/auth/refresh 更新。更新失敗會丟棄 refresh token,改用密碼登入。只有登入回應 401 或 429 才會重試:runtime 等待 1 秒,重新讀取已交付的憑證(涵蓋進行中的重設),再試一次。同時呼叫的請求共用每個 SA 一個進行中的登入。

在伺服器端,SA 登入失敗一律回應 401 Incorrect username or password,並計入該 SA 自己的額度(每 300 秒 30 次失敗,超過則 429 Too many authentication attempts. Please try again later.)。SA 的更新有獨立額度(每 300 秒 60 次)。SA 從不以呼叫端 IP 計算,因此共用出口 IP 的雲端 Worker 不會互相限流,正確的機密也永遠不會被限流器拒絕。

服務帳號能做與不能做的事

能不能
在接受 JWT 的 TeamSync 路由上,以部門 role-0 成員的身分行事:被加入的聊天室及其授權,加上部門層級的預設呼叫任何 Edge Workers 路由。成員路由(GET /worker、呼叫、openapi.json …)→ 403 Service accounts cannot invoke Edge Workers;管理者路由 → 403 Insufficient permissions.(角色檢查先執行;SA 的 role 為 0)
被加入聊天室,只能透過 Edge Workers 的 SA 聊天室路由(它也能移出)透過一般聊天室路由加入、建立或被邀請進聊天室,或成為聊天室管理者/擁有者 → 400 service_account_managed_by_department
簽發 API 金鑰、變更自己的 email、暱稱或密碼、使用助理、核准 OAuth 同意 → 403 service_account_cannot_manage_credentials
被當成一般人員管理(密碼、角色、停用、移動部門、刪除使用者、重寄歡迎/重設信)→ 400 service_account_managed_by_department;刪除只能透過 SA 刪除路由
使用 60 分鐘聊天室登入(401)、忘記密碼(永不選中)或密碼重設票證(400)

只透過聊天室成員資格給 SA 資料存取權。在 Worker 內,操作的 SA 就是最小權限的控制點:任何能呼叫該操作的人(其存取模式允許的公開呼叫者,或透過後端閘道的任何部門成員)都能取得該 SA 可觸及的資料。

部門刪除與退役

刪除部門時,該部門所有存活的 SA 會在刪除交易內被軟刪除,不會移到備援部門:成員資格與聊天室角色被清除、username 被輪替、refresh token 被撤銷,且各寫入一筆 auth_method 為 department_delete 的稽核資料列 service_account.deleted。接著 Worker 會退役:先鎖定其 Durable Object(authHeaders() 擲出 service_account_unavailable),再清除所有已交付的 SA 憑證與快取的 token(v2.9 或更新的 release)。沒有還原路由。

稽核事實

動作何時digest 輸入
service_account.created建立{"department_id", "username"}
service_account.chatrooms_changed每個處理過的聊天室一筆(added、already_member、removed、not_member){"operation", "room_id", "status"}
service_account.credential_reset每次重設{"delivery"}
service_account.deletedSA 刪除路由/部門刪除(auth_method department_delete){"deleted": true}/另加 reason 與 service_account_id
service_accounts.credentials.put憑證交付(設定寫入、部署暫存、重設)機密的加鹽 SHA-256
service_accounts.credentials.deleted(…delete_noop)SA 刪除後移除 Worker 中的副本sha256("delete-service-account:<id>")

前四項是與變更同一交易寫入的直接資料列(resource_type service_account)。兩個 service_accounts.credentials.* 動作走控制面稽核 outbox(resource_type edge_worker_service_account);outbox 會拒絕任何 password 或 salt 欄位,因此機密永遠進不了 outbox。綁定透過操作設定的稽核事實記錄,其 digest 涵蓋每一個綁定的 SA id。見稽核。

維運需求

  • HOST 必須是單純的 https:// origin:有主機名稱、沒有 userinfo、路徑為空或 /、沒有 query 或 fragment。它會成為 apiBase。http:// 或其他值會停用 SA 綁定:503 api_base_unconfigured(This deployment's HOST is not a bare HTTPS origin);僅含存取設定的操作設定仍可運作。變更後的 HOST 要等下一次設定寫入或部署暫存才會到達 Worker,重設不會帶過去。
  • JWT_SECRET_KEY 是所有 SA 驗證值的金鑰。未設定 → 設定寫入與部署暫存回應 503 service_account_verifier_unavailable(重設回報 delivery: "failed"),且任何 SA 都無法登入。輪替它會使所有 SA 憑證失效:之後請逐一重設每個 SA 以重新交付。
  • 在地端套件上,Worker 會呼叫套件自己的 HTTPS API,因此 Worker runtime 必須信任套件的 TLS 憑證;否則 authHeaders() 擲出 service_account_sign_in_failed(地端)。

安全模型

在 Worker release 中執行的程式碼(你的原始碼或同一 isolate 中經核准的相依套件)能觀察到該 release 使用之 SA 的機密,而被截取的機密在下一次重設或刪除前都有效。移除或替換可疑的 Worker 程式碼之後,請重設該 Worker 用過的每一個服務帳號。 此介面的錯誤:錯誤。

Last updated on