gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taldir] branch master updated: improve handling of registrations


From: gnunet
Subject: [taler-taldir] branch master updated: improve handling of registrations
Date: Tue, 12 Jul 2022 12:43:29 +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 eb21817  improve handling of registrations
eb21817 is described below

commit eb21817b69582e5774a07e2ae306f76b85aefba7
Author: Martin Schanzenbach <schanzen@gnunet.org>
AuthorDate: Tue Jul 12 12:43:26 2022 +0200

    improve handling of registrations
---
 cmd/taldir-server/main_test.go |   6 +-
 pkg/rest/taldir.go             | 160 ++++++++++++++++++++++++++++-------------
 2 files changed, 113 insertions(+), 53 deletions(-)

diff --git a/cmd/taldir-server/main_test.go b/cmd/taldir-server/main_test.go
index a0dfbd4..4390037 100644
--- a/cmd/taldir-server/main_test.go
+++ b/cmd/taldir-server/main_test.go
@@ -47,12 +47,12 @@ var validRegisterRequest = []byte(`
   }
 `)
 
-var validRegisterRequestShort = []byte(`
+var validRegisterRequestUnmodified = []byte(`
   {
     "address": "abc@test",
     "public_key": "000G006XE97PTWV3B7AJNCRQZA6BF26HPV3XZ07293FMY7KD4181946A90",
     "inbox_url": "myinbox@xyz",
-    "duration": 23328000000000
+    "duration": 0
   }
 `)
 
@@ -194,7 +194,7 @@ func TestReRegisterRequest(s *testing.T) {
   if http.StatusNoContent != response.Code {
     s.Errorf("Expected response code %d. Got %d\n", http.StatusNoContent, 
response.Code)
   }
-  req, _ = http.NewRequest("POST", "/register/test", 
bytes.NewBuffer(validRegisterRequestShort))
+  req, _ = http.NewRequest("POST", "/register/test", 
bytes.NewBuffer(validRegisterRequestUnmodified))
   response = executeRequest(req)
 
   if http.StatusOK != response.Code {
diff --git a/pkg/rest/taldir.go b/pkg/rest/taldir.go
index 280664a..95e2c71 100644
--- a/pkg/rest/taldir.go
+++ b/pkg/rest/taldir.go
@@ -203,17 +203,28 @@ type Validation struct {
   // Public key of the user to register
   PublicKey string `json:"public_key"`
 
-  // When does this validation timeframe begin (for retry calculation)
-  TimeframeStart time.Time
-
-  // How often was this validation re-initiated
-  InitiationCount int
-
   // How often was a solution for this validation tried
   SolutionAttemptCount int
 
   // The beginning of the last solution timeframe
   LastSolutionTimeframeStart time.Time
+
+  // The order ID associated with this validation
+  OrderId string `json:"-"`
+}
+
+type ValidationMetadata struct {
+  // ORM
+  gorm.Model `json:"-"`
+
+  // The hash (SHA512) of the address
+  HAddress string `json:"h_address"`
+
+  // When does this validation timeframe begin (for retry calculation)
+  TimeframeStart time.Time
+
+  // How often was this validation re-initiated for this address
+  InitiationCount int
 }
 
 type ErrorDetail struct {
@@ -365,6 +376,34 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, 
r *http.Request){
   w.WriteHeader(http.StatusNoContent)
 }
 
+func (t *Taldir) IsRateLimited(h_address string) (bool, error) {
+  var validationMetadata ValidationMetadata
+  err := t.Db.First(&validationMetadata, "h_address = ?", h_address).Error
+  // NOTE: Check rate limit
+  if err == nil {
+    // Limit re-initiation attempts
+    validationMetadata.InitiationCount++
+    if 
time.Now().Before(validationMetadata.TimeframeStart.Add(t.ValidationTimeframe)) 
{
+      if validationMetadata.InitiationCount > t.ValidationInitiationMax {
+        return true, nil
+      }
+    } else {
+      log.Println("Validation stale, resetting retry counter")
+      validationMetadata.TimeframeStart = time.Now()
+      validationMetadata.InitiationCount = 1
+    }
+    err = t.Db.Save(&validationMetadata).Error
+  } else  {
+    validationMetadata.HAddress = h_address
+    validationMetadata.InitiationCount = 1
+    validationMetadata.TimeframeStart = time.Now()
+    err = t.Db.Create(&validationMetadata).Error
+  }
+  if err != nil {
+    return false, err
+  }
+  return false, nil
+}
 
 func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request){
   vars := mux.Vars(r)
@@ -409,59 +448,54 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
   // Round to the nearest multiple of a month
   reqDuration := time.Duration(req.Duration * 1000)
   reqDuration = reqDuration.Round(MONTH_DURATION)
-  validation.Duration = reqDuration.Microseconds()
   if err == nil {
+    // Check if  this entry is to be modified or extended
+    entryModified := (req.Inbox != entry.Inbox) ||
+                     (req.PublicKey != entry.PublicKey)
     log.Println("Entry for this address already exists..")
     regAt := time.UnixMicro(entry.RegisteredAt)
     entryValidity := regAt.Add(entry.Duration)
-    requestedValidity := time.Now().Add(reqDuration)
-    log.Printf("Entry valid until: %s , requested until: %s\n", entryValidity, 
time.Now().Add(reqDuration))
+    requestedValidity := entryValidity.Add(reqDuration)
+    log.Printf("Entry valid until: %s , requested until: %s\n", entryValidity, 
requestedValidity)
     // NOTE: The extension must be at least one month
-    if requestedValidity.Before(entryValidity.Add(MONTH_DURATION)) {
+    if (reqDuration.Microseconds() == 0) && !entryModified {
+      // Nothing changed. Return validity
       w.WriteHeader(http.StatusOK)
       w.Header().Set("Content-Type", "application/json")
       w.Write([]byte("{\"valid_until\": " + entryValidity.String() + "}"))
       return
     } else {
-      validation.Duration = 
entryValidity.Sub(requestedValidity).Round(MONTH_DURATION).Microseconds()
+      // Entry modified or duration extension requested
     }
   }
-  err = t.Db.First(&validation, "h_address = ?", h_address).Error
-  validation.Challenge = util.GenerateChallenge(t.ChallengeBytes)
-  validation.Inbox = req.Inbox
-  validation.PublicKey = req.PublicKey
-  validation.SolutionAttemptCount = 0
-  validation.LastSolutionTimeframeStart = time.Now()
-  if err == nil {
-    // Limit re-initiation attempts
-    validation.InitiationCount++
-    if time.Now().Before(validation.TimeframeStart.Add(t.ValidationTimeframe)) 
{
-      if validation.InitiationCount > t.ValidationInitiationMax {
-        w.WriteHeader(429)
-        rlResponse := RateLimitedResponse{
-          Code: gana.TALDIR_REGISTER_RATE_LIMITED,
-          RequestFrequency: t.ValidationTimeframe.Microseconds() / 
int64(t.ValidationInitiationMax),
-          Hint: "Registration rate limit reached",
-        }
-        jsonResp, _ := json.Marshal(rlResponse)
-        w.Write(jsonResp)
-        t.Db.Delete(&validation)
-        return
-      }
-    } else {
-      log.Println("Validation stale, resetting retry counter")
-      validation.TimeframeStart = time.Now()
-      validation.InitiationCount = 1
-    }
-    err = t.Db.Save(&validation).Error
-  } else  {
-    validation.InitiationCount = 1
-    validation.TimeframeStart = time.Now()
-    err = t.Db.Create(&validation).Error
-  }
-  if err != nil {
+  rateLimited, err := t.IsRateLimited(h_address)
+  if nil != err {
+    log.Printf("Error checking rate limit! %w", err)
     w.WriteHeader(http.StatusInternalServerError)
     return
+  } else if rateLimited {
+    w.WriteHeader(http.StatusTooManyRequests)
+    rlResponse := RateLimitedResponse{
+      Code: gana.TALDIR_REGISTER_RATE_LIMITED,
+      RequestFrequency: t.ValidationTimeframe.Microseconds() / 
int64(t.ValidationInitiationMax),
+      Hint: "Registration rate limit reached",
+    }
+    jsonResp, _ := json.Marshal(rlResponse)
+    w.Write(jsonResp)
+    t.Db.Delete(&validation)
+    return
+  }
+  err = t.Db.First(&validation, "h_address = ? AND public_key = ? AND inbox = 
? AND duration = ?",
+                                h_address, req.PublicKey, req.Inbox, 
reqDuration).Error
+  validationExists := (nil == err)
+  // FIXME: Always set new challenge?
+  validation.Challenge = util.GenerateChallenge(t.ChallengeBytes)
+  if !validationExists {
+    validation.Inbox = req.Inbox
+    validation.PublicKey = req.PublicKey
+    validation.SolutionAttemptCount = 0
+    validation.LastSolutionTimeframeStart = time.Now()
+    validation.Duration = reqDuration.Microseconds()
   }
 
   fixedCost := t.Cfg.Section("taldir-" + 
vars["method"]).Key("challenge_fee").MustString("KUDOS:0")
@@ -477,31 +511,49 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r 
*http.Request){
 
   log.Printf("The calculated cost for this registration is: %s:%f for a delta 
duration of %f", currency, cost, float64(validation.Duration) / 
float64(MONTH_DURATION.Microseconds()))
   if cost > 0 {
-    // FIXME what if provided order ID and validation order ID differ???
-    if len(order.Id) == 0 {
+    if validationExists {
+      if order.Id != validation.OrderId {
+        log.Fatalf("Order ID is not validation ID what to do?")
+        w.WriteHeader(http.StatusInternalServerError)
+        return
+      }
+    }
+    if len(validation.OrderId) == 0 {
       // Add new order for new validations
       orderId, newOrderErr := t.Merchant.AddNewOrder(cost, currency)
       if newOrderErr != nil {
         w.WriteHeader(http.StatusInternalServerError)
         return
       }
-      order.Id = orderId
+      validation.OrderId = orderId
     }
+
+    // FIXME what if provided order ID and validation order ID differ???
     // Check if order paid. FIXME: How to check if this the a correct order??
-    payto, paytoErr := t.Merchant.IsOrderPaid(order.Id)
+    payto, paytoErr := t.Merchant.IsOrderPaid(validation.OrderId)
     if paytoErr != nil {
       w.WriteHeader(http.StatusInternalServerError)
       log.Println(paytoErr)
       return
     }
     if len(payto) != 0 {
+      t.Db.Save(&validation)
       w.WriteHeader(http.StatusPaymentRequired)
       w.Header().Set("Location", payto) // FIXME no idea what to do with this.
       return
     }
     // In this case, this order was paid
   }
-
+  if validationExists {
+    err = t.Db.Save(&validation).Error
+  } else {
+    err = t.Db.Create(&validation).Error
+  }
+  if err != nil {
+    log.Println(err)
+    w.WriteHeader(500)
+    return
+  }
 
   log.Println("Address registration request created:", validation)
   if !t.Cfg.Section("taldir-" + vars["method"]).HasKey("command") {
@@ -615,6 +667,7 @@ func (t *Taldir) DeleteEntry(addr string) error {
 func (t *Taldir) ClearDatabase() {
   t.Db.Where("1 = 1").Delete(&Entry{})
   t.Db.Where("1 = 1").Delete(&Validation{})
+  t.Db.Where("1 = 1").Delete(&ValidationMetadata{})
 }
 
 func (t *Taldir) termsResponse(w http.ResponseWriter, r *http.Request) {
@@ -776,7 +829,14 @@ func (t *Taldir) Initialize(cfgfile string) {
   if err := t.Db.AutoMigrate(&Validation{}); err != nil {
     panic(err)
   }
+  if err := t.Db.AutoMigrate(&ValidationMetadata{}); err != nil {
+    panic(err)
+  }
+
 
+  // Clean up validations
+  tx := t.Db.Where("created_at < ?", time.Now().AddDate(0, 0, 
-1)).Delete(&Validation{})
+  log.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected)
   validationLandingTplFile := 
t.Cfg.Section("taldir").Key("validation_landing").MustString("templates/validation_landing.html")
   t.ValidationTpl, err = template.ParseFiles(validationLandingTplFile)
   if err != nil {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]