From 0ad0415dba09b3313301e8db0611e4e887b5225f Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 2 Aug 2026 17:17:33 +0200 Subject: [PATCH] feat: GetSecret returns full item (all fields: username/uri/notes/fields) --- internal/handlers/handlers.go | 5 +--- internal/vaultwarden/client.go | 42 ++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 045a5fa..7f0517a 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -108,10 +108,7 @@ func (h *Handler) GetSecret(c *fiber.Ctx) error { }) } - return c.JSON(fiber.Map{ - "name": secretName, - "value": value, - }) + return c.JSON(value.ToMap()) } func parseUUIDQuery(field, raw string) (string, error) { diff --git a/internal/vaultwarden/client.go b/internal/vaultwarden/client.go index d6759e8..6a08c2f 100644 --- a/internal/vaultwarden/client.go +++ b/internal/vaultwarden/client.go @@ -126,11 +126,22 @@ func matchesSecretFilter(item DecryptedItem, f SecretFilter) bool { return true } -// GetSecret retrieves a decrypted secret by name. -// It searches by exact name (case-insensitive), then falls back to partial match. -func (c *Client) GetSecret(name string, filter SecretFilter) (string, error) { +// GetSecret retrieves a decrypted secret by name and returns the full item +// (all fields) so the caller can see username/uri/notes/custom fields, not +// just the primary value. Searches by exact name (case-insensitive), then +// falls back to partial match. +func (c *Client) GetSecret(name string, filter SecretFilter) (DecryptedItem, error) { + item, err := c.getSecretItem(name, filter) + if err != nil { + return DecryptedItem{}, err + } + return item, nil +} + +// getSecretItem finds the matching decrypted item without extracting a single value. +func (c *Client) getSecretItem(name string, filter SecretFilter) (DecryptedItem, error) { if name == "" { - return "", fmt.Errorf("secret name cannot be empty") + return DecryptedItem{}, fmt.Errorf("secret name cannot be empty") } c.mu.RLock() @@ -148,18 +159,35 @@ func (c *Client) GetSecret(name string, filter SecretFilter) (string, error) { // Case 1: Exact match. for _, item := range candidates { if strings.EqualFold(item.Name, name) { - return extractSecret(item), nil + return item, nil } } // Case 2: Partial match for _, item := range candidates { if strings.Contains(strings.ToLower(item.Name), key) { logger.Debug.Printf("Partial match found for secret lookup") - return extractSecret(item), nil + return item, nil } } - return "", fmt.Errorf("secret not found") + return DecryptedItem{}, fmt.Errorf("secret not found") +} + +// ToMap renders the decrypted item as a flat map for JSON output. +// The "value" key holds the same primary value extractSecret() would return, +// while the other keys expose every decrypted field. +func (item DecryptedItem) ToMap() map[string]interface{} { + out := map[string]interface{}{ + "name": item.Name, + "type": item.Type, + "username": item.Username, + "password": item.Password, + "uri": item.URI, + "notes": item.Notes, + "fields": item.Fields, + } + out["value"] = extractSecret(item) + return out } // ClearCache triggers a fresh vault sync.