gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taldir] branch master updated (c1fc528 -> 493615f)


From: gnunet
Subject: [taler-taldir] branch master updated (c1fc528 -> 493615f)
Date: Tue, 05 Jul 2022 12:40:43 +0200

This is an automated email from the git hooks/post-receive script.

martin-schanzenbach pushed a change to branch master
in repository taldir.

    from c1fc528  towards new API
     new 0cb3795  add validation scripts
     new 93c8da8  add additional endpoints
     new 493615f  Enable test runs for registration

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 taldir.conf      |  11 ++-
 taldir.go        | 218 ++++++++++++++++++++++++++++++++++++-------------------
 test.sh          |  11 +++
 validate_test.sh |   3 +
 4 files changed, 165 insertions(+), 78 deletions(-)
 create mode 100755 test.sh
 create mode 100755 validate_test.sh

diff --git a/taldir.conf b/taldir.conf
index 2f8fdf3..e49c25a 100644
--- a/taldir.conf
+++ b/taldir.conf
@@ -1,17 +1,24 @@
 [taldir]
 production = false
-validators = "email phone"
+validators = "email phone test"
 host = "https://taldir.net";
 bind_to = "localhost:11000"
 salt = "ChangeMe"
 monthly_fee = 1 Bazillion Kudos
+request_frequency = 3
 
 [taldir-email]
 sender = "taldir@taler.net"
-challenge_fee = 0.5 Fantastillion Kudos 
+challenge_fee = 0.5 Fantastillion Kudos
+command = validate_email.sh
 
 [taldir-phone]
 challenge_fee = 5 Kudos
+command = validate_phone.sh
+
+[taldir-test]
+challenge_fee = 23 Kudos
+command = ./validate_test.sh
 
 [taldir-pq]
 host = "localhost"
diff --git a/taldir.go b/taldir.go
index 3ba1bc0..302a1fb 100644
--- a/taldir.go
+++ b/taldir.go
@@ -2,6 +2,8 @@ package main
 
 import (
   "os"
+  "os/exec"
+  "time"
   "fmt"
   "log"
   "net/http"
@@ -10,8 +12,7 @@ import (
   "gorm.io/gorm"
   "encoding/base32"
   "math/rand"
-  "net/smtp"
-  "crypto/sha256"
+  "crypto/sha512"
   "gorm.io/driver/postgres"
   "gopkg.in/ini.v1"
   "strings"
@@ -30,7 +31,7 @@ type VersionResponse struct {
   Methods []Method `json:"methods"`
 
   // fee for one month of registration
-  Monthly_fee string `json:"monthly_fee"`
+  MonthlyFee string `json:"monthly_fee"`
 
 }
 
@@ -39,7 +40,7 @@ type Method struct {
   Name string `json:"name"`
 
   // per challenge fee
-  Challenge_fee string `json:"challenge_fee"`
+  ChallengeFee string `json:"challenge_fee"`
 
 }
 
@@ -49,7 +50,7 @@ type RateLimitedResponse struct {
   Code int `json:"code"`
 
   // At what frequency are new registrations allowed. FIXME: In what?
-  Request_frequency uint64 `json:"request_frequency"`
+  RequestFrequency int64 `json:"request_frequency"`
 
   // The human readable error message.
   Hint string `json:"hint"`
@@ -61,13 +62,13 @@ type RegisterMessage struct {
   Address string `json:"address"`
 
   // Public key of the user to register
-  Public_key string `json:"public_key"`
+  PublicKey string `json:"public_key"`
 
   // (HTTPS) endpoint URL for the inbox service for this address
   Inbox string `json:"inbox_url"`
 
   // For how long should the registration last
-  Duration uint64 `json:"duration"`
+  Duration int64 `json:"duration"`
 
   // Order ID, if the client recently paid for this registration
   // FIXME: As an optional field, maybe we want to parse this separately
@@ -79,9 +80,12 @@ type RegisterMessage struct {
 // The identity key hash is sha256(sha256(identity)|salt) where identity is
 // one of the identity key types supported (e.g. email)
 type Entry struct {
-  gorm.Model
-  IdentityKeyHash string `json:"identity_key_hash"`
-  TalerWalletKey string `json:"taler_wallet_key"`
+  gorm.Model  `json:"-"`
+  HsAddress string `json:"-"`
+  Inbox string `json:"inbox_url"`
+  PublicKey string `json:"public_key"`
+  RegisteredAt int64 `json:"-"`
+  Duration int64 `json:"-"`
 }
 
 // A validation is created when a registration for an entry is initiated.
@@ -89,10 +93,12 @@ type Entry struct {
 // validation reference. The validation reference is sent to the identity
 // depending on the out-of-band chennel defined through the identity key type.
 type Validation struct {
-  gorm.Model
+  gorm.Model `json:"-"`
   HAddress string `json:"h_address"`
   Method string `json:"method"`
-  ValidationReference string `json:"reference"`
+  Duration int64 `json:"duration"`
+  Inbox string `json:"inbox_url"`
+  Solution string `json:"solution"`
   PublicKey string `json:"public_key"`
 }
 
@@ -128,10 +134,14 @@ type ErrorDetail struct {
   Currency string `json:"currency,omitempty"`
 
   // Expected type (if applicable).
-  Type_expected string `json:"type_expected,omitempty"`
+  TypeExpected string `json:"type_expected,omitempty"`
 
   // Type that was provided instead (if applicable).
-  Type_actual string `json:"type_actual,omitempty"`
+  TypeActual string `json:"type_actual,omitempty"`
+}
+
+type ValidationConfirmation struct {
+  Solution string `json:"solution"`
 }
 
 // The main DB handle
@@ -143,91 +153,94 @@ var cfg *ini.File
 // Map of supported validators as defined in the configuration
 var validators map[string]bool
 
-// Send an email for email identities 
-func sendEmail(recipient string, ref Validation) {
-
-  from := 
cfg.Section("taldir-email").Key("sender").MustString("taldir@example.com")
-  to := []string{
-    recipient,
-  }
-
-  smtpHost := "localhost"
-  smtpPort := "587"
-
-  message := fmt.Sprintf("Please click here to validate your Taldir identity: 
%s%s",
-  cfg.Section("taldir").Key("host").MustString("http://localhost";),
-  ref.ValidationReference)
-
-  err := smtp.SendMail(smtpHost+":"+smtpPort, nil, from, to, []byte(message))
-  if err != nil {
-    fmt.Println(err)
-    return
-  }
-  fmt.Println("Email Sent Successfully!")
-}
-
 // Primary lookup function.
 // Allows the caller to query a wallet key using the hash(!) of the
 // identity, e.g. sha256(<email address>)
-/*func returnSingleEntry(w http.ResponseWriter, r *http.Request){
+func getSingleEntry(w http.ResponseWriter, r *http.Request){
   vars := mux.Vars(r)
   var entry Entry
-  identityKeyHash := hashIdentityKey(vars["identity_key"])
-  var err = db.First(&entry, "identity_key_hash = ?", identityKeyHash).Error
+  //identityKeyHash := hashIdentityKey(vars["identity_key"])
+  hs_address := saltHAddress(vars["h_address"])
+  var err = db.First(&entry, "hs_address = ?", hs_address).Error
   if err == nil {
     w.Header().Set("Content-Type", "application/json")
-    json.NewEncoder(w).Encode(entry)
+    resp, _ := json.Marshal(entry)
+    w.Write(resp)
     return
   }
   w.WriteHeader(http.StatusNotFound)
-}*/
+}
 
 // Hashes an identity key (e.g. sha256(<email address>)) with a salt for
 // Lookup and storage.
-func hashIdentityKey(idkey string) string {
+func saltHAddress(h_address string) string {
   salt := os.Getenv("TALDIR_SALT")
   if "" == salt {
     salt = cfg.Section("taldir").Key("salt").MustString("ChangeMe")
   }
   fmt.Println("Using salt " + salt)
-  h := sha256.New()
-  h.Write([]byte(idkey))
+  h := sha512.New()
+  h.Write([]byte(h_address))
   h.Write([]byte(salt))
   return base32.StdEncoding.EncodeToString(h.Sum(nil))
 }
 
 // Called by the registrant to validate the registration request. The 
reference ID was
 // provided "out of band" using a validation method such as email or SMS
-/*func validateSingleEntry(w http.ResponseWriter, r *http.Request){
+func validationRequest(w http.ResponseWriter, r *http.Request){
   vars := mux.Vars(r)
   var entry Entry
   var validation Validation
-  var err = db.First(&validation, "validation_reference = ?", 
vars["reference"]).Error
+  var confirm ValidationConfirmation
+  var errDetail ErrorDetail
+  if r.Body == nil {
+    http.Error(w, "No request body", 400)
+    return
+  }
+  err := json.NewDecoder(r.Body).Decode(&confirm)
   if err != nil {
-    w.WriteHeader(http.StatusNotFound)
+    errDetail.Code = 1006 //TALER_EC_JSON_INVALID
+    errDetail.Hint = "Unable to parse JSON"
+    resp, _ := json.Marshal(errDetail)
+    w.WriteHeader(400)
+    w.Write(resp)
+    return
   }
-  err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error
+  err = db.First(&validation, "h_address = ?", vars["h_address"]).Error
   if err != nil {
     w.WriteHeader(http.StatusNotFound)
+    return
+  }
+  if confirm.Solution != validation.Solution {
+    // FIXME how TF do we rate limit here??
+    w.WriteHeader(http.StatusForbidden)
+    return
   }
+  // FIXME: Expire validations somewhere?
   err = db.Delete(&validation).Error
   if err != nil {
     w.WriteHeader(http.StatusInternalServerError)
-  }
-  entry.IdentityKeyHash = hashIdentityKey(validation.IdentityKey)
-  entry.TalerWalletKey = validation.TalerWalletKey
-  err = db.First(&entry, "identity_key_hash = ?", entry.IdentityKeyHash).Error
-  if err == nil {
-    w.WriteHeader(http.StatusConflict)
     return
   }
-  err = db.Create(&entry).Error
-  if err != nil {
-    w.WriteHeader(http.StatusInternalServerError)
+  // FIXME are we still doing this??
+  entry.HsAddress = saltHAddress(validation.HAddress)
+  entry.Inbox = validation.Inbox
+  entry.Duration = validation.Duration
+  entry.RegisteredAt = time.Now().UnixMicro()
+  entry.PublicKey = validation.PublicKey
+  err = db.First(&entry, "hs_address = ?", entry.HsAddress).Error
+  if err == nil {
+    db.Save(&entry)
     return
+  } else {
+    err = db.Create(&entry).Error
+    if err != nil {
+      w.WriteHeader(http.StatusInternalServerError)
+      return
+    }
   }
-  w.WriteHeader(http.StatusCreated)
-}*/
+  w.WriteHeader(http.StatusNoContent)
+}
 
 
 // Generates random reference token used in the validation flow.
@@ -245,6 +258,7 @@ func registerRequest(w http.ResponseWriter, r 
*http.Request){
   var req RegisterMessage
   var errDetail ErrorDetail
   var validation Validation
+  var entry Entry
   if r.Body == nil {
     http.Error(w, "No request body", 400)
     return
@@ -267,30 +281,74 @@ func registerRequest(w http.ResponseWriter, r 
*http.Request){
     w.Write(resp)
     return
   }
-  // TODO make sure sha256(vars["identity"]) == validation.IdentityKey or 
simply set it?
-  h := sha256.New()
+  h := sha512.New()
   h.Write([]byte(req.Address))
   validation.HAddress = base32.StdEncoding.EncodeToString(h.Sum(nil))
-  validation.ValidationReference = generateToken()
+  // We first try if there is already an entry for this address which
+  // is still valid and the duration is not extended.
+  hs_address := saltHAddress(validation.HAddress)
+  err = db.First(&entry, "hs_address = ?", hs_address).Error
+  if err != nil {
+    lastRegValidity := entry.RegisteredAt + entry.Duration
+    requestedValidity := time.Now().UnixMicro() + req.Duration
+    reqFrequency := 
cfg.Section("taldir").Key("request_frequency").MustInt64(1000)
+    earliestReRegistration := entry.RegisteredAt + reqFrequency
+    // Rate limit re-registrations.
+    if time.Now().UnixMicro() < earliestReRegistration {
+      w.WriteHeader(429)
+      rlResponse := RateLimitedResponse{
+        Code: 23, //FIXME TALER_EC_TALDIR_REGISTER_RATE_LIMITED
+        RequestFrequency: reqFrequency,
+        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??
+      return
+    }
+  }
   err = db.First(&validation, "h_address = ?", validation.HAddress).Error
   if err == nil {
-    // FIXME: We need to handle this properly:
-    // Registration already exists for this address for the specified duration.
-    // Returns for how long this registration is paid for.
-    w.WriteHeader(200)
+    // Validation already pending for this address
+    db.Delete(&validation) // FIXME for debugging only
+    w.WriteHeader(202)
+    return
+  } else {
+    validation.Solution = generateToken()
+    validation.Inbox = req.Inbox
+    validation.Duration = req.Duration
+    validation.PublicKey = req.PublicKey
+    err = db.Create(&validation).Error
+    if err != nil {
+      // FIXME: API needs 400 error codes in such cases
+      w.WriteHeader(http.StatusInternalServerError)
+      return
+    }
+    fmt.Println("Address registration request created:", validation)
+  }
+  // FIXME: Here we should call the validator shell script with the
+  // parsed parameters to initiate the validation.
+  if !cfg.Section("taldir-" + vars["method"]).HasKey("command") {
+    log.Fatal(err)
+    db.Delete(&validation)
+    w.WriteHeader(500)
     return
   }
-  err = db.Create(&validation).Error
+  command := cfg.Section("taldir-" + vars["method"]).Key("command").String()
+  out, err := exec.Command(command, req.Address, validation.Solution).Output()
   if err != nil {
-    // FIXME: API needs 400 error codes in such cases
-    w.WriteHeader(http.StatusInternalServerError)
+    log.Fatal(err)
+    db.Delete(&validation)
+    w.WriteHeader(500)
     return
   }
-  fmt.Println("Address registration request created:", validation)
   w.WriteHeader(202)
-  // FIXME: Here we should call the validator shell script with the
-  // parsed parameters to initiate the validation.
-  sendEmail(vars["identity"], validation)
+  fmt.Printf("Output from method script is %s\n", out)
 }
 
 func notImplemented(w http.ResponseWriter, r *http.Request) {
@@ -303,14 +361,14 @@ func configResponse(w http.ResponseWriter, r 
*http.Request) {
   for key, _ := range validators {
     var meth Method
     meth.Name = key
-    meth.Challenge_fee = cfg.Section("taldir-" + 
key).Key("challenge_fee").MustString("1 Kudos")
+    meth.ChallengeFee = cfg.Section("taldir-" + 
key).Key("challenge_fee").MustString("1 Kudos")
     i++
     meths = append(meths, meth)
   }
   cfg := VersionResponse{
     Version: "0:0:0",
     Name: "taler-directory",
-    Monthly_fee: cfg.Section("taldir").Key("monthly_fee").MustString("1 
Kudos"),
+    MonthlyFee: cfg.Section("taldir").Key("monthly_fee").MustString("1 Kudos"),
     Methods: meths,
   }
   w.Header().Set("Content-Type", "application/json")
@@ -318,6 +376,11 @@ func configResponse(w http.ResponseWriter, r 
*http.Request) {
   w.Write(response)
 }
 
+func validationPage(w http.ResponseWriter, r *http.Request) {
+  // FIXME provided HTML page here
+  return
+}
+
 func handleRequests() {
   myRouter := mux.NewRouter().StrictSlash(true)
 
@@ -332,7 +395,10 @@ func handleRequests() {
   /* Registration API */
   //myRouter.HandleFunc("/directory/{identity_key}", 
returnSingleEntry).Methods("GET")
   //myRouter.HandleFunc("/validation/{reference}", 
validateSingleEntry).Methods("GET")
+  myRouter.HandleFunc("/{h_address}", getSingleEntry).Methods("GET")
   myRouter.HandleFunc("/register/{method}", registerRequest).Methods("POST")
+  myRouter.HandleFunc("/register/{h_address}/{validation_code}", 
validationPage).Methods("GET")
+  myRouter.HandleFunc("/{h_address}", validationRequest).Methods("POST")
 
   
log.Fatal(http.ListenAndServe(cfg.Section("taldir").Key("bind_to").MustString("localhost:11000"),
 myRouter))
 }
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..de0e45d
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+# New request
+curl -v localhost:11000/register/test --data '{"address": "abc@test", 
"public_key": "pkey", "inbox_url": "myinbox@xyz", "duration": 23}'
+# Read validation code from tempfile
+CODE=`cat validation_code`
+H_ADDRESS=`echo -n abc@test | openssl dgst -binary -sha512 | base32 -w0`
+echo "Code: $CODE; Address: $H_ADDRESS"
+# Validate
+curl -v localhost:11000/$H_ADDRESS --data "{\"solution\": \"${CODE}\"}"
+# Get mapping
+curl -v localhost:11000/$H_ADDRESS
diff --git a/validate_test.sh b/validate_test.sh
new file mode 100755
index 0000000..cd1b6e4
--- /dev/null
+++ b/validate_test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+echo $1 $2
+echo $2 > validation_code

-- 
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]