[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taldir] 01/02: Refactor for better testing
From: |
gnunet |
Subject: |
[taler-taldir] 01/02: Refactor for better testing |
Date: |
Wed, 06 Jul 2022 23:35:46 +0200 |
This is an automated email from the git hooks/post-receive script.
martin-schanzenbach pushed a commit to branch master
in repository taldir.
commit 5a70d6d172f2f9330276830f415e62ae32ae032c
Author: Martin Schanzenbach <schanzen@gnunet.org>
AuthorDate: Wed Jul 6 22:23:48 2022 +0200
Refactor for better testing
---
cmd/taldir-server/main.go | 487 +---------------------------------------
scripts/taldir-validate-twitter | 13 +-
taldir.conf | 2 +-
3 files changed, 18 insertions(+), 484 deletions(-)
diff --git a/cmd/taldir-server/main.go b/cmd/taldir-server/main.go
index b2ef3f7..38261f1 100644
--- a/cmd/taldir-server/main.go
+++ b/cmd/taldir-server/main.go
@@ -29,445 +29,11 @@ package main
import (
"os"
- "os/exec"
- "bufio"
- "time"
"fmt"
- "log"
+ "bufio"
"flag"
- "net/http"
- "html/template"
- "encoding/json"
- "github.com/gorilla/mux"
- "gorm.io/gorm"
- "encoding/base64"
- "taler.net/taldir/util"
- "taler.net/taldir/gana"
- "crypto/sha512"
- "gorm.io/driver/postgres"
- "gopkg.in/ini.v1"
- "strings"
- "github.com/skip2/go-qrcode"
)
-type VersionResponse struct {
- // libtool-style representation of the Merchant protocol version, see
- //
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
- // The format is "current:revision:age".
- Version string `json:"version"`
-
- // Name of the protocol.
- Name string `json:"name"` // "taler-directory"
-
- // Supported registration methods
- Methods []Method `json:"methods"`
-
- // fee for one month of registration
- MonthlyFee string `json:"monthly_fee"`
-
-}
-
-type Method struct {
-
- // Name of the method, e.g. "email" or "sms".
- Name string `json:"name"`
-
- // per challenge fee
- ChallengeFee string `json:"challenge_fee"`
-
-}
-
-type RateLimitedResponse struct {
-
- // Taler error code, TALER_EC_TALDIR_REGISTER_RATE_LIMITED.
- Code int `json:"code"`
-
- // At what frequency are new registrations allowed. FIXME: In what?
Currently: In microseconds
- RequestFrequency int64 `json:"request_frequency"`
-
- // The human readable error message.
- Hint string `json:"hint"`
-}
-
-type RegisterMessage struct {
-
- // Address, in method-specific format
- Address string `json:"address"`
-
- // Public key of the user to register
- 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 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
- // instead?
- // Order_id string `json:"order_id"`
-}
-
-// A mappind entry from the identity key hash to a wallet key
-// 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 {
-
- // ORM
- gorm.Model `json:"-"`
-
- // The salted hash (SHA512) of the hashed address (h_address)
- HsAddress string `json:"-"`
-
- // (HTTPS) endpoint URL for the inbox service for this address
- Inbox string `json:"inbox_url"`
-
- // Public key of the user to register in base32
- PublicKey string `json:"public_key"`
-
- // Time of (re)registration. In Unix epoch microseconds)
- RegisteredAt int64 `json:"-"`
-
- // How long the registration lasts in microseconds
- Duration int64 `json:"-"`
-}
-
-// A validation is created when a registration for an entry is initiated.
-// The validation stores the identity key (sha256(identity)) the secret
-// 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 {
-
- // ORM
- gorm.Model `json:"-"`
-
- // The hash (SHA512) of the address
- HAddress string `json:"h_address"`
-
- // For how long should the registration last
- Duration int64 `json:"duration"`
-
- // (HTTPS) endpoint URL for the inbox service for this address
- Inbox string `json:"inbox_url"`
-
- // The activation code sent to the client
- Code string `json:"activation_code"`
-
- // Public key of the user to register
- PublicKey string `json:"public_key"`
-}
-
-type ErrorDetail struct {
-
- // Numeric error code unique to the condition.
- // The other arguments are specific to the error value reported here.
- Code int `json:"code"`
-
- // Human-readable description of the error, i.e. "missing parameter",
"commitment violation", ...
- // Should give a human-readable hint about the error's nature. Optional, may
change without notice!
- Hint string `json:"hint,omitempty"`
-
- // Optional detail about the specific input value that failed. May change
without notice!
- Detail string `json:"detail,omitempty"`
-
- // Name of the parameter that was bogus (if applicable).
- Parameter string `json:"parameter,omitempty"`
-
- // Path to the argument that was bogus (if applicable).
- Path string `json:"path,omitempty"`
-
- // Offset of the argument that was bogus (if applicable).
- Offset string `json:"offset,omitempty"`
-
- // Index of the argument that was bogus (if applicable).
- Index string `json:"index,omitempty"`
-
- // Name of the object that was bogus (if applicable).
- Object string `json:"object,omitempty"`
-
- // Name of the currency than was problematic (if applicable).
- Currency string `json:"currency,omitempty"`
-
- // Expected type (if applicable).
- TypeExpected string `json:"type_expected,omitempty"`
-
- // Type that was provided instead (if applicable).
- TypeActual string `json:"type_actual,omitempty"`
-}
-
-type ValidationConfirmation struct {
- Solution string `json:"solution"`
-}
-
-// The main DB handle
-var db *gorm.DB
-
-// Our configuration from the config.json
-var cfg *ini.File
-
-// Map of supported validators as defined in the configuration
-var validators map[string]bool
-
-// landing page
-var validationTpl *template.Template
-
-// Primary lookup function.
-// Allows the caller to query a wallet key using the hash(!) of the
-// identity, e.g. SHA512(<email address>)
-func getSingleEntry(w http.ResponseWriter, r *http.Request){
- vars := mux.Vars(r)
- var entry Entry
- 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")
- 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 saltHAddress(h_address string) string {
- salt := os.Getenv("TALDIR_SALT")
- if "" == salt {
- salt = cfg.Section("taldir").Key("salt").MustString("ChangeMe")
- }
- h := sha512.New()
- h.Write([]byte(h_address))
- h.Write([]byte(salt))
- return util.EncodeBinaryToString(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 validationRequest(w http.ResponseWriter, r *http.Request){
- vars := mux.Vars(r)
- var entry Entry
- var validation Validation
- 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 {
- 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, "h_address = ?", vars["h_address"]).Error
- if err != nil {
- w.WriteHeader(http.StatusNotFound)
- return
- }
- 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 = db.Delete(&validation).Error
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- 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)
- } else {
- err = db.Create(&entry).Error
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- }
- w.WriteHeader(http.StatusNoContent)
-}
-
-
-func registerRequest(w http.ResponseWriter, r *http.Request){
- vars := mux.Vars(r)
- var req RegisterMessage
- var errDetail ErrorDetail
- var validation Validation
- var entry Entry
- if r.Body == nil {
- http.Error(w, "No request body", 400)
- return
- }
- err := json.NewDecoder(r.Body).Decode(&req)
- if err != nil {
- errDetail.Code = gana.GENERIC_JSON_INVALID
- errDetail.Hint = "Unable to parse JSON"
- resp, _ := json.Marshal(errDetail)
- w.WriteHeader(400)
- w.Write(resp)
- return
- }
- if !validators[vars["method"]] {
- errDetail.Code = gana.TALDIR_METHOD_NOT_SUPPORTED
- errDetail.Hint = "Unsupported method"
- errDetail.Detail = "Given method: " + vars["method"]
- resp, _ := json.Marshal(errDetail)
- w.WriteHeader(404)
- w.Write(resp)
- return
- }
- 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.
- 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: gana.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
- validation.Code = util.GenerateCode()
- validation.Inbox = req.Inbox
- validation.Duration = req.Duration
- validation.PublicKey = req.PublicKey
- if err == nil {
- // FIXME: Validation already pending for this address
- // How should we proceed here? Expire old validations?
- log.Println("Validation for this address already exists")
- err = db.Save(&validation).Error
- } else {
- 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)
- if !cfg.Section("taldir-" + vars["method"]).HasKey("command") {
- log.Fatal(err)
- db.Delete(&validation)
- w.WriteHeader(500)
- return
- }
- command := cfg.Section("taldir-" + vars["method"]).Key("command").String()
- path, err := exec.LookPath(command)
- if err != nil {
- log.Println(err)
- db.Delete(&validation)
- w.WriteHeader(500)
- return
- }
- out, err := exec.Command(path, req.Address, validation.Code).Output()
- if err != nil {
- log.Println(err)
- db.Delete(&validation)
- w.WriteHeader(500)
- return
- }
- w.WriteHeader(202)
- fmt.Printf("Output from method script %s is %s\n", path, out)
-}
-
-func notImplemented(w http.ResponseWriter, r *http.Request) {
- return
-}
-
-func configResponse(w http.ResponseWriter, r *http.Request) {
- meths := []Method{}
- i := 0
- for key, _ := range validators {
- var meth Method
- meth.Name = key
- 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",
- MonthlyFee: cfg.Section("taldir").Key("monthly_fee").MustString("1 Kudos"),
- Methods: meths,
- }
- w.Header().Set("Content-Type", "application/json")
- response, _ := json.Marshal(cfg)
- w.Write(response)
-}
-
-func validationPage(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- var walletLink string
- walletLink = "taler://taldir/" + vars["h_address"] + "/" +
vars["validation_code"] + "-wallet"
- var png []byte
- png, err := qrcode.Encode(walletLink, qrcode.Medium, 256)
- if err != nil {
- w.WriteHeader(500)
- return
- }
- encodedPng := base64.StdEncoding.EncodeToString(png)
-
- fullData := map[string]interface{}{
- "QRCode": template.URL("data:image/png;base64," + encodedPng),
- "WalletLink": template.URL(walletLink),
- }
- validationTpl.Execute(w, fullData)
- return
-}
-
-func handleRequests() {
- myRouter := mux.NewRouter().StrictSlash(true)
-
- /* ToS API */
- myRouter.HandleFunc("/terms", notImplemented).Methods("GET")
- myRouter.HandleFunc("/privacy", notImplemented).Methods("GET")
-
- /* Config API */
- myRouter.HandleFunc("/config", configResponse).Methods("GET")
-
-
- /* Registration API */
- 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))
-}
-
func main() {
var dropFlag = flag.Bool("D", false, "Drop all data in table (DANGEROUS!)")
var cfgFlag = flag.String("c", "", "Configuration file to use")
@@ -476,42 +42,8 @@ func main() {
if len(*cfgFlag) != 0 {
cfgfile = *cfgFlag
}
- _cfg, err := ini.Load(cfgfile)
- if err != nil {
- fmt.Printf("Failed to read config: %v", err)
- os.Exit(1)
- }
- cfg = _cfg
- if cfg.Section("taldir").Key("production").MustBool(false) {
- fmt.Println("Production mode enabled")
- }
-
- validators = make(map[string]bool)
- for _, a := range
strings.Split(cfg.Section("taldir").Key("validators").String(), " ") {
- validators[a] = true
- }
- validators = make(map[string]bool)
- for _, a := range
strings.Split(cfg.Section("taldir").Key("validators").String(), " ") {
- validators[a] = true
- }
-
- psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s
sslmode=disable",
- cfg.Section("taldir-pq").Key("host").MustString("localhost"),
- cfg.Section("taldir-pq").Key("port").MustInt64(5432),
- cfg.Section("taldir-pq").Key("user").MustString("taldir"),
- cfg.Section("taldir-pq").Key("password").MustString("secret"),
- cfg.Section("taldir-pq").Key("db_name").MustString("taldir"))
- _db, err := gorm.Open(postgres.Open(psqlconn), &gorm.Config{})
- if err != nil {
- panic(err)
- }
- db = _db
- if err := db.AutoMigrate(&Entry{}); err != nil {
- panic(err)
- }
- if err := db.AutoMigrate(&Validation{}); err != nil {
- panic(err)
- }
+ t := Taldir{}
+ clearDb := false
if *dropFlag {
fmt.Println("Really delete all data in database? [y/N]:")
reader := bufio.NewReader(os.Stdin)
@@ -520,19 +52,12 @@ func main() {
if err == nil {
fmt.Println(char)
if char == 'y' {
- fmt.Println("Deleting entries...")
- db.Where("1 = 1").Delete(&Entry{})
- fmt.Println("Deleting validations...")
- db.Where("1 = 1").Delete(&Validation{})
+ clearDb = true
}
os.Exit(0)
}
os.Exit(1)
}
-
- validationTpl, err = template.ParseFiles("templates/validation_landing.html")
- if err != nil {
- fmt.Println(err)
- }
- handleRequests()
+ t.Initialize(cfgfile, clearDb)
+ t.Run()
}
diff --git a/scripts/taldir-validate-twitter b/scripts/taldir-validate-twitter
index cd1b6e4..e684ea2 100755
--- a/scripts/taldir-validate-twitter
+++ b/scripts/taldir-validate-twitter
@@ -1,3 +1,12 @@
#!/bin/bash
-echo $1 $2
-echo $2 > validation_code
+#
+# IMPORTANT: Before this can be used, as the taldir service user
+# you need to authorize this CLI app for the taldir twitter account.
+# e.g.:
+# $ t authorize
+#
+TWITTER_USER=$1
+CODE=$2
+LINK=$(taldir-cli -l -a $1 -c $2)
+MESSAGE="Follow this link to complete your Taldir registration: $LINK"
+t dm $TWITTER_USER $MESSAGE
diff --git a/taldir.conf b/taldir.conf
index 9560e77..aac8fe1 100644
--- a/taldir.conf
+++ b/taldir.conf
@@ -1,6 +1,6 @@
[taldir]
production = false
-validators = "email phone test"
+validators = "twitter test"
host = "https://taldir.net"
bind_to = "localhost:11000"
salt = "ChangeMe"
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.