// tokenstore.go — Session Token 儲存介面(AB5 範圍)。 // // 本檔只定義 interface 與一個測試用的 MemoryTokenStore。真正的 OS Keychain / // encrypted file 實作留到 AB7(見 ADR-009、TDD §9)。AB7 完成後,只要把 Wails // app 啟動時建立的 TokenStore 換成 keychain 版,Manager 不用動。 // // 設計原則(對齊 §9 Token 儲存策略): // // - Session Token 是長效憑證(雛形無 TTL,Phase 1 = 90 天),洩漏等於整台 // 電腦被遠端控制;AB7 之前用 MemoryTokenStore 只保證 process 內不落檔。 // - 介面故意極簡(Save / Load / Delete),避免 AB7 換實作時 Manager 要改。 // - Load 在「尚未儲存」時回傳空字串 + nil error(非 error 情境),與 // os.IsNotExist 語義對齊,讓呼叫端用 `if token == ""` 判斷更直覺。 // - 任何 I/O / 加解密錯誤才回 error(AB7 檔案讀取失敗、keychain 拒絕等)。 // // Thread safety: // // - Manager 可能在 main goroutine(Pair)與 reconnect goroutine(401 清 token) // 同時觸碰,實作必須 thread-safe。MemoryTokenStore 已內建 sync.RWMutex。 package tunnel import "sync" // TokenStore 是 Session Token 的持久化介面。 // // AB7 會提供三個平台實作: // - macOS:go-keychain(Keychain Services) // - Windows:wincred(Credential Manager) // - Linux:secret-service 或 encrypted file fallback // // 雛形(AB5 / AB6)先用 MemoryTokenStore,重啟 Wails app 會遺失 token、需要 // 重新配對——這是雛形可接受的代價。 // // 介面契約(所有實作必須遵守): // // - Save(token) 覆蓋既有值。**Save("") 等同 Delete()**:傳入空字串 token // 視為「清除已儲存值」,不留下空字串條目,與 Delete() 行為等價。 // 這個契約讓呼叫端在「不確定 token 是空還是要清掉」的場景可以統一走 Save, // 而不必先檢查 token 是否為空。 // - Load() 在「從未儲存」或「已 Delete」時回傳 ("", nil),**非 error**。 // 對齊 os.IsNotExist 語義,呼叫端用 `if token == ""` 即可判斷未儲存。 // 僅 I/O / 解密失敗(AB7 keychain 拒絕、檔案損壞等)才回 non-nil error。 // - Delete() 是 idempotent:不存在時視為成功(不回 error)。 // - 所有方法必須 thread-safe(Manager 會在 main / reconnect goroutine 同時呼叫)。 type TokenStore interface { // Save 儲存 Session Token。覆蓋既有值。 // Save("") 等同 Delete()(見介面 doc 「介面契約」第 1 點)。 Save(token string) error // Load 回傳已儲存的 Session Token;未儲存時回傳 ("", nil)。 // 只有 I/O / 解密錯誤才回 non-nil error。 Load() (string, error) // Delete 清除儲存的 token。不存在時視為成功(idempotent)。 Delete() error } // MemoryTokenStore 是測試與 AB5/AB6 雛形用的 in-memory 實作。 // AB7 完成後會被平台 keychain 實作取代。 // // ⚠️ 不持久化:process 結束 token 就消失。 type MemoryTokenStore struct { mu sync.RWMutex token string } // NewMemoryTokenStore 建立一個空的 in-memory TokenStore。 func NewMemoryTokenStore() *MemoryTokenStore { return &MemoryTokenStore{} } // Save 儲存 token;空字串視為清除(對齊 TokenStore 介面語義)。 func (s *MemoryTokenStore) Save(token string) error { s.mu.Lock() defer s.mu.Unlock() s.token = token return nil } // Load 回傳目前儲存的 token;未儲存回 ("", nil)。 func (s *MemoryTokenStore) Load() (string, error) { s.mu.RLock() defer s.mu.RUnlock() return s.token, nil } // Delete 清除儲存的 token。idempotent。 func (s *MemoryTokenStore) Delete() error { s.mu.Lock() defer s.mu.Unlock() s.token = "" return nil }