taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit 93c8da8d359b3ce06fa51d259d3339ccde6e60ee
parent 0cb37955d24f95c106bbb93552fc8db8f3ea2a46
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Tue,  5 Jul 2022 11:50:01 +0200

add additional endpoints

Diffstat:
Mtaldir.go | 102++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 65 insertions(+), 37 deletions(-)

diff --git a/taldir.go b/taldir.go @@ -3,6 +3,7 @@ package main import ( "os" "os/exec" + "time" "fmt" "log" "net/http" @@ -12,7 +13,7 @@ import ( "encoding/base32" "math/rand" "net/smtp" - "crypto/sha256" + "crypto/sha512" "gorm.io/driver/postgres" "gopkg.in/ini.v1" "strings" @@ -50,7 +51,7 @@ type RateLimitedResponse struct { Code int `json:"code"` // At what frequency are new registrations allowed. FIXME: In what? - Request_frequency uint `json:"request_frequency"` + RequestFrequency int64 `json:"request_frequency"` // The human readable error message. Hint string `json:"hint"` @@ -68,7 +69,7 @@ type RegisterMessage struct { 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 @@ -81,8 +82,10 @@ type RegisterMessage struct { // 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"` + HAddress string `json:"h_address"` + PublicKey string `json:"public_key"` + RegisteredAt int64 `json:"valid_until"` + Duration int64 `json:"duration"` } // A validation is created when a registration for an entry is initiated. @@ -95,7 +98,6 @@ type Validation struct { Method string `json:"method"` ValidationReference string `json:"reference"` PublicKey string `json:"public_key"` - RetryCount uint } type ErrorDetail struct { @@ -171,18 +173,18 @@ func sendEmail(recipient string, ref Validation) { // 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"]) + var err = db.First(&entry, "h_address = ?", vars["h_address"]).Error if err == nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(entry) return } w.WriteHeader(http.StatusNotFound) -}*/ +} // Hashes an identity key (e.g. sha256(<email address>)) with a salt for // Lookup and storage. @@ -192,7 +194,7 @@ func hashIdentityKey(idkey string) string { salt = cfg.Section("taldir").Key("salt").MustString("ChangeMe") } fmt.Println("Using salt " + salt) - h := sha256.New() + h := sha512.New() h.Write([]byte(idkey)) h.Write([]byte(salt)) return base32.StdEncoding.EncodeToString(h.Sum(nil)) @@ -200,26 +202,33 @@ func hashIdentityKey(idkey string) string { // 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 + err := db.First(&validation, "h_address = ?", vars["h_address"]).Error if err != nil { w.WriteHeader(http.StatusNotFound) + return } - err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error - if err != nil { - w.WriteHeader(http.StatusNotFound) + if vars["validation_code"] != validation.ValidationReference { + // 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) + return } - entry.IdentityKeyHash = hashIdentityKey(validation.IdentityKey) - entry.TalerWalletKey = validation.TalerWalletKey - err = db.First(&entry, "identity_key_hash = ?", entry.IdentityKeyHash).Error + // FIXME are we still doing this?? + //entry.IdentityKeyHash = hashIdentityKey(validation.IdentityKey) + entry.HAddress = validation.HAddress + entry.PublicKey = validation.PublicKey + err = db.First(&entry, "h_address = ?", entry.HAddress).Error if err == nil { + // FIXME is this not in theory possible that we find such an entry?? w.WriteHeader(http.StatusConflict) return } @@ -228,8 +237,8 @@ func hashIdentityKey(idkey string) string { w.WriteHeader(http.StatusInternalServerError) return } - w.WriteHeader(http.StatusCreated) -}*/ + w.WriteHeader(http.StatusNoContent) +} // Generates random reference token used in the validation flow. @@ -247,6 +256,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 @@ -269,29 +279,42 @@ 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)) - err = db.First(&validation, "h_address = ?", validation.HAddress).Error - if err == nil { - reqFrequency := cfg.Section("taldir").Key("request_frequency").MustUint(1) - if validation.RetryCount >= reqFrequency - 1 { + // We first try if there is already an entry for this address which + // is still valid and the duration is not extended. + err = db.First(&entry, "h_address = ?", validation.HAddress).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 - Request_frequency: reqFrequency, - Hint: "Retry maximum reached. Deleting validation", + RequestFrequency: reqFrequency, + Hint: "Registration rate limit reached", } jsonResp, _ := json.Marshal(rlResponse) w.Write(jsonResp) - db.Delete(&validation) return } - validation.RetryCount++ - validation.ValidationReference = generateToken() - db.Save(&validation) - fmt.Printf("Retrying validation send\n") + // 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 { + // Validation already pending for this address + db.Delete(&validation) // FIXME for debugging only + w.WriteHeader(202) + return } else { validation.ValidationReference = generateToken() err = db.Create(&validation).Error @@ -302,21 +325,24 @@ func registerRequest(w http.ResponseWriter, r *http.Request){ } 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. if !cfg.Section("taldir-" + vars["method"]).HasKey("command") { log.Fatal(err) - // FIXME cleanup validation? + db.Delete(&validation) + w.WriteHeader(500) return } command := cfg.Section("taldir-" + vars["method"]).Key("command").String() out, err := exec.Command(command, req.Address, validation.ValidationReference).Output() if err != nil { log.Fatal(err) + db.Delete(&validation) + w.WriteHeader(500) + return } + w.WriteHeader(202) fmt.Printf("Output from method script is %s\n", out) - // sendEmail(vars["identity"], validation) } func notImplemented(w http.ResponseWriter, r *http.Request) { @@ -358,7 +384,9 @@ 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}", validationRequest).Methods("GET") log.Fatal(http.ListenAndServe(cfg.Section("taldir").Key("bind_to").MustString("localhost:11000"), myRouter)) }