diff --git a/store/keychain/keychain.go b/store/keychain/keychain.go index e472bb37..a13c2f96 100644 --- a/store/keychain/keychain.go +++ b/store/keychain/keychain.go @@ -25,6 +25,24 @@ import ( var _ store.Store = &keychainStore[store.Secret]{} +// ErrNoDefaultCollection is returned when the secret service has no usable +// default collection (no 'login' collection and no collection assigned to the +// 'default' alias). This typically happens on headless hosts where the keyring +// has not been initialized. +// +// NOTE: this condition is currently specific to the Linux keyring (the +// freedesktop Secret Service). macOS and Windows have no equivalent "default +// collection" concept, so the keychain store never returns this error on those +// platforms. The sentinel is nonetheless declared here, in the cross-platform +// file (rather than the Linux-specific one), so that platform-agnostic callers +// can reference it on every platform without build tags. On non-Linux platforms +// it simply never matches. +// +// It is exported so callers can use [errors.Is] to detect the absence of usable +// keychain infrastructure and fall back gracefully, rather than relying on +// fragile error message comparisons. +var ErrNoDefaultCollection = errors.New("no default keychain collection available") + type ( Option interface{ apply(any) error } optionFunc[K any] func(K) error diff --git a/store/keychain/keychain_linux.go b/store/keychain/keychain_linux.go index f40730ea..651bfb6e 100644 --- a/store/keychain/keychain_linux.go +++ b/store/keychain/keychain_linux.go @@ -62,12 +62,6 @@ const ( secretServiceIsCollectionLockedProperty = "org.freedesktop.Secret.Collection.Locked" ) -// errNoDefaultCollection is returned when the secret service has no usable -// default collection (no 'login' collection and no collection assigned to the -// 'default' alias). This typically happens on headless hosts where the keyring -// has not been initialized. -var errNoDefaultCollection = errors.New("no default keychain collection available") - // getDefaultCollection gets the secret service collection dbus object path. // // It prefers the loginKeychainObjectPath, since most users on X11 would have @@ -122,7 +116,7 @@ func resolveDefaultCollection(collections []dbus.ObjectPath, aliasPath dbus.Obje // The null path is syntactically valid (so IsValid above returns true) but // does not point at a real collection, so it must be rejected explicitly. if aliasPath == nullObjectPath { - return "", errNoDefaultCollection + return "", ErrNoDefaultCollection } return aliasPath, nil diff --git a/store/keychain/keychain_linux_test.go b/store/keychain/keychain_linux_test.go index 44fd29e7..d9aff5a7 100644 --- a/store/keychain/keychain_linux_test.go +++ b/store/keychain/keychain_linux_test.go @@ -53,7 +53,7 @@ func TestResolveDefaultCollection(t *testing.T) { // ReadAlias returns the null object path "/" collections: []dbus.ObjectPath{}, aliasPath: nullObjectPath, - wantErr: errNoDefaultCollection, + wantErr: ErrNoDefaultCollection, }, { name: "rejects syntactically invalid alias path",