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>
77 lines
2.0 KiB
Go
77 lines
2.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package credstore
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/zalando/go-keyring"
|
|
)
|
|
|
|
// KeyringStore stores values in the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager).
|
|
type KeyringStore[T any] struct {
|
|
serviceName string
|
|
codec Codec[T]
|
|
}
|
|
|
|
// NewKeyringStore creates a new KeyringStore with the given codec.
|
|
// Panics if codec is nil.
|
|
func NewKeyringStore[T any](serviceName string, codec Codec[T]) *KeyringStore[T] {
|
|
if codec == nil {
|
|
panic("credstore: NewKeyringStore called with nil codec")
|
|
}
|
|
return &KeyringStore[T]{serviceName: serviceName, codec: codec}
|
|
}
|
|
|
|
// Load loads data from the keyring for the given client ID.
|
|
func (k *KeyringStore[T]) Load(clientID string) (T, error) {
|
|
var zero T
|
|
data, err := keyring.Get(k.serviceName, clientID)
|
|
if err != nil {
|
|
if errors.Is(err, keyring.ErrNotFound) {
|
|
return zero, ErrNotFound
|
|
}
|
|
return zero, fmt.Errorf("failed to read from keyring: %w", err)
|
|
}
|
|
|
|
decoded, err := k.codec.Decode(data)
|
|
if err != nil {
|
|
return zero, fmt.Errorf("failed to decode keyring data: %w", err)
|
|
}
|
|
return decoded, nil
|
|
}
|
|
|
|
// Save saves data to the keyring for the given client ID.
|
|
func (k *KeyringStore[T]) Save(clientID string, data T) error {
|
|
if clientID == "" {
|
|
return ErrEmptyClientID
|
|
}
|
|
|
|
encoded, err := k.codec.Encode(data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode data for keyring: %w", err)
|
|
}
|
|
|
|
if err := keyring.Set(k.serviceName, clientID, encoded); err != nil {
|
|
return fmt.Errorf("failed to save to keyring: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes data for the given client ID from the keyring.
|
|
func (k *KeyringStore[T]) Delete(clientID string) error {
|
|
err := keyring.Delete(k.serviceName, clientID)
|
|
if err != nil && !errors.Is(err, keyring.ErrNotFound) {
|
|
return fmt.Errorf("failed to delete from keyring: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// String returns a description of this store.
|
|
func (k *KeyringStore[T]) String() string {
|
|
return "keyring: " + k.serviceName
|
|
}
|