HUAN 讙 · 開發者

Server

Fastify 5,TypeScript,PostgreSQL。

結構

apps/server/src/
	main.ts          進入點
	app.ts           buildServer(),測試也用同一個組裝函式
	env.ts           以 @huan/config 驗證環境變數
	plugins/         cookie、cors、rate limit、websocket、swagger、static、錯誤處理
	lib/             認證、密碼、session、TOTP、儲存、稽核、目標狀態計算
	routes/          依領域切分的路由
	ws/              裝置與後台的 WebSocket 端點
	cli/             migrate、seed、create-admin

結構驗證與 OpenAPI

每一條路由的請求與回應都用 @huan/protocol 的 zod 結構驗證,OpenAPI 文件由同一份結構自動產生。

這表示 API 文件不可能和實作不同步——它們是同一個東西的兩種輸出。

開發模式下可以在 /api/v1/docs 瀏覽,/api/v1/openapi.json 取得原始規格。

認證

密碼

Argon2id。系統中沒有任何地方保存明文密碼,也沒有任何可逆的表示法。

Session

登入成功後產生一組隨機的不透明 token,放進 HttpOnlySameSite=Lax 的 cookie(正式環境加上 Secure)。

資料庫只存這個 token 的 SHA-256。資料庫外洩時拿不到可用的 token,撤銷也只是刪掉一列資料。

長期憑證絕不放在 localStorage——那裡的東西任何一段跑在同源的 JavaScript 都讀得到。

兩步驟驗證

啟用 TOTP 後,登入分成兩段:

challengeToken 是短時效、一次性的中繼憑證,不能存取任何 API,只能用來完成第二階段。

TOTP 遵循 RFC 6238,容忍前後各一個時間步長的時鐘誤差,並記錄上次成功的時間步長以拒絕同一組驗證碼的重放。實作有 RFC 4226 與 RFC 6238 官方測試向量的單元測試。

TOTP 密鑰只在啟用流程回傳一次,之後不會出現在任何 API 回應或日誌中。

復原碼

啟用 2FA 時產生 10 組復原碼,只顯示一次。Server 只保存雜湊,使用後立即失效。

速率限制

每次登入嘗試都寫進 login_attempts。同一組帳號或同一個來源 IP 在時間窗內失敗次數超過上限就會被擋下。

紀錄寫在資料庫而不是記憶體,因此重啟 Server 不會把攻擊者的計數歸零。

角色

只有兩種角色:

角色權限
super_admin管理使用者、所有裝置、查看稽核紀錄
user操作素材、版面、排程與裝置,不能管理其他使用者

沒有公開註冊。 第一個管理員由 CLI 建立,之後的使用者由 super_admin 在後台建立。

健康檢查

端點檢查內容
GET /health/live永遠 200,只證明 process 還活著
GET /health/ready檢查 PostgreSQL 連線,失敗回 503

RustFS 刻意不列入 readiness。物件儲存暫時不可用時,Server 仍然能提供後台 API 與裝置的目標狀態,裝置也能繼續播放本機已有的內容;把整個服務標成 not ready 只會讓負載平衡器把它從輪替中移除,讓情況更糟。上傳與下載會在當下回報明確的錯誤。

稽核紀錄

所有重要操作都會留下紀錄:登入、登入失敗、啟用與停用 2FA、建立與停用使用者、裝置配對與解除綁定、發布版面、修改排程、刪除素材、強制同步。

每筆紀錄包含操作者、動作、目標、時間與來源 IP。

絕不寫入密碼、TOTP 密鑰、復原碼、session token、裝置憑證或簽章網址的查詢字串。