fix(auth): make logout reject only the credential tea stored

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 17:14:46 +02:00
1 parent d67963b29a
commit 45e015e7dc
6 files changed
+50 -28

No files matched your search

+10 -4
View File
@@ -98,10 +98,11 @@ func (m Manager) register(instance, user string, account settings.Account, optio
})
}
// Logout removes one account and restores its credential if publication fails.
// An unreadable credential does not prevent removing the account.
func (m Manager) Logout(instance, user string) error {
return m.settings.Transaction(settings.TargetHosts, func(s *settings.Snapshot) (func() error, error) {
// Logout removes one account, restores its credential if publication fails and returns the
// removed credential. An unreadable credential does not prevent removing the account.
func (m Manager) Logout(instance, user string) (Credential, error) {
var removed Credential
err := m.settings.Transaction(settings.TargetHosts, func(s *settings.Snapshot) (func() error, error) {
host := s.Hosts.Hosts[instance]
account, exists := host.Users[user]
if !exists {
@@ -111,6 +112,7 @@ func (m Manager) Logout(instance, user string) error {
if !account.Tokenless() {
credential, err := m.credentials.Load(instance, user, account.CredentialStore)
if err == nil {
removed = credential
// Preserve a compensating copy until the account file is successfully published.
restore = func() error { return m.credentials.Save(instance, user, account.CredentialStore, credential) }
}
@@ -132,4 +134,8 @@ func (m Manager) Logout(instance, user string) error {
s.Hosts.Hosts[instance] = host
return restore, nil
})
if err != nil {
return Credential{}, err
}
return removed, nil
}
+10 -4
View File
@@ -70,7 +70,9 @@ func TestRegisterAndLogoutIsolateAccounts(t *testing.T) {
for _, user := range []string{"alice", "bob", "charlie"} {
require.NoError(t, manager.Register("https://example.org", user, "keyring", "token", HostOptions{}, Credential{AccessToken: "secret-" + user}))
}
require.NoError(t, manager.Logout("https://example.org", "charlie"))
removed, err := manager.Logout("https://example.org", "charlie")
require.NoError(t, err)
require.Equal(t, "secret-charlie", removed.AccessToken)
s, err := config.Load()
require.NoError(t, err)
require.Empty(t, s.Hosts.Hosts["https://example.org"].User)
@@ -154,14 +156,17 @@ func TestLogoutRemovesAccountWithMissingCredential(t *testing.T) {
manager := newTestManager(t, config, &Store{Dir: config.Dir, Keyring: keyring})
require.NoError(t, manager.Register("https://example.org", "alice", settings.StoreKeyring, settings.AuthToken, HostOptions{}, Credential{AccessToken: "secret"}))
clear(keyring.values)
require.NoError(t, manager.Logout("https://example.org", "alice"))
removed, err := manager.Logout("https://example.org", "alice")
require.NoError(t, err)
require.Empty(t, removed.AccessToken)
s, err := config.Load()
require.NoError(t, err)
require.Empty(t, s.Hosts.Hosts["https://example.org"].Users)
require.NoError(t, manager.Register("https://example.org", "alice", settings.StoreKeyring, settings.AuthToken, HostOptions{}, Credential{AccessToken: "secret"}))
// An unavailable keyring keeps the account rather than orphaning its token.
keyring.fail = true
require.Error(t, manager.Logout("https://example.org", "alice"))
_, err = manager.Logout("https://example.org", "alice")
require.Error(t, err)
s, err = config.Load()
require.NoError(t, err)
require.Contains(t, s.Hosts.Hosts["https://example.org"].Users, "alice")
@@ -175,7 +180,8 @@ func TestRegisterHTTPSignRejectsTokenAccounts(t *testing.T) {
s, err := config.Load()
require.NoError(t, err)
require.Equal(t, settings.Account{AuthMethod: settings.AuthSSH, SSHKey: "/keys/id"}, s.Hosts.Hosts["https://example.org"].Users["alice"])
require.NoError(t, manager.Logout("https://example.org", "alice"))
_, err = manager.Logout("https://example.org", "alice")
require.NoError(t, err)
}
func TestRegisterReplacesAnAccountOnlyWhenAsked(t *testing.T) {