Files
gitea-tea/modules/config/credstore.go
T
Bo-Yi Wu f6d939a8df refactor(credstore): embed credential store and drop sdk-go dependency
- Embed the minimal credstore subset used by tea (SecureStore,
  EncryptedFileStore, KeyringStore, FileStore) as modules/credstore so
  external SDK renames can no longer break the build
- Keep the on-disk format fully compatible: AES-256-GCM values with the
  v1: prefix, credentials.json / credentials.json.enc paths, and the
  Token JSON field names are unchanged, verified by a ciphertext fixture
  generated with sdk-go v1.1.0
- Store the keyring master key under a tea-owned account name
- Reuse the existing kernel-level filelock module instead of the
  upstream lockfile protocol, removing a stale-lock race
- Cover roundtrip, keyring-unavailable fallback, and fixture decryption
  with tests using a mocked keyring
- Remove github.com/go-signet/sdk-go and promote
  github.com/zalando/go-keyring to a direct dependency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:59:07 +08:00

74 lines
2.1 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package config
import (
"path/filepath"
"sync"
"time"
"gitea.dev/tea/modules/credstore"
"github.com/adrg/xdg"
"golang.org/x/oauth2"
)
var (
tokenStore *credstore.SecureStore[credstore.Token]
tokenStoreOnce sync.Once
saveOAuthTokenToStore = func(loginName, accessToken, refreshToken string, expiresAt time.Time) error {
return getTokenStore().Save(loginName, credstore.Token{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt,
ClientID: loginName,
})
}
deleteOAuthTokenFromStore = func(loginName string) error {
return getTokenStore().Delete(loginName)
}
)
func getTokenStore() *credstore.SecureStore[credstore.Token] {
tokenStoreOnce.Do(func() {
filePath := filepath.Join(xdg.ConfigHome, "tea", "credentials.json")
tokenStore = credstore.DefaultTokenSecureStore("tea-cli", filePath)
})
return tokenStore
}
// LoadOAuthToken loads OAuth tokens from the secure store.
func LoadOAuthToken(loginName string) (*credstore.Token, error) {
tok, err := getTokenStore().Load(loginName)
if err != nil {
return nil, err
}
return &tok, nil
}
// SaveOAuthToken saves OAuth tokens to the secure store.
func SaveOAuthToken(loginName, accessToken, refreshToken string, expiresAt time.Time) error {
return saveOAuthTokenToStore(loginName, accessToken, refreshToken, expiresAt)
}
// DeleteOAuthToken removes tokens from the secure store.
func DeleteOAuthToken(loginName string) error {
return deleteOAuthTokenFromStore(loginName)
}
// SaveOAuthTokenFromOAuth2 saves an oauth2.Token to credstore, falling back to
// the existing login's values for empty refresh token or zero expiry.
func SaveOAuthTokenFromOAuth2(loginName string, token *oauth2.Token, login *Login) error {
refreshToken := token.RefreshToken
if refreshToken == "" {
refreshToken = login.GetRefreshToken()
}
expiry := token.Expiry
if expiry.IsZero() {
expiry = login.GetTokenExpiry()
}
return SaveOAuthToken(loginName, token.AccessToken, refreshToken, expiry)
}