Filtering of synchronized secrets on collection, organization, and/or folder (#12)

* Filtering feature, minor logic changes
+: Add IP validation (to properly log IP even when trusted proxy is missing)
+: Added synchronization of decrypted organization, collection, and folder names (for filtering purposes)
+: Changed sync behavior to prevent collapsing multiple logins with the same name
+: Added possibility to filter logins based on organization, collection, and folder name/id in GetSecret
+: Added support for space character in secret name validation (it is also properly URL-decoded)

* Update README

* Fixes related to issues reported by CodeRabbit:
+: Changed british use of organisations in README
+: Reduced line count in parseSecretFilters by including helper function for filter dimensions resolution
+: Added comment clarifying absence of existence checking on dimension IDs as opposed to names
+: Removed efensive comma split logic on IPs (redundant due to IPValidation setting on Fiber)
+: Changed Query validator to align with name validator (mainly removing hard-coded values)
+: Added comments to decryptVaultLabel for clarity
+: Change empty SyncNameMaps instance with emptySyncNameMaps helper for consistency
+: Add lexical sort for lookupIDByName for deterministic result in case of multiple matches

* Bumped logging to warn and added entity metadata in log

* Unit tests:
+: Added unit tests for every newly introduced or changed function
+: Rewrote NewClient constructor to accept optional preloaded data (so it can be used in test scenarios)

* Changes requested by CodeRabbit
This commit is contained in:
Jethro Young authored and GitHub committed 2026-05-20 23:26:29 +02:00
1 parent cac042a5c4
commit 76b82d4dba
11 files changed
+1174 -88

No files matched your search

+15 -2
View File
@@ -8,7 +8,7 @@ import (
const SecretNameMaxLength = 255
var SecretNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-\./]*[a-zA-Z0-9]$`)
var SecretNamePattern = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9 _\-\./]*[a-zA-Z0-9])?$`)
func IsValidSecretName(name string) bool {
if len(name) == 0 || len(name) > SecretNameMaxLength {
@@ -43,4 +43,17 @@ func SanitizeSecretName(name string) (string, bool) {
}
return "", false
}
}
func IsValidFilterQueryValue(s string) bool {
s = strings.TrimSpace(s)
if s == "" || len(s) > SecretNameMaxLength {
return false
}
for _, r := range s {
if r < 32 || r == 127 {
return false
}
}
return true
}
+42
View File
@@ -0,0 +1,42 @@
package validators
import (
"strings"
"testing"
)
func TestIsValidFilterQueryValue(t *testing.T) {
t.Parallel()
// Test allowed length of filter query values
longOK := strings.Repeat("a", SecretNameMaxLength)
tooLong := strings.Repeat("a", SecretNameMaxLength+1)
tests := []struct {
name string
input string
want bool
}{
// Add test cases here
{"simple name", "Acme Corp", true},
{"trimmed valid", " Dev Team ", true},
{"unicode printable", "Team-α", true},
{"max length", longOK, true},
{"empty", "", false},
{"whitespace only", " ", false},
{"too long", tooLong, false},
{"newline", "bad\nname", false},
{"tab", "bad\tname", false},
{"del control", "bad\x7fname", false},
{"null", "bad\x00name", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := IsValidFilterQueryValue(tt.input); got != tt.want {
t.Errorf("IsValidFilterQueryValue(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}