Files
gitea-tea/modules/credstore/secure_store.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

59 lines
2.0 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package credstore
// Prober is an optional interface that a Store can implement to test
// whether its backend is available.
type Prober interface {
Probe() bool
}
// DefaultSecureStore creates a SecureStore with the given codec and sensible defaults.
// The primary backend is an EncryptedFileStore writing to filePath+".enc"
// with its master key in the OS keyring; see EncryptedFileStore for why only
// the key lives there. When the keyring is unavailable, it falls back to
// plaintext file storage at filePath.
func DefaultSecureStore[T any](serviceName, filePath string, codec Codec[T]) *SecureStore[T] {
return NewSecureStore[T](
NewEncryptedFileStore[T](serviceName, filePath+".enc", codec),
NewFileStore[T](filePath, codec))
}
// SecureStore is a composite Store that uses the keyring-backed primary
// store when the keyring is available and falls back to file-based storage
// otherwise. The active backend is chosen once at construction time.
type SecureStore[T any] struct {
active Store[T]
}
// NewSecureStore creates a SecureStore. If kr implements Prober and the probe
// succeeds, kr is used as the active store. Otherwise, file is used as the
// fallback.
func NewSecureStore[T any](kr, file Store[T]) *SecureStore[T] {
if p, ok := kr.(Prober); ok && p.Probe() {
return &SecureStore[T]{active: kr}
}
return &SecureStore[T]{active: file}
}
// Load loads data from the active store.
func (s *SecureStore[T]) Load(clientID string) (T, error) {
return s.active.Load(clientID)
}
// Save saves data to the active store.
func (s *SecureStore[T]) Save(clientID string, data T) error {
return s.active.Save(clientID, data)
}
// Delete removes data from the active store.
func (s *SecureStore[T]) Delete(clientID string) error {
return s.active.Delete(clientID)
}
// String returns a description of the active store.
func (s *SecureStore[T]) String() string {
return s.active.String()
}