Help us shape the future of Go! Take the 2026 Go Developer Survey

credentials

package
v0.65.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package credentials stores provider secrets keyed by Ref{Provider, Account}. Values live in the OS keychain; the refs themselves live in a separate index, because keychains cannot enumerate.

It is deliberately a leaf: no dependency on app, flow, or any connector, so a connector package can import it without a cycle — the same rule that put the connector vocabulary in cmd/desktop/internal/app/sources/connector.

Two rules govern everything here. **Config holds refs, never tokens**: flows/ is dotfiles-managed, so a token embedded in a node's config is a token in a git repo. And **the keychain is the truth, the index is a cache**: a ref present in the index whose keychain entry is gone reads as absent and is pruned, because the alternative is a "Connected" badge over a credential that no longer exists.

Index

Constants

View Source
const (
	// EnvKeyringService gives development launches an isolated OS keychain namespace.
	EnvKeyringService = "HIVE_DESKTOP_DEVELOPMENT_KEYRING_SERVICE"
)

Variables

View Source
var ErrNotFound = errors.New("credentials: not found")

ErrNotFound is returned by Get for a ref with no stored value. It is a distinct error rather than a zero Secret so a caller cannot mistake "no credential" for "an empty credential" — the first is an ordinary state that means "not connected", the second is corruption.

Functions

func EnvOverrideName

func EnvOverrideName(provider string) string

EnvOverrideName is the environment variable that overrides every stored credential for one provider: HIVE_GITHUB_TOKEN, HIVE_GRAFANA_TOKEN.

It is provider-wide rather than per-account because its purpose is headless operation — CI, the server build, the e2e harness — where there is one identity and no keychain to read. A machine with several accounts configured is not the case it serves.

func HasEnvOverride

func HasEnvOverride(provider string) bool

HasEnvOverride reports whether a provider's environment override is set.

It is what lets a caller tell "connected with no stored credential" from "not connected": the override is provider-wide and names no account, so a listing of stored refs is empty for it while every fetch still succeeds. Reporting that as disconnected would contradict the working feed beside it.

func Resolve

func Resolve(store Store, ref Ref) (string, error)

Resolve reads one ref's value, preferring the provider's environment override. It returns "" and no error when nothing is stored.

Types

type KeychainStore

type KeychainStore struct {
	// contains filtered or unexported fields
}

KeychainStore keeps values in the OS keychain and refs in a JSON index beside the app's other state.

The index exists because keychains do not enumerate: zalando/go-keyring has no List, and neither does the API underneath it. Only refs go in the file — never values — so a leaked or backed-up index discloses which accounts are configured and nothing more.

func NewKeychainStore

func NewKeychainStore(indexPath string) *KeychainStore

NewKeychainStore builds a store whose ref index lives at indexPath. The path is a parameter rather than derived from cmd/desktop/internal/app/settings so this package stays a leaf and a test can point it at a temp dir.

func NewKeychainStoreWithService

func NewKeychainStoreWithService(indexPath, service string) *KeychainStore

NewKeychainStoreWithService builds a store in a specific OS keychain namespace.

func (*KeychainStore) Delete

func (s *KeychainStore) Delete(ref Ref) error

Delete removes a credential and its ref. A ref absent from either side is not an error: deleting what is already gone is the caller's intent either way.

func (*KeychainStore) Get

func (s *KeychainStore) Get(ref Ref) (string, error)

Get reads one credential. A ref listed in the index whose keychain entry is gone — revoked in Keychain Access, restored from a backup without it — is pruned and reported as absent: the index is a cache and the keychain is the truth, and the alternative is a "Connected" badge over nothing.

func (*KeychainStore) List

func (s *KeychainStore) List() ([]Ref, error)

List returns every recorded ref, sorted.

It reads the index alone and does not verify each ref against the keychain: on macOS every keychain read can prompt, and a status screen that enumerates connectors must not turn into one prompt per provider. Callers that need certainty about a single credential use Get, which does check.

func (*KeychainStore) Set

func (s *KeychainStore) Set(ref Ref, value string) error

Set stores a credential and records its ref. The keychain is written first: an index entry with no keychain value is the divergence Get already self-heals, whereas a keychain value with no index entry is invisible to List and leaks silently.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore keeps credentials in memory. It backs mock modes and tests: a keychain read can prompt, and a suite that prompts is a suite that hangs in CI.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

func (*MemoryStore) Delete

func (s *MemoryStore) Delete(ref Ref) error

func (*MemoryStore) Get

func (s *MemoryStore) Get(ref Ref) (string, error)

func (*MemoryStore) List

func (s *MemoryStore) List() ([]Ref, error)

func (*MemoryStore) Set

func (s *MemoryStore) Set(ref Ref, value string) error

type Ref

type Ref struct {
	// Provider names the connector family: "github", "grafana".
	Provider string
	// Account distinguishes several credentials for one provider — a login, a
	// hostname, an environment.
	Account string
}

Ref identifies one credential. A Value Object: immutable, compared by value, self-validating, with no identity of its own. A credential is referred to by this and never by a bare string, so "which account" cannot be lost in a signature that takes two strings in the wrong order.

Refs compare exactly and are not case-normalised. Providers come from connector descriptor types, which are constants.

func ListProvider

func ListProvider(store Store, provider string) ([]Ref, error)

ListProvider returns every stored ref for one provider, sorted. It is what an Integrations screen enumerates and what a connector offers when a node picks which account to use.

func ParseRef

func ParseRef(s string) (Ref, error)

ParseRef reads the "provider/account" form a node's `credential:` field carries.

func (Ref) String

func (r Ref) String() string

String is the wire and storage form, "github/octocat". It is also the keychain account name, which is why the separator may not appear in either half.

func (Ref) Validate

func (r Ref) Validate() error

Validate reports whether the ref is well-formed. Both halves must be non-empty and neither may contain the separator, or String and ParseRef stop being inverses.

type Resolver

type Resolver func() (string, error)

Resolver returns a credential's current value, or "" when none is stored.

Empty-and-no-error is deliberate: with providers configured per connector rather than gating the app, "not connected" is an ordinary state a caller reports, not an error it propagates. A consumer that needs a token maps "" to its own "not authenticated" error at the point it would have used one.

A Resolver reads the store on every call. That is what makes connecting, rotating, or disconnecting a credential take effect on the next use — nothing captures the value, so there is no cache to invalidate.

func Bind

func Bind(store Store, ref Ref) Resolver

Bind returns a Resolver over one ref.

type Store

type Store interface {
	Get(ref Ref) (string, error)
	Set(ref Ref, value string) error
	Delete(ref Ref) error
	List() ([]Ref, error)
}

Store persists credentials. Get returns ErrNotFound when a ref has no value; List enumerates the refs that have one.

Values are plain strings. A redacting wrapper type earns its place when secrets are loaded from config and flow through structs that get marshalled and logged — but the rule above means config carries refs and never tokens, so no such path exists here. A value's whole life is keychain → connector factory → provider client, and none of those marshal it.

A consumer-defined interface lives with its consumer, but this one is declared here on purpose: it has two implementations in this package (keychain and memory) and the narrow views its callers want — "read one credential" — are declared at those call sites instead.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL