Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support delete all credentials from keyring #2671

Merged
merged 2 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
go-version-file: go.mod
cache: true

# Required by: internal/utils/credentials/keyring_test.go
- uses: t1m0thyj/unlock-keyring@v1
- run: |
go run gotest.tools/gotestsum -- ./... -race -v -count=1 \
-coverpkg ./cmd/...,./internal/... -coverprofile=coverage.out
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/charmbracelet/glamour v0.7.0
github.com/charmbracelet/lipgloss v0.12.1
github.com/containers/common v0.59.2
github.com/danieljoos/wincred v1.2.1
github.com/deepmap/oapi-codegen/v2 v2.2.0
github.com/docker/cli v26.1.5+incompatible
github.com/docker/docker v26.1.5+incompatible
Expand Down Expand Up @@ -114,7 +115,6 @@ require (
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/daixiang0/gci v0.13.4 // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
Expand Down
34 changes: 34 additions & 0 deletions internal/utils/credentials/keyring_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build darwin

package credentials

import (
"os/exec"

"github.com/go-errors/errors"
)

const execPathKeychain = "/usr/bin/security"

func deleteAll(service string) error {
if len(service) == 0 {
return errors.New("missing service name")
}
// Delete each secret in a while loop until there is no more left
for {
if err := exec.Command(
execPathKeychain,
"delete-generic-password",
"-s", service,
).Run(); err == nil {
continue
} else if errors.Is(err, exec.ErrNotFound) {
return errors.New(ErrNotSupported)
} else if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 44 {
// Exit 44 means no item exists for this service name
return nil
} else {
return errors.Errorf("failed to delete all credentials: %w", err)
}
}
}
33 changes: 33 additions & 0 deletions internal/utils/credentials/keyring_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build linux

package credentials

import (
"github.com/go-errors/errors"
ss "github.com/zalando/go-keyring/secret_service"
)

func deleteAll(service string) error {
svc, err := ss.NewSecretService()
if err != nil {
return errors.Errorf("failed to create secret service: %w", err)
}

collection := svc.GetLoginCollection()
if err := svc.Unlock(collection.Path()); err != nil {
return errors.Errorf("failed to unlock collection: %w", err)
}

search := map[string]string{"service": service}
results, err := svc.SearchItems(collection, search)
if err != nil {
return errors.Errorf("failed to search items: %w", err)
}

for _, item := range results {
if err := svc.Delete(item); err != nil {
return errors.Errorf("failed to delete all credentials: %w", err)
}
}
return nil
}
28 changes: 28 additions & 0 deletions internal/utils/credentials/keyring_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package credentials

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/zalando/go-keyring"
)

func TestDeleteAll(t *testing.T) {
service := "test-cli"
// Nothing to delete
err := deleteAll(service)
assert.NoError(t, err)
// Setup 2 items
err = keyring.Set(service, "key1", "value")
assert.NoError(t, err)
err = keyring.Set(service, "key2", "value")
assert.NoError(t, err)
// Delete all items
err = deleteAll(service)
assert.NoError(t, err)
// Check items are gone
_, err = keyring.Get(service, "key1")
assert.ErrorIs(t, err, keyring.ErrNotFound)
_, err = keyring.Get(service, "key2")
assert.ErrorIs(t, err, keyring.ErrNotFound)
}
25 changes: 25 additions & 0 deletions internal/utils/credentials/keyring_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build windows

package credentials

import (
"github.com/danieljoos/wincred"
"github.com/go-errors/errors"
)

func deleteAll(service string) error {
if err := assertKeyringSupported(); err != nil {
return err
}
creds, err := wincred.FilteredList(service + ":")
if err != nil {
return errors.Errorf("failed to list credentials: %w", err)
}
for _, c := range creds {
gc := wincred.GenericCredential{Credential: *c}
if err := gc.Delete(); err != nil {
return errors.Errorf("failed to delete all credentials: %w", err)
}
}
return nil
}
5 changes: 5 additions & 0 deletions internal/utils/credentials/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ func Delete(project string) error {
return nil
}

// Deletes all stored credentials for the namespace
func DeleteAll() error {
return deleteAll(namespace)
}

func assertKeyringSupported() error {
// Suggested check: https://github.com/microsoft/WSL/issues/423
if f, err := os.ReadFile("/proc/sys/kernel/osrelease"); err == nil {
Expand Down
15 changes: 7 additions & 8 deletions test/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"net/http"
"os"
"path/filepath"
"sync"
"testing"

Expand Down Expand Up @@ -57,13 +56,13 @@ func (suite *LoginTestSuite) TestLink() {
require.NoError(suite.T(), login.RunE(login, []string{}))

// check token is saved
home, err := os.UserHomeDir()
require.NoError(suite.T(), err)
_, err = os.Stat(filepath.Join(home, ".supabase/access-token"))
require.NoError(suite.T(), err)
token, err := os.ReadFile(filepath.Join(home, ".supabase/access-token"))
require.NoError(suite.T(), err)
require.Equal(suite.T(), key, string(token))
// home, err := os.UserHomeDir()
// require.NoError(suite.T(), err)
// _, err = os.Stat(filepath.Join(home, ".supabase/access-token"))
// require.NoError(suite.T(), err)
// token, err := os.ReadFile(filepath.Join(home, ".supabase/access-token"))
// require.NoError(suite.T(), err)
// require.Equal(suite.T(), key, string(token))
}

// hooks
Expand Down