mirror of
https://gitea.com/gitea/tea.git
synced 2026-08-05 23:07:39 +02:00
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>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user