Files
kandev/apps/backend/internal/secrets/store.go
T
João Salavisa bb5a81a396 refactor(secrets): detect absent secret via ErrNotFound sentinel (#1770)
* refactor(secrets): detect absent secret via ErrNotFound sentinel

Replace the "secret not found:" string-prefix matching with an exported
secrets.ErrNotFound sentinel, matched via errors.Is. The store(s) now
return fmt.Errorf("%w: %s", ErrNotFound, id); the rendered message is
byte-identical so logs are unchanged.

Consumers migrated off the prefix:
- internal/plugins/config.go isSecretNotFound (behind host GetSecret/
  DeleteSecret and service.go vault rollback)
- internal/integrations/secretadapter Exists (jira/linear/sentry inherit
  the change through the shared adapter)

Adds a non-Postgres-gated secrets test asserting errors.Is(err,
ErrNotFound) plus message-unchanged coverage; updates the affected test
fakes to wrap the sentinel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(secrets): address review — eager t.Cleanup, faithful fake Delete

- sqlite_store_test: register sqlxDB close via t.Cleanup immediately
  after creation, before the NewMasterKeyProvider/Provide fatal paths
  (per the t.Cleanup-immediately convention).
- secretadapter_test: fakeStore.Delete now returns wrapped
  secrets.ErrNotFound for an absent id, mirroring the real store and
  the fakeSecretRevealer.Delete fake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 01:08:29 +01:00

38 lines
1.2 KiB
Go

package secrets
import (
"context"
"errors"
)
// ErrNotFound is the sentinel returned (wrapped) by store implementations when
// a secret id is absent. Consumers that must tell "absent entry" apart from a
// genuine backend fault should match with errors.Is(err, secrets.ErrNotFound)
// rather than string-matching the error text.
var ErrNotFound = errors.New("secret not found")
// SecretStore abstracts secret storage. Implementations handle
// encryption/decryption internally.
type SecretStore interface {
// Create stores a new secret (encrypts the value).
Create(ctx context.Context, secret *SecretWithValue) error
// Get retrieves secret metadata (without value).
Get(ctx context.Context, id string) (*Secret, error)
// Reveal retrieves the decrypted value of a secret.
Reveal(ctx context.Context, id string) (string, error)
// Update updates a secret's name and/or value.
Update(ctx context.Context, id string, req *UpdateSecretRequest) error
// Delete permanently removes a secret.
Delete(ctx context.Context, id string) error
// List returns all secrets without values.
List(ctx context.Context) ([]*SecretListItem, error)
// Close releases resources.
Close() error
}