Harden API-key model: per-key scoping + revocable keys, and configurable/whitelist-aware rate limiting (#16)

* API scope and rate limiting

* improvements from review
This commit is contained in:
Thijs Herman authored and GitHub committed 2026-05-28 11:45:16 +02:00
1 parent a400e07ae2
commit 495edffde3
11 files changed
+843 -42

No files matched your search

+44 -1
View File
@@ -55,7 +55,7 @@ func TestMatchesSecretFilter(t *testing.T) {
SecretFilter{
OrganizationID: testOrgID,
CollectionID: testCollectionID,
FolderID: testFolderID,
FolderID: testFolderID,
},
true,
},
@@ -64,6 +64,35 @@ func TestMatchesSecretFilter(t *testing.T) {
SecretFilter{OrganizationID: testOrgID, CollectionID: "77777777-7777-4777-8777-777777777777"},
false,
},
// Server-side scope (plural fields).
{"scope org in set", SecretFilter{OrganizationIDs: []string{testOrgID2, testOrgID}}, true},
{"scope org not in set", SecretFilter{OrganizationIDs: []string{testOrgID2}}, false},
{"scope collection intersects", SecretFilter{CollectionIDs: []string{testCollectionID}}, true},
{
"scope collection disjoint",
SecretFilter{CollectionIDs: []string{"77777777-7777-4777-8777-777777777777"}},
false,
},
{
"scope org and collection both match",
SecretFilter{OrganizationIDs: []string{testOrgID}, CollectionIDs: []string{testCollectionID}},
true,
},
{
"scope org ok but collection disjoint",
SecretFilter{OrganizationIDs: []string{testOrgID}, CollectionIDs: []string{"77777777-7777-4777-8777-777777777777"}},
false,
},
{
"scope narrowed further by client filter",
SecretFilter{OrganizationID: testOrgID, OrganizationIDs: []string{testOrgID}},
true,
},
{
"client filter cannot widen beyond scope",
SecretFilter{OrganizationID: testOrgID, OrganizationIDs: []string{testOrgID2}},
false,
},
}
for _, tt := range tests {
@@ -75,3 +104,17 @@ func TestMatchesSecretFilter(t *testing.T) {
})
}
}
func TestMatchesSecretFilter_PersonalItemExcludedByOrgScope(t *testing.T) {
t.Parallel()
// A personal (no-org) item must be excluded once a key scopes to organizations.
personal := DecryptedItem{ID: "personal-1", Name: "personal-secret"}
if matchesSecretFilter(personal, SecretFilter{OrganizationIDs: []string{testOrgID}}) {
t.Error("personal item should be excluded by an org scope")
}
if !matchesSecretFilter(personal, SecretFilter{}) {
t.Error("personal item should match an empty (full-access) scope")
}
}