mirror of
https://gitea.com/gitea/tea.git
synced 2026-08-05 23:07:39 +02:00
f6d939a8df
- 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>
59 lines
2.0 KiB
Go
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()
|
|
}
|