Skip to Content
核心概念公開讀取

公開讀取:把單一檢視公開給匿名讀者

公開讀取權杖是本模組唯一不需要登入的讀取介面。管理者公開的是某一張表的某一個已存檢視,拿到權杖 URL 的人不需要 TeamSync 登入狀態就能讀那個檢視。除此之外什麼都碰不到:其他檢視不行、其他資料表不行、資料列的建立者不行、資料表的規則與權限設定也不行。

公開是一個明確的動作,也有明確的影響範圍。這一頁的目的,就是讓你在把 URL 交出去之前先知道那個範圍有多大。

鑄造:管理者公開一個檢視

curl -X POST \ "$BASE_URL/private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/public-read-tokens" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "公開價目表", "view_id": "33333333-3333-4333-8333-333333333333", "secretless": true, "visible_columns": ["品項", "單價"], "read_filter": { "已上架": { "eq": true } }, "allow_query": false, "rpm": 120, "valid_until": "2026-12-31T23:59:59" }'

這個 URL 有兩件事常讓人踩坑。它沒有 scope id 這一段:路徑是 /chatroom/tables/{table_id}/...,不是 /chatroom/{chatroom_id}/tables/...,因為管理者權限檢查只用 table_id 就能推出範圍。同樣形狀在 /department//company/ 底下也有。只有資料表管理者能鑄造:明確指定的管理者、聊天室建立者、部門表的部門管理者,或公司管理者。

回應是密鑰唯一會出現的一次:

{ "token_id": "44444444-4444-4444-8444-444444444444", "secret": null, "url": "/public/module/custom_tables/read/44444444-4444-4444-8444-444444444444/records", "view_id": "33333333-3333-4333-8333-333333333333", "visible_columns": ["col_aaaaaaaa_aaaa_4aaa_8aaa_aaaaaaaaaaaa"], "allow_query": false, "rpm": 120, "valid_until": "2026-12-31T23:59:59", "revoked": false }

無密鑰或帶密鑰

  • secretless: true 不會存任何密鑰雜湊。URL 裡的 uuid 本身就是全部的憑證。 請把那個 URL 當成密碼看待:它傳到哪裡,那裡的人就能讀這個檢視。
  • secretless: false(預設)會產生一個 Bearer 密鑰,只在鑄造回應裡出現一次,資料庫只存它的 SHA-256。之後讀者要帶 Authorization: Bearer <secret>。比對是固定時間的,且不存在的 token id 也會花掉同樣的計算成本,所以時間差不會洩漏權杖是否存在。

拿到密鑰請立刻存進密鑰管理系統。沒有「再看一次」的路由,而列表回應裡的 visible_columns 回的是內部 col_<hex> 鍵,永遠不會回密鑰或它的雜湊。

匿名讀者會拿到什麼

每一列就是這些,不多不少:

{ "id": "55555555-5555-4555-8555-555555555555", "created_at": "2026-07-25T01:00:00", "updated_at": "2026-07-25T01:05:00", "data": { "品項": "升降桌", "單價": 12800 } }

再加上 totaltable_name,以及 columns{name, type}),所以前端不必呼叫任何私有 API 就能畫出表頭。data 的鍵是顯示名稱,而舊 schema 留下的殘餘鍵會被丟掉。

刻意不給的有:created_bycreated_by_clientpending_approvalsort_order、聊天室/部門/公司 id、資料表 id,以及資料表的 settingsrulesdefault_permissionscolumn_aclclient_access。被軟刪除的資料列永遠不會出現。在公開通道上,idcreated_atupdated_at 是呼叫端唯一可以在過濾或排序中引用的系統欄位,所以匿名讀者無法拿你的表按建立者排序。

可見欄位的規則

實際公開的欄位是檢視自己的 columns 與權杖 visible_columns交集,再扣掉模組拒絕公開的部分:

欄位種類在公開通道上
stringtext、數值、boolean、日期、選單、json落在交集裡就會公開
attachmentlinkrolluplookup永不公開。鑄造時指定會得到 422,讀取端也會強制隱藏
principalusersocial_client預設隱藏——這道閘門是從 principal 家族推導的,所以新的 principal 型別會自動繼承。只有明確指定才公開,而且只給原始儲存字串principal cell 會保留 user:smc:room: 標籤),不做任何身分解析
formula會公開,但只以同表純量為輸入計算:所有被隱藏或跨表的輸入都會解析成 null
僅管理者可見的欄位(column_acl永不公開

最後一列的 formula 規則在你想公開「計算後的價格或分數」時特別重要:如果那個公式讀的是 rollup,公開出去的值會是 null,不是數字。宣傳之前先看一眼實際輸出。

哪些東西永遠不能公開

  • 受 SCP channel 規則治理的資料表。 鑄造會失敗並回 409 Cannot publish a table governed by a scoped channel policy。公開讀者沒有「目前所在聊天室」,所以讀取時 channel 門檻還會再以「缺少綁定就拒絕」的方式套一層保險。
  • 帶跨表計算語法的檢視computed_filters 非空,或 sort_by / sort 的鍵指到 link、rollup、lookup、formula 欄位。鑄造時是 422,而且每次讀取都會重新檢查。已經公開之後才被改成這種形狀的檢視會停止服務,改回統一的 404。
  • 不存在的欄位,或上面被拒絕的那幾種欄位:鑄造時 422

讀取

# 列表 curl "$BASE_URL/public/module/custom_tables/read/$TOKEN_ID/records?skip=0&limit=50" # 單一資料列 curl "$BASE_URL/public/module/custom_tables/read/$TOKEN_ID/records/$RECORD_ID" # 進一步收窄,只有鑄造時帶 allow_query: true 才可用 curl -X POST "$BASE_URL/public/module/custom_tables/read/$TOKEN_ID/query" \ -H "Content-Type: application/json" \ -d '{ "filters": { "col_aaaaaaaa_aaaa_4aaa_8aaa_aaaaaaaaaaaa": "桌" }, "sort": [{ "column": "col_bbbbbbbb_bbbb_4bbb_8bbb_bbbbbbbbbbbb", "order": "asc" }], "limit": 20 }'

limit 上限是 100。權杖帶密鑰時要加上 -H "Authorization: Bearer $SECRET"

/query body 精確對應 PublicQueryRequest,未知鍵一律禁止:

欄位契約
filters選填的 legacy filter map,從 internal col_<hex> ID 直接對應到值;不使用 {op: value} object
stored_filters選填,最多 20 個 typed stored/link predicate;需要明確 {column, op, value} operation 時使用此欄位
any_of選填,1–10 個 OR group;每組內的 predicate 以 AND 組合
q選填,只搜尋可見 string/text 欄位、不分大小寫,最多 200 字元
sort_bysort_order選填,單一 internal column/system key,加上 ascdesc
sort選填,最多 10 個 {column, order} 的多欄排序;優先於 sort_by
skip大於等於 0 的整數,預設 0
limit1–100 的整數,預設 50

這個 model 刻意沒有 computed_filters,也沒有任何 aggregate member。空 body 合法,會變成帶上述預設值的 PublicQueryRequest()。伺服器先讀取 raw request body:剛好 16,384 bytes 合法,任何更大的 body 都在 JSON parse 之前413。JSON 無效或不符合 model 則回 400,不是 FastAPI 常見的 422

查詢通道的資料列選取只能收窄。呼叫端的 filters、stored predicates、OR groups 與文字搜尋都會 AND 在檢視的資料列條件上,因此無法放寬或移除已發布的資料列切片;若有提供,排序與分頁則由呼叫端決定:

  • filters 的鍵撞在一起時,檢視的值贏。
  • 呼叫端的 sort(或 sort_by)會取代檢視的排序;sort 優先於 sort_by
  • skiplimit 由呼叫端決定。
  • 請求主體禁止未知鍵,結構上也沒有 computed_filters、沒有任何彙總成員,所以公開呼叫端無法注入跨表條件或彙總。
  • q 是不分大小寫的子字串搜尋,只搜可見的 string 與 text 欄位,最長 200 字。
  • filtersstored_filtersany_ofsort_bysort 都使用 internal col_<hex> column ID(排序另接受明確列出的 system keys)。Response data 會用顯示名稱,但顯示名稱不是 query schema 的契約。

引用權杖沒有公開的欄位,回的錯誤與引用一個真的不存在的欄位完全相同(Column 'X' does not exist in table schema)。沒有「存在但被隱藏」的探測管道。

權杖上的 row policy

權杖可以帶 read_filter,它就是一般的 row policy,但在沒有主體的情況下解析:

  • $today$today±Nd$now 正常解析,所以「本週班表」或「目前促銷」不需要有人定期改權杖就會一直正確。
  • $me$me.department 在鑄造時就會被 422 拒絕:公開讀取沒有行為主體,而一個無法指名主體的政策,絕不能默默地對所有人成立。

沒有 read_filter 時,權杖讀整個檢視;有的時候,就讀被政策收窄後的檢視,且身分不是管理者。

所有失敗都是同一個 404

不存在的 token id、已撤銷、已過期、缺少或錯誤的 Bearer 密鑰、allow_query: false 卻打 /query、資料表被刪除、檢視被刪或被改成不能公開的形狀、檢視的過濾或排序現在指到被隱藏的欄位、單列查詢被檢視或 row policy 濾掉:全部都回 404 {"detail":"Not found"}

這種一致性正是重點。公開 URL 不應該告訴匿名探測者:權杖存不存在、是不是過期了、還是那筆資料只是被隱藏。代價是你少了一條除錯路徑,所以請改從私有側除錯:

  1. 讀那張表的權杖列表。它包含已撤銷的權杖(revoked: true),還有 valid_untilrpmallow_querylast_used_at
  2. 打開綁定的檢視,確認它仍然沒有跨表計算語法、也沒有指到被隱藏的欄位。
  3. 429 要另外看:流量限制是唯一有自己狀態碼的失敗。

流量限制、活躍度、有效期

每個權杖有自己的 rpm(1 到 600,預設 60),以每權杖固定 60 秒視窗計算。超過會回 429 rate limit exceeded (N/min per token)。Redis 不可用時這道閘門會 fail open,因為它是防濫用機制,不是安全邊界。不要為了多一點額度再鑄一個權杖,那只會讓你要撤銷的表面積變兩倍。

last_used_at 每個權杖最多每分鐘更新一次,所以請把它讀成「還有人在用嗎」的粗略訊號,不要當成點擊計數。

valid_until 在兩條鑄造路徑上都會正規化成 naive UTC,所以輸入 2026-12-31T23:59:59+08:00 會存成等值的 UTC 時刻,不管你怎麼傳都指同一個瞬間。null 表示不過期。

撤銷是永久的

curl -X DELETE \ "$BASE_URL/private/module/custom_tables/chatroom/tables/$TABLE_ID/public-read-tokens/$TOKEN_ID" \ -H "Authorization: Bearer $TOKEN"

撤銷是墓碑:資料列會留下並標記 revoked: true,URL 永遠失效。沒有任何機制能把它重新啟用,IaC 也不行。要輪替就是:鑄造新權杖、部署、用新權杖驗證讀得到,最後撤銷舊的。

Warning 無密鑰的 URL 沒辦法「收回」,只能撤銷。如果無密鑰 URL 流到你沒預期的地方,請當成資料已經公開處理:撤銷權杖再重新鑄造。只換密鑰沒有用,因為對無密鑰權杖來說,uuid 就是密鑰。

用 IaC 宣告已公開的權杖

public_read 行類型 讓「公開」這件事進版控:

{"kind":"public_read","table":"prices","ref":"public_price_list","spec":{"view":"published_items","secretless":true,"rpm":120,"allow_query":false}}

有四條規則要記住。IaC v1 只支援無密鑰,因為只顯示一次的 Bearer 密鑰無法在文件裡來回;要帶密鑰的權杖請走 REST 鑄造。view 必須指向受管理的檢視:來自前一次 apply 的狀態,或同一份文件裡的 view 行。更新是就地更新,所以重複 apply 時 token id 與憑證 URL 都保持不變。而 state: "absent" 會撤銷線上的權杖,那和 REST 撤銷一樣是永久墓碑。

每次 apply 都會用與 REST 相同的驗證程式碼重跑完整的鑄造政策,所以 IaC 不可能公開出 REST 會拒絕的東西。

端點的完整請求與回應合約見 公開讀取權杖;反方向的寫入請見 外部回呼

Last updated on