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,57 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package credstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNotFound indicates that no data was found for the given client ID.
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// ErrEmptyClientID is returned when an empty client ID is passed to Save.
|
||||
var ErrEmptyClientID = errors.New("client ID cannot be empty")
|
||||
|
||||
// Store defines the interface for loading, saving, and deleting data by client ID.
|
||||
type Store[T any] interface {
|
||||
Load(clientID string) (T, error)
|
||||
Save(clientID string, data T) error
|
||||
Delete(clientID string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// Token represents saved tokens for a specific client.
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
ClientID string `json:"client_id"`
|
||||
}
|
||||
|
||||
// IsExpired reports whether the token has expired.
|
||||
// Returns false if ExpiresAt is zero (token has no expiry).
|
||||
func (t *Token) IsExpired() bool {
|
||||
return !t.ExpiresAt.IsZero() && time.Now().After(t.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsValid reports whether the token has a non-empty access token and is not expired.
|
||||
func (t *Token) IsValid() bool {
|
||||
return t.AccessToken != "" && !t.IsExpired()
|
||||
}
|
||||
|
||||
// NewStringKeyringStore creates a KeyringStore for plain string values.
|
||||
func NewStringKeyringStore(serviceName string) *KeyringStore[string] {
|
||||
return NewKeyringStore[string](serviceName, StringCodec{})
|
||||
}
|
||||
|
||||
// DefaultTokenSecureStore creates a SecureStore for Token values with sensible defaults.
|
||||
// Tokens are AES-256-GCM-encrypted to filePath+".enc" with the master key in
|
||||
// the OS keyring; see DefaultSecureStore for details.
|
||||
func DefaultTokenSecureStore(serviceName, filePath string) *SecureStore[Token] {
|
||||
return DefaultSecureStore[Token](serviceName, filePath, JSONCodec[Token]{})
|
||||
}
|
||||
Reference in New Issue
Block a user