Skip to content

密碼管理

在 Potato 中配置 PBKDF2-SHA256 密碼雜湊、管理員 CLI 與 API 重置、使用者自助的令牌重置連結,以及 SQLite 或 PostgreSQL 憑據儲存。

v2.4.0 新增

Potato 的認證系統使用 PBKDF2-SHA256,迭代 10 萬次,併為每個使用者配一個獨立的 salt,這也是 NIST 推薦的密碼儲存做法。本頁講的是密碼如何儲存、如何重置,以及怎樣讓憑據在伺服器重啟後繼續有效。

這些內容適用於 in_memory 和 database 兩種方法。在 OAuth 下密碼歸身份提供方管,所以除了混合模式裡的本地賬號之外,本頁內容都不適用。

安全實現

密碼以 salt$hash 的格式儲存:

  • 32 個字元的十六進位制 salt,每個使用者各不相同
  • 64 個字元的十六進位制摘要,由 PBKDF2-HMAC-SHA256 以該 salt 對密碼迭代 100,000 次匯出
  • 通過 hmac.compare_digest 做恆定時間比較,以防時序攻擊

迭代次數才是關鍵。對加了 salt 的密碼只做一次 SHA-256 運算,快到足以被大規模暴力破解,所以這裡的匯出過程要跑 100,000 次。

user_config.json 檔案裡已有的明文密碼會在 Potato 載入時自動用各自獨立的 salt 重新雜湊,不需要手動遷移。

預設配置

Potato 預設使用記憶體認證。require_password 是頂層鍵,不屬於 authentication 塊:

yaml
require_password: true
 
authentication:
  method: in_memory
 
user_config:
  users:
    - username: "annotator1"
      password: "initial-password"   # will be hashed on first load
    - username: "annotator2"
      password: "initial-password"

持久化憑據

通過登錄檔單建立的賬號會寫入一個 JSONL 檔案,並在下次啟動時讀回來。在 in_memory 下,除非你另外指定,這個檔案就是輸出目錄旁邊的 user_config.json。

基於檔案的持久化

yaml
authentication:
  method: in_memory
  user_config_path: /shared/path/to/user_config.jsonl

當多個服務實例共用一份使用者名稱單,或者你想把名單放在標註結果之外的地方時,就指定一個路徑。新註冊和密碼變更會隨時寫入。

把使用者名稱單和標註輸出放在一起。 標註者的目錄以使用者名稱命名,所以名單丟了而輸出目錄還在的研究,下一個輸入該使用者名稱的人就會繼承別人的工作。Potato 會拒絕這次註冊,這也意味著在你恢復該檔案之前,那位標註者進不來。

在 v2.8.3 之前,除非顯式設定了 user_config_path,否則 in_memory 什麼都不寫,賬號也就撐不過一次重啟。如果你在更早的版本上跑過研究,那些賬號已經沒了;標註結果還在。

資料庫後端

SQLite(不需要額外依賴):

yaml
authentication:
  method: database
  database_url: "sqlite:///auth/users.db"

PostgreSQL(需要 psycopg2-binary):

yaml
authentication:
  method: database
  database_url: "postgresql://user:password@localhost:5432/potato_auth"

Potato 會在首次啟動時建立 users 表,並讓 SQLite 以 WAL 模式執行,以便更好地支援併發讀取。database_url 必須以 sqlite:/// 或 postgresql:// 開頭。你也可以在環境中設定 POTATO_DB_CONNECTION,兩者同時存在時 database_url 優先。

注意:method: database 和 user_config_path 互斥,只能選一種持久化方案。兩者都配置時 Potato 會報錯。

重置密碼

管理員 CLI

從命令列重置密碼:

bash
# Prompts for username and password
potato reset-password config.yaml
 
# Prompts for the password only
potato reset-password config.yaml --username annotator1

管理員 API

用管理員 API 金鑰以程式方式重置:

bash
curl -X POST http://localhost:8000/admin/reset_password \
  -H "X-API-Key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "annotator1", "new_password": "new-secure-password"}'

金鑰按這個順序取:頂層的 admin_api_key 設定、POTATO_ADMIN_API_KEY 環境變數,或者任務目錄下自動生成的 admin_api_key.txt。

使用者自助的令牌重置

忘了密碼的標註者可以自己重置。他們訪問 /forgot-password 並輸入使用者名稱,Potato 會生成一個有效期 24 小時的一次性令牌。重置連結顯示在頁面上,由管理員轉交;Potato 不發郵件。標註者開啟 /reset/<token> 設定新密碼,令牌隨之作廢。

這個流程不需要任何配置。設定了 require_password: true 時,登入頁上會出現一個 “Forgot Password?” 連結。

管理員也可以直接簽發令牌:

bash
curl -X POST http://localhost:8000/admin/create_reset_token \
  -H "X-API-Key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "annotator1"}'
 
# {"reset_link": "http://localhost:8000/reset/abc123...",
#  "token": "abc123...", "expires_in_hours": 24}

想換一個過期時間,在請求體裡傳 "ttl_hours": 48。

免密模式

課堂演示、快速研究,或者使用外部認證(Prolific、SONA)的任務,可以完全關掉密碼:

yaml
require_password: false
 
authentication:
  method: in_memory

標註者輸入任意使用者名稱即可登入,不會出現密碼輸入框。涉及敏感資料或需要驗證身份的任務不建議這樣做。

詳見免密登入。

完整參考

yaml
# Top-level: require a password at login (default: true)
require_password: true
 
# Top-level: key for the admin endpoints
admin_api_key: ${POTATO_ADMIN_API_KEY}
 
authentication:
  # in_memory (default) | database | clerk | oauth
  method: in_memory
 
  # in_memory only, mutually exclusive with method: database
  user_config_path: users.jsonl
 
  # database only; sqlite:/// or postgresql://
  # database_url: "sqlite:///auth.db"
 
user_config:
  users:
    - username: "researcher"
      password: "secure-passphrase"
    - username: "annotator1"
      password: "initial-pass"
端點方法認證說明
/forgot-passwordGET, POST無申請重置令牌
/reset/<token>GET, POST無設定新密碼
/admin/reset_passwordPOSTAPI 金鑰管理員重置密碼
/admin/create_reset_tokenPOSTAPI 金鑰生成重置令牌

延伸閱讀

實現細節見源文件。