added RateLimitRule.Audience field
This commit is contained in:
+32
-7
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user