added RateLimitRule.Audience field

This commit is contained in:
Gani Georgiev
2024-11-08 18:04:13 +02:00
parent 0e56521e8a
commit f6aef4471d
37 changed files with 387 additions and 141 deletions
+32 -7
View File
@@ -617,19 +617,28 @@ func checkUniqueRuleLabel(value any) error {
return validators.ErrUnsupportedValueType
}
labels := make(map[string]struct{}, len(rules))
existing := make([]string, 0, len(rules))
for i, rule := range rules {
_, ok := labels[rule.Label]
if ok {
fullKey := rule.Label + "@@" + rule.Audience
var conflicts bool
for _, key := range existing {
if strings.HasPrefix(key, fullKey) || strings.HasPrefix(fullKey, key) {
conflicts = true
break
}
}
if conflicts {
return validation.Errors{
strconv.Itoa(i): validation.Errors{
"label": validation.NewError("validation_duplicated_rate_limit_tag", "Rate limit tag with label "+rule.Label+" already exists.").
"label": validation.NewError("validation_conflcting_rate_limit_rule", "Rate limit rule configuration with label "+rule.Label+" already exists or conflicts with another rule.").
SetParams(map[string]any{"label": rule.Label}),
},
}
} else {
labels[rule.Label] = struct{}{}
existing = append(existing, fullKey)
}
}
@@ -638,6 +647,13 @@ func checkUniqueRuleLabel(value any) error {
var rateLimitRuleLabelRegex = regexp.MustCompile(`^(\w+\ \/[\w\/-]*|\/[\w\/-]*|^\w+\:\w+|\*\:\w+|\w+)$`)
// The allowed RateLimitRule.Audience values
const (
RateLimitRuleAudienceAll = ""
RateLimitRuleAudienceGuest = "@guest"
RateLimitRuleAudienceAuth = "@auth"
)
type RateLimitRule struct {
// Label is the identifier of the current rule.
//
@@ -652,12 +668,18 @@ type RateLimitRule struct {
// - POST /api/collections/
Label string `form:"label" json:"label"`
// MaxRequests is the max allowed number of requests per Duration.
MaxRequests int `form:"maxRequests" json:"maxRequests"`
// Audience specifies the auth group the rule should apply for:
// - "" - both guests and authenticated users (default)
// - "guest" - only for guests
// - "auth" - only for authenticated users
Audience string `form:"audience" json:"audience"`
// Duration specifies the interval (in seconds) per which to reset
// the counted/accumulated rate limiter tokens.
Duration int64 `form:"duration" json:"duration"`
// MaxRequests is the max allowed number of requests per Duration.
MaxRequests int `form:"maxRequests" json:"maxRequests"`
}
// Validate makes RateLimitRule validatable by implementing [validation.Validatable] interface.
@@ -666,6 +688,9 @@ func (c RateLimitRule) Validate() error {
validation.Field(&c.Label, validation.Required, validation.Match(rateLimitRuleLabelRegex)),
validation.Field(&c.MaxRequests, validation.Required, validation.Min(1)),
validation.Field(&c.Duration, validation.Required, validation.Min(1)),
validation.Field(&c.Audience,
validation.In(RateLimitRuleAudienceAll, RateLimitRuleAudienceGuest, RateLimitRuleAudienceAuth),
),
)
}
+112 -1
View File
@@ -539,6 +539,86 @@ func TestRateLimitsConfigValidate(t *testing.T) {
},
[]string{},
},
{
"duplicated rules with the same audience",
core.RateLimitsConfig{
Enabled: true,
Rules: []core.RateLimitRule{
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
},
{
Label: "/a",
Duration: 2,
MaxRequests: 3,
},
},
},
[]string{"rules"},
},
{
"duplicated rule with conflicting audience (A)",
core.RateLimitsConfig{
Enabled: true,
Rules: []core.RateLimitRule{
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
},
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
Audience: core.RateLimitRuleAudienceGuest,
},
},
},
[]string{"rules"},
},
{
"duplicated rule with conflicting audience (B)",
core.RateLimitsConfig{
Enabled: true,
Rules: []core.RateLimitRule{
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
Audience: core.RateLimitRuleAudienceAuth,
},
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
},
},
},
[]string{"rules"},
},
{
"duplicated rule with non-conflicting audience",
core.RateLimitsConfig{
Enabled: true,
Rules: []core.RateLimitRule{
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
Audience: core.RateLimitRuleAudienceAuth,
},
{
Label: "/a",
Duration: 1,
MaxRequests: 2,
Audience: core.RateLimitRuleAudienceGuest,
},
},
},
[]string{},
},
}
for _, s := range scenarios {
@@ -611,8 +691,9 @@ func TestRateLimitRuleValidate(t *testing.T) {
Label: "@abc",
Duration: -1,
MaxRequests: -1,
Audience: "invalid",
},
[]string{"label", "duration", "maxRequests"},
[]string{"label", "duration", "maxRequests", "audience"},
},
{
"valid data (name)",
@@ -659,6 +740,36 @@ func TestRateLimitRuleValidate(t *testing.T) {
},
[]string{},
},
{
"invalid audience",
core.RateLimitRule{
Label: "/a/b/",
Duration: 1,
MaxRequests: 1,
Audience: "invalid",
},
[]string{"audience"},
},
{
"valid audience - " + core.RateLimitRuleAudienceGuest,
core.RateLimitRule{
Label: "POST /a/b/",
Duration: 1,
MaxRequests: 1,
Audience: core.RateLimitRuleAudienceGuest,
},
[]string{},
},
{
"valid audience - " + core.RateLimitRuleAudienceAuth,
core.RateLimitRule{
Label: "POST /a/b/",
Duration: 1,
MaxRequests: 1,
Audience: core.RateLimitRuleAudienceAuth,
},
[]string{},
},
}
for _, s := range scenarios {