[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taldir] branch master updated: fix rate limiting logic
From: |
gnunet |
Subject: |
[taler-taldir] branch master updated: fix rate limiting logic |
Date: |
Mon, 11 Jul 2022 18:29:47 +0200 |
This is an automated email from the git hooks/post-receive script.
martin-schanzenbach pushed a commit to branch master
in repository taldir.
The following commit(s) were added to refs/heads/master by this push:
new a1a6414 fix rate limiting logic
a1a6414 is described below
commit a1a641455a0ded785a7337c183e3ef28dc3da76d
Author: Martin Schanzenbach <schanzen@gnunet.org>
AuthorDate: Mon Jul 11 18:29:44 2022 +0200
fix rate limiting logic
---
cmd/taldir-server/main_test.go | 34 ++++++++++++
pkg/taldir/taldir.go | 116 +++++++++++++++++++++++++----------------
taldir.conf | 4 +-
3 files changed, 108 insertions(+), 46 deletions(-)
diff --git a/cmd/taldir-server/main_test.go b/cmd/taldir-server/main_test.go
index 77c18c9..f5b6675 100644
--- a/cmd/taldir-server/main_test.go
+++ b/cmd/taldir-server/main_test.go
@@ -181,6 +181,40 @@ func TestReRegisterRequestTooMany(s *testing.T) {
}
+func TestSolutionRequestTooMany(s *testing.T) {
+ t.ClearDatabase()
+
+ req, _ := http.NewRequest("POST", "/register/test",
bytes.NewBuffer(validRegisterRequest))
+ response := executeRequest(req)
+
+ if http.StatusAccepted != response.Code {
+ s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted,
response.Code)
+ }
+ h_addr := getHAddress("abc@test")
+ solution :=
util.GenerateSolution("000G006XE97PTWV3B7AJNCRQZA6BF26HPV3XZ07293FMY7KD4181946A90",
"wrongSolution")
+ solutionJSON := "{\"solution\": \"" + solution + "\"}"
+ req, _ = http.NewRequest("POST", "/" + h_addr,
bytes.NewBuffer([]byte(solutionJSON)))
+ response = executeRequest(req)
+ if http.StatusForbidden != response.Code {
+ s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden,
response.Code)
+ }
+ req, _ = http.NewRequest("POST", "/" + h_addr,
bytes.NewBuffer([]byte(solutionJSON)))
+ response = executeRequest(req)
+ if http.StatusForbidden != response.Code {
+ s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden,
response.Code)
+ }
+ req, _ = http.NewRequest("POST", "/" + h_addr,
bytes.NewBuffer([]byte(solutionJSON)))
+ response = executeRequest(req)
+ if http.StatusForbidden != response.Code {
+ s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden,
response.Code)
+ }
+ req, _ = http.NewRequest("POST", "/" + h_addr,
bytes.NewBuffer([]byte(solutionJSON)))
+ response = executeRequest(req)
+ if http.StatusTooManyRequests != response.Code {
+ s.Errorf("Expected response code %d. Got %d\n",
http.StatusTooManyRequests, response.Code)
+ }
+
+}
func TestRegisterRequestWrongPubkey(s *testing.T) {
t.ClearDatabase()
diff --git a/pkg/taldir/taldir.go b/pkg/taldir/taldir.go
index b5dd9d8..76c5a6a 100644
--- a/pkg/taldir/taldir.go
+++ b/pkg/taldir/taldir.go
@@ -78,8 +78,15 @@ type Taldir struct {
// Code TTL
CodeTtl time.Duration
- // Code retries max
- CodeRetryMax int
+ // How often may a challenge be requested
+ ValidationInitiationMax int
+
+ // How often may a solution be attempted (in the given timeframe)
+ SolutionAttemptsMax int
+
+ // The timeframe for the above solution attempts
+ SolutionTimeframe time.Duration
+
// Code length in bytes before encoding
CodeBytes int
@@ -161,11 +168,11 @@ type Entry struct {
// Public key of the user to register in base32
PublicKey string `json:"public_key"`
- // Time of (re)registration. In Unix epoch microseconds)
+ // Time of (re)registration.
RegisteredAt int64 `json:"-"`
// How long the registration lasts in microseconds
- Duration int64 `json:"-"`
+ Duration time.Duration `json:"-"`
}
// A validation is created when a registration for an entry is initiated.
@@ -196,7 +203,13 @@ type Validation struct {
TimeframeStart time.Time
// How often was this validation re-initiated
- RetryCount int
+ InitiationCount int
+
+ // How often was a solution for this validation tried
+ SolutionAttemptCount int
+
+ // The beginning of the last solution timeframe
+ LastSolutionTimeframeStart time.Time
}
type ErrorDetail struct {
@@ -241,13 +254,6 @@ type ValidationConfirmation struct {
Solution string `json:"solution"`
}
-// matcher is a language.Matcher configured for all supported languages.
-var langMatcher = language.NewMatcher([]language.Tag{
- language.BritishEnglish,
- //language.Norwegian,
- language.German,
-})
-
// Primary lookup function.
// Allows the caller to query a wallet key using the hash(!) of the
// identity, e.g. SHA512(<email address>)
@@ -300,21 +306,40 @@ func (t *Taldir) validationRequest(w http.ResponseWriter,
r *http.Request){
w.WriteHeader(http.StatusNotFound)
return
}
+ validation.SolutionAttemptCount++
+ if
validation.LastSolutionTimeframeStart.Add(t.SolutionTimeframe).After(time.Now())
{
+ if validation.SolutionAttemptCount > t.SolutionAttemptsMax {
+ w.WriteHeader(429)
+ rlResponse := RateLimitedResponse{
+ Code: gana.TALDIR_REGISTER_RATE_LIMITED,
+ RequestFrequency: t.RequestFrequency,
+ Hint: "Solution attempt rate limit reached",
+ }
+ jsonResp, _ := json.Marshal(rlResponse)
+ w.Write(jsonResp)
+ return
+ }
+ } else {
+ log.Println("New solution timeframe set.")
+ validation.LastSolutionTimeframeStart = time.Now()
+ validation.SolutionAttemptCount = 1
+ }
+ t.Db.Save(&validation)
expectedSolution := util.GenerateSolution(validation.PublicKey,
validation.Code)
if confirm.Solution != expectedSolution {
- // FIXME how TF do we rate limit here??
w.WriteHeader(http.StatusForbidden)
return
}
// FIXME: Expire validations somewhere?
err = t.Db.Delete(&validation).Error
if err != nil {
+ log.Fatalf("Error deleting validation")
w.WriteHeader(http.StatusInternalServerError)
return
}
entry.HsAddress = saltHAddress(validation.HAddress, t.Salt)
entry.Inbox = validation.Inbox
- entry.Duration = validation.Duration
+ entry.Duration = time.Duration(validation.Duration)
entry.RegisteredAt = time.Now().UnixMicro()
entry.PublicKey = validation.PublicKey
err = t.Db.First(&entry, "hs_address = ?", entry.HsAddress).Error
@@ -350,6 +375,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r
*http.Request){
w.Write(resp)
return
}
+ // Check if this validation method is supported or not.
if !t.Validators[vars["method"]] {
errDetail.Code = gana.TALDIR_METHOD_NOT_SUPPORTED
errDetail.Hint = "Unsupported method"
@@ -359,47 +385,39 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r
*http.Request){
w.Write(resp)
return
}
+
+ // Setup validation object. Retrieve object from DB if it already
+ // exists.
h := sha512.New()
h.Write([]byte(req.Address))
- validation.HAddress = util.EncodeBinaryToString(h.Sum(nil))
- // We first try if there is already an entry for this address which
- // is still valid and the duration is not extended.
+ h_address := util.EncodeBinaryToString(h.Sum(nil))
+ validation.HAddress = h_address
hs_address := saltHAddress(validation.HAddress, t.Salt)
err = t.Db.First(&entry, "hs_address = ?", hs_address).Error
if err == nil {
log.Println("Entry for this address already exists..")
- lastRegValidity := entry.RegisteredAt + entry.Duration
- requestedValidity := time.Now().UnixMicro() + req.Duration
- earliestReRegistration := entry.RegisteredAt + t.RequestFrequency
- // Rate limit re-registrations.
- if time.Now().UnixMicro() < earliestReRegistration {
- w.WriteHeader(429)
- rlResponse := RateLimitedResponse{
- Code: gana.TALDIR_REGISTER_RATE_LIMITED,
- RequestFrequency: t.RequestFrequency,
- Hint: "Registration rate limit reached",
- }
- jsonResp, _ := json.Marshal(rlResponse)
- w.Write(jsonResp)
- return
- }
- // Do not allow re-registrations with shorter duration.
- if requestedValidity <= lastRegValidity {
- w.WriteHeader(200)
- // FIXME how to return how long it is already paid for??
+ regAt := time.UnixMicro(entry.RegisteredAt)
+ entryValidity := regAt.Add(entry.Duration)
+ log.Printf("Entry valid until: %s , requested until: %s\n", entryValidity,
time.Now().Add(time.Duration(req.Duration)))
+ if time.Now().Add(time.Duration(req.Duration)).Before(entryValidity) {
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+ w.Write([]byte("{\"valid_until\": " + entryValidity.String() + "}"))
return
}
}
- err = t.Db.First(&validation, "h_address = ?", validation.HAddress).Error
+ err = t.Db.First(&validation, "h_address = ?", h_address).Error
validation.Code = util.GenerateCode(t.CodeBytes)
validation.Inbox = req.Inbox
validation.Duration = req.Duration
validation.PublicKey = req.PublicKey
+ validation.SolutionAttemptCount = 0
+ validation.LastSolutionTimeframeStart = time.Now()
if err == nil {
- // FIXME: Validation already pending for this address
- // How should we proceed here? Expire old validations?
+ // Limit re-initiation attempts
+ validation.InitiationCount++
if time.Now().Before(validation.TimeframeStart.Add(t.CodeTtl)) {
- if validation.RetryCount >= t.CodeRetryMax {
+ if validation.InitiationCount > t.ValidationInitiationMax {
w.WriteHeader(429)
rlResponse := RateLimitedResponse{
Code: gana.TALDIR_REGISTER_RATE_LIMITED,
@@ -411,14 +429,14 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r
*http.Request){
t.Db.Delete(&validation)
return
}
- validation.RetryCount++
} else {
log.Println("Validation stale, resetting retry counter")
validation.TimeframeStart = time.Now()
- validation.RetryCount = 0
+ validation.InitiationCount = 1
}
err = t.Db.Save(&validation).Error
} else {
+ validation.InitiationCount = 1
validation.TimeframeStart = time.Now()
err = t.Db.Create(&validation).Error
}
@@ -426,7 +444,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r
*http.Request){
w.WriteHeader(http.StatusInternalServerError)
return
}
- fmt.Println("Address registration request created:", validation)
+ log.Println("Address registration request created:", validation)
if !t.Cfg.Section("taldir-" + vars["method"]).HasKey("command") {
log.Fatal(err)
t.Db.Delete(&validation)
@@ -449,7 +467,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r
*http.Request){
return
}
w.WriteHeader(202)
- fmt.Printf("Output from method script %s is %s\n", path, out)
+ log.Printf("Output from method script %s is %s\n", path, out)
}
func notImplemented(w http.ResponseWriter, r *http.Request) {
@@ -655,13 +673,21 @@ func (t *Taldir) Initialize(cfgfile string) {
t.Validators[a] = true
}
t.CodeBytes =
t.Cfg.Section("taldir").Key("activation_code_bytes").MustInt(16)
- t.CodeRetryMax =
t.Cfg.Section("taldir").Key("activation_retry_max").MustInt(2)
+ t.ValidationInitiationMax =
t.Cfg.Section("taldir").Key("validation_initiation_max").MustInt(3)
+ t.SolutionAttemptsMax =
t.Cfg.Section("taldir").Key("solution_attempt_max").MustInt(3)
+
validationTtlStr :=
t.Cfg.Section("taldir").Key("activation_code_ttl").MustString("5m")
t.CodeTtl, err = time.ParseDuration(validationTtlStr)
if err != nil {
log.Fatal(err)
}
+ retryTimeframeStr :=
t.Cfg.Section("taldir").Key("code_attempt_timeframe").MustString("1h")
+ t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr)
+ if err != nil {
+ log.Fatal(err)
+ }
+
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s
sslmode=disable",
t.Cfg.Section("taldir-pq").Key("host").MustString("localhost"),
t.Cfg.Section("taldir-pq").Key("port").MustInt64(5432),
diff --git a/taldir.conf b/taldir.conf
index a34bd21..1c9a899 100644
--- a/taldir.conf
+++ b/taldir.conf
@@ -11,8 +11,10 @@ default_doc_lang = en-US
default_tos_path = terms/
default_pp_path = privacy/
activation_code_bytes = 16
-activation_retry_max = 2
+validation_initiation_max = 3
+solution_attempt_max = 3
activation_code_ttl = 10m
+solution_attempt_timeframe = 1h
[taldir-email]
sender = "taldir@taler.net"
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-taldir] branch master updated: fix rate limiting logic,
gnunet <=