gnunet-gns-registrar

GNU Name System registrar
Log | Files | Refs | README

commit dfa72ebc7df107045f5a5ac0e0aa7ba5f4f3c1fa
parent be6f360667fff3c98797df97dd307fd31368c117
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Thu,  7 Dec 2023 18:23:56 +0100

merge conflict

Diffstat:
Mgns-registrar.conf | 11+++++------
Mgo.mod | 11+++++++++--
Mpkg/rest/gnsregistrar.go | 405++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Aweb/templates/buy.html | 22++++++++++++++++++++++
Mweb/templates/landing.html | 20+++++++++++++-------
Mweb/templates/name.html | 46+++++++++++++++++++++++-----------------------
Dweb/templates/names.html | 20--------------------
7 files changed, 404 insertions(+), 131 deletions(-)

diff --git a/gns-registrar.conf b/gns-registrar.conf @@ -1,20 +1,19 @@ [gns-registrar] production = false -host = "https://taldir.gnunet.org" +base_url = "http://localhost:7776" +base_url_gnunet = "http://localhost:7776" +gnunet_auth = "" +base_url_merchant = https://backend.demo.taler.net +merchant_token = sandbox bind_to = "localhost:11000" delegations_public = no -monthly_fee = KUDOS:1 default_doc_filetype = text/html default_doc_lang = en default_tos_path = terms/ default_pp_path = privacy/ supported_doc_filetypes = text/html application/pdf application/epub application/xml text/plain -merchant_baseurl_private = http://merchant.taldir/instances/myInstance -merchant_token = superSecretToken registrar_landing = web/templates/landing.html registrar_name = web/templates/name.html registration_failed = templates/registration_failed.html -registration_duration = 1y root_zone_name = test suffix_hint = gnunet.gns.alt -registration_policy = fcfs diff --git a/go.mod b/go.mod @@ -3,8 +3,15 @@ module gnunet.org/gnunet-gns-registrar go 1.18 require ( - github.com/gorilla/mux v1.8.0 + github.com/gorilla/mux v1.8.1 + github.com/schanzen/taler-go v0.0.3 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + golang.org/x/text v0.14.0 gopkg.in/ini.v1 v1.67.0 ) -require github.com/schanzen/taler-go v0.0.3 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/pkg/rest/gnsregistrar.go b/pkg/rest/gnsregistrar.go @@ -20,18 +20,36 @@ package gnsregistrar import ( "bytes" + "encoding/base64" "encoding/json" + "errors" "fmt" "html/template" "io" "net/http" "os" + "strconv" + "strings" + "time" "github.com/gorilla/mux" + "github.com/skip2/go-qrcode" + "golang.org/x/text/currency" + "golang.org/x/text/language" + "golang.org/x/text/message" "gopkg.in/ini.v1" "github.com/schanzen/taler-go/pkg/merchant" + talerutil "github.com/schanzen/taler-go/pkg/util" ) + +type RegistrationMetadata struct { + Expiration uint64 `json:"expiration"` + Paid bool `json:"paid"` + OrderID string `json:"order_id"` + NeedsPaymentUntil time.Time `json:"needs_payment_until"` +} + type IdentityInfo struct { Pubkey string `json:"pubkey"` Name string `json:"name"` @@ -51,6 +69,7 @@ type RecordData struct { IsRelativeExpiration bool `json:"is_relative_expiration"` IsSupplemental bool `json:"is_supplemental"` IsShadow bool `json:"is_shadow"` + IsMaintenance bool `json:"is_maintenance"` } @@ -77,19 +96,24 @@ type Registrar struct { // name page NameTpl *template.Template - // all names page - AllNamesTpl *template.Template + // buy names page + BuyTpl *template.Template // Merchant object Merchant merchant.Merchant // Relative record expiration (NOT registration expiration!) - RelativeDelegationExpiration uint64 + RelativeDelegationExpiration time.Duration + + // Registration expiration (NOT record expiration!) + RelativeRegistrationExpiration time.Duration + + // Payment expiration (time you have to pay for registration) + PaymentExpiration time.Duration // Name of our root zone RootZoneName string - // Key of our root zone RootZoneKey string @@ -99,14 +123,12 @@ type Registrar struct { // Gnunet REST API basename GnunetUrl string - // Registration policy; fcfs or paid - RegistrationPolicy string - + // Registrar base URL + BaseUrl string + // Cost for a registration - RegistrationCost uint64 + RegistrationCost *talerutil.Amount - // Currency to use - RegistrationCostCurrency string } type VersionResponse struct { @@ -125,6 +147,22 @@ func (t *Registrar) configResponse(w http.ResponseWriter, r *http.Request) { w.Write(response) } +// FIXME: Implement https://docs.taler.net/design-documents/051-fractional-digits.html and move to taler-go +func (t *Registrar) localizedAmountString() (string) { + p := message.NewPrinter(language.English) + costStr := t.RegistrationCost.String() + costSplit := strings.Split(costStr, ":") + left := costSplit[0] + right := costSplit[1] + cur, err := currency.ParseISO(left) + if nil != err { + return fmt.Sprintf("%s %s", right, left) + } + amf, _ := strconv.ParseFloat(right, 8) + v := currency.Symbol(cur.Amount(amf)) + return p.Sprint(v) +} + func (t *Registrar) landingPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") @@ -139,68 +177,149 @@ func (t *Registrar) landingPage(w http.ResponseWriter, r *http.Request) { func (t *Registrar) registerName(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var namestoreRequest NamestoreRecord - var delegationRecord RecordData + var delegationRecord RecordData + var metadataRecord RecordData var gnunetError GnunetError + var registrationMetadata RegistrationMetadata w.Header().Set("Content-Type", "text/html; charset=utf-8") delegationRecord.IsPrivate = false delegationRecord.IsRelativeExpiration = true delegationRecord.IsSupplemental = false + delegationRecord.IsMaintenance = false delegationRecord.IsShadow = false - delegationRecord.RecordType = "PKEY" // FIXME - delegationRecord.RelativeExpiration = t.RelativeDelegationExpiration + delegationRecord.RecordType = guessDelegationRecordType(r.URL.Query().Get("zkey")) + delegationRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds()) delegationRecord.Value = r.URL.Query().Get("zkey") + metadataRecord.IsPrivate = true + metadataRecord.IsRelativeExpiration = true + metadataRecord.IsSupplemental = false + metadataRecord.IsMaintenance = true + metadataRecord.IsShadow = false + metadataRecord.RecordType = "TXT" // FIXME use new recory type + metadataRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds()) + registrationMetadata = RegistrationMetadata{ + Paid: false, + Expiration: uint64(time.Now().Add(t.RelativeRegistrationExpiration).UnixMicro()), + } + metadataRecordValue, err := json.Marshal(registrationMetadata) + if nil != err { + http.Redirect(w, r, "/name?label="+vars["label"] + "&error=Registration failed", http.StatusSeeOther) + return + } + metadataRecord.Value = string(metadataRecordValue) namestoreRequest.RecordName = vars["label"] - namestoreRequest.Records = []RecordData{delegationRecord} - reqString, _ := json.Marshal(namestoreRequest) - // FIXME handle errors here - fmt.Println(namestoreRequest) + namestoreRequest.Records = []RecordData{delegationRecord,metadataRecord} + reqString, _ := json.Marshal(namestoreRequest) + // FIXME handle errors here + fmt.Println(namestoreRequest) resp, err := http.Post(t.GnunetUrl+"/namestore/" + t.RootZoneName, "application/json", bytes.NewBuffer(reqString)) - resp.Body.Close() + resp.Body.Close() if http.StatusNoContent != resp.StatusCode { fmt.Printf("Got error: %d\n", resp.StatusCode) json.NewDecoder(resp.Body).Decode(&gnunetError) fmt.Println(gnunetError.Code) fmt.Println(err) - http.Redirect(w, r, "/name?label="+r.URL.Query().Get("label") + "&error=" + gnunetError.Description, http.StatusSeeOther) + http.Redirect(w, r, "/name?label="+vars["label"] + "&error=" + gnunetError.Description, http.StatusSeeOther) return } http.Redirect(w, r, "/name/"+ vars["label"] + "?registered=true", http.StatusSeeOther) return } - func (t *Registrar) searchPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") http.Redirect(w, r, "/name/"+r.URL.Query().Get("label"), http.StatusSeeOther) return } -func (t *Registrar) buyPage(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - if t.RegistrationCost == 0 { - http.Redirect(w, r, "/name/"+vars["label"]+"/register?zkey="+r.URL.Query().Get("zkey"), http.StatusSeeOther) +func (t *Registrar) expireRegistration(label string) (error) { + var gnunetError GnunetError + client := &http.Client{} + req, _ := http.NewRequest(http.MethodDelete,t.GnunetUrl+"/namestore/" + t.RootZoneName + "/" + label, nil) + resp, err := client.Do(req) + resp.Body.Close() + if err != nil { + return err } - http.Redirect(w, r, "/name/"+vars["label"]+"?error=Not implemented", http.StatusSeeOther) - return + if http.StatusNotFound == resp.StatusCode { + return nil + } + if http.StatusNoContent != resp.StatusCode { + fmt.Printf("Got error: %d\n", resp.StatusCode) + err = json.NewDecoder(resp.Body).Decode(&gnunetError) + return errors.New("GNUnet REST API error: " + gnunetError.Description) + } + return nil } -func (t *Registrar) namePage(w http.ResponseWriter, r *http.Request) { - var namestoreResponse NamestoreRecord +func (t *Registrar) createOrUpdateRegistration(nsRecord *NamestoreRecord) (error) { + var gnunetError GnunetError + reqString, _ := json.Marshal(nsRecord) + fmt.Println(nsRecord) + client := &http.Client{} + req, _ := http.NewRequest(http.MethodPut,t.GnunetUrl+"/namestore/" + t.RootZoneName, bytes.NewBuffer(reqString)) + resp, err := client.Do(req) + if nil != err { + return err + } + resp.Body.Close() + if http.StatusNoContent != resp.StatusCode { + fmt.Printf("Got error: %d\n", resp.StatusCode) + err = json.NewDecoder(resp.Body).Decode(&gnunetError) + return errors.New("GNUnet REST API error: " + gnunetError.Description) + } + return nil +} + +func (t *Registrar) setupRegistrationMetadataBeforePayment(label string, zkey string, orderId string, paymentUntil time.Time) (error) { + var namestoreRequest NamestoreRecord + var delegationRecord RecordData + var metadataRecord RecordData + var registrationMetadata RegistrationMetadata + delegationRecord.IsPrivate = true // Private until payment is through + delegationRecord.IsRelativeExpiration = true + delegationRecord.IsSupplemental = false + delegationRecord.IsMaintenance = false + delegationRecord.IsShadow = false + delegationRecord.RecordType = guessDelegationRecordType(zkey) + delegationRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds()) + delegationRecord.Value = zkey + metadataRecord.IsPrivate = true + metadataRecord.IsRelativeExpiration = true + metadataRecord.IsSupplemental = false + metadataRecord.IsMaintenance = true + metadataRecord.IsShadow = false + metadataRecord.RecordType = "TXT" // FIXME use new recory type + metadataRecord.RelativeExpiration = uint64(t.RelativeDelegationExpiration.Microseconds()) + registrationMetadata = RegistrationMetadata{ + Paid: false, + OrderID: orderId, + NeedsPaymentUntil: paymentUntil, + Expiration: uint64(time.Now().Add(t.RelativeRegistrationExpiration).UnixMicro()), + } + metadataRecordValue, err := json.Marshal(registrationMetadata) + if nil != err { + return err + } + metadataRecord.Value = string(metadataRecordValue) + namestoreRequest.RecordName = label + namestoreRequest.Records = []RecordData{delegationRecord,metadataRecord} + return t.createOrUpdateRegistration(&namestoreRequest) +} + + +func (t *Registrar) buyPage(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) + var namestoreResponse NamestoreRecord + var regMetadata *RegistrationMetadata w.Header().Set("Content-Type", "text/html; charset=utf-8") - var available = false - var value = "" - var registered = r.URL.Query().Get("registered") == "true" - // FIXME redirect back if label empty - resp, err := http.Get(t.GnunetUrl + "/namestore/" + t.RootZoneName + "/" + vars["label"]) + resp, err := http.Get(t.GnunetUrl + "/namestore/" + t.RootZoneName + "/" + vars["label"] + "?include_maintenance=yes") if err != nil { fmt.Printf("Failed to get zone contents") return } defer resp.Body.Close() - if http.StatusNotFound == resp.StatusCode { - available = true - } else if http.StatusOK == resp.StatusCode { + if http.StatusOK == resp.StatusCode { respData, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("Failed to get zone contents" + err.Error()) @@ -211,51 +330,184 @@ func (t *Registrar) namePage(w http.ResponseWriter, r *http.Request) { fmt.Printf("Failed to get zone contents" + err.Error()) return } - value = namestoreResponse.Records[0].Value - } else { - fmt.Printf("Failed to get zone contents" + err.Error()) + regMetadata, err = t.getCurrentRegistrationMetadata(vars["label"], &namestoreResponse) + if err != nil { + fmt.Printf("Failed to get registration metadata" + err.Error()) + return + } + } else if http.StatusNotFound != resp.StatusCode { + http.Redirect(w, r, "/name/" + vars["label"] + "?error=Registration failed: Error determining zone status", http.StatusSeeOther) + return + } + var errorMsg = "" + if nil != regMetadata { + http.Redirect(w, r, "/name/"+vars["label"] + "?error=Registration failed: Pending buy order", http.StatusSeeOther) + return + } + orderID, newOrderErr := t.Merchant.AddNewOrder(*t.RegistrationCost, "GNS registrar name registration", "/name/" + vars["label"]) + if newOrderErr != nil { + fmt.Println(newOrderErr) + http.Redirect(w, r, "/name/"+vars["label"] + "?error=Registration failed: Unable to create order", http.StatusSeeOther) + return + } + payto, paytoErr := t.Merchant.IsOrderPaid(orderID) + if paytoErr != nil { + http.Redirect(w, r, "/name/"+vars["label"] + "?error=Registration failed: Error getting payment data", http.StatusSeeOther) return } + qrPng, qrErr := qrcode.Encode(payto, qrcode.Medium, 256) + if qrErr != nil { + http.Redirect(w, r, "/name/"+vars["label"] + "?error=Registration failed: Error generating QR code", http.StatusSeeOther) + return + } + paymentUntil := time.Now().Add(t.PaymentExpiration) + err = t.setupRegistrationMetadataBeforePayment(vars["label"], r.URL.Query().Get("zkey"), orderID, paymentUntil) + if err != nil { + fmt.Println(err) + http.Redirect(w, r, "/name/"+vars["label"] + "?error=Registration failed: Internal error", http.StatusSeeOther) + return + } + encodedPng := base64.StdEncoding.EncodeToString(qrPng) fullData := map[string]interface{}{ + "qrCode": template.URL("data:image/png;base64," + encodedPng), + "payto": template.URL(payto), + "fulfillmentUrl": template.URL(t.BaseUrl + "/name/" + vars["label"]), "label": vars["label"], - "error": r.URL.Query().Get("error"), - "cost": t.RegistrationCost, - "currency": t.RegistrationCostCurrency, - "available": available, - "currentValue": value, + "error": errorMsg, + "cost": t.localizedAmountString(), "suffixHint": t.SuffixHint, - "registrationPolicy": t.RegistrationPolicy, - "registrationSuccess": registered, } - t.NameTpl.Execute(w, fullData) + t.BuyTpl.Execute(w, fullData) return } -func (t *Registrar) allNamesPage(w http.ResponseWriter, r *http.Request) { - var namestoreResponse []NamestoreRecord +func (t *Registrar) getCurrentRegistrationMetadata(label string, nsRecord *NamestoreRecord) (*RegistrationMetadata, error) { + var regMetadata RegistrationMetadata + var haveMetadata = false + for _, record := range nsRecord.Records { + if record.RecordType == "TXT" { + err := json.Unmarshal([]byte(record.Value), &regMetadata) + if err != nil { + fmt.Printf("Failed to get zone contents" + err.Error()) + return nil, err + } + haveMetadata = true + } + } + if !haveMetadata { + return nil, nil + } + if !regMetadata.Paid { + payto, paytoErr := t.Merchant.IsOrderPaid(regMetadata.OrderID) + if nil != paytoErr { + return nil, errors.New("Error determining payment status") + } + if "" == payto { + // Order was paid! + regMetadata.Paid = true + var newZkeyRecord RecordData + var newMetaRecord RecordData + for _, record := range nsRecord.Records { + if isDelegationRecordType(record.RecordType) { + record.IsPrivate = false + newZkeyRecord = record + } + if record.RecordType == "TXT" { + metadataRecordValue, err := json.Marshal(regMetadata) + if nil != err { + return nil, err + } + record.Value = string(metadataRecordValue) + newMetaRecord = record + } + } + nsRecord.Records = []RecordData{newMetaRecord, newZkeyRecord} + t.createOrUpdateRegistration(nsRecord) + } else { + if time.Now().After(regMetadata.NeedsPaymentUntil) { + fmt.Printf("Payment request for %s has expired, removing\n", label) + t.expireRegistration(label) + return nil, nil + } + } + } else { + if time.Now().After(time.UnixMicro(int64(regMetadata.Expiration))) { + fmt.Printf("Registration for %s has expired, removing\n", label) + t.expireRegistration(label) + return nil, nil + } + } + return &regMetadata, nil +} + +func guessDelegationRecordType(val string) (string) { + if strings.HasPrefix(val, "000G00") { + return "PKEY" + } else { + return "EDKEY" + } +} + +func isDelegationRecordType(typ string) (bool) { + return typ == "PKEY" || typ == "EDKEY" +} + +func (t *Registrar) namePage(w http.ResponseWriter, r *http.Request) { + var namestoreResponse NamestoreRecord + vars := mux.Vars(r) w.Header().Set("Content-Type", "text/html; charset=utf-8") - resp, err := http.Get(t.GnunetUrl + "/namestore/" + t.RootZoneName) + var value = "" + var registeredUntil = "" + var regMetadata *RegistrationMetadata + var registered = r.URL.Query().Get("registered") == "true" + // FIXME redirect back if label empty + resp, err := http.Get(t.GnunetUrl + "/namestore/" + t.RootZoneName + "/" + vars["label"] + "?include_maintenance=yes") if err != nil { fmt.Printf("Failed to get zone contents") return } defer resp.Body.Close() - if http.StatusOK != resp.StatusCode { - fmt.Printf("Failed to get zone contents. Retcode=%d", resp.StatusCode) - return + if http.StatusOK == resp.StatusCode { + respData, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Failed to get zone contents: " + err.Error()) + return + } + err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse) + if err != nil { + fmt.Println("Failed to get zone contents: " + err.Error()) + return + } + regMetadata, err = t.getCurrentRegistrationMetadata(vars["label"], &namestoreResponse) + if err != nil { + fmt.Println("Failed to get registration metadata: " + err.Error()) + return + } + } else if http.StatusNotFound != resp.StatusCode { + http.Redirect(w, r, "/name/" + vars["label"] + "?error=Error retrieving zone information.", http.StatusSeeOther) + return } - respData, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Printf("Failed to get zone contents" + err.Error()) - return + for _, record := range namestoreResponse.Records { + if isDelegationRecordType(record.RecordType) { + value = record.Value + } } - err = json.NewDecoder(bytes.NewReader(respData)).Decode(&namestoreResponse) - if err != nil { - fmt.Printf("Failed to get zone contents" + err.Error()) - return + if regMetadata != nil { + if regMetadata.Paid { + registeredUntil = time.UnixMicro(int64(regMetadata.Expiration)).String() + } } - fmt.Println(namestoreResponse) - t.AllNamesTpl.Execute(w, namestoreResponse) + fullData := map[string]interface{}{ + "label": vars["label"], + "error": r.URL.Query().Get("error"), + "cost": t.localizedAmountString(), + "available": regMetadata == nil, + "currentValue": value, + "suffixHint": t.SuffixHint, + "registeredUntil": registeredUntil, + "registrationSuccess": registered, + } + t.NameTpl.Execute(w, fullData) return } @@ -267,7 +519,6 @@ func (t *Registrar) setupHandlers() { t.Router.HandleFunc("/name/{label}/buy", t.buyPage).Methods("GET") t.Router.HandleFunc("/name/{label}/register", t.registerName).Methods("GET") t.Router.HandleFunc("/search", t.searchPage).Methods("GET") - t.Router.HandleFunc("/names", t.allNamesPage).Methods("GET") /* ToS API */ // t.Router.HandleFunc("/terms", t.termsResponse).Methods("GET") @@ -302,17 +553,25 @@ func (t *Registrar) Initialize(cfgfile string) { if err != nil { fmt.Println(err) } - allNamesTplFile := t.Cfg.Section("gns-registrar").Key("registrar_all_names").MustString("web/templates/names.html") - t.AllNamesTpl, err = template.ParseFiles(allNamesTplFile) + buyTplFile := t.Cfg.Section("gns-registrar").Key("buy_template").MustString("web/templates/buy.html") + t.BuyTpl, err = template.ParseFiles(buyTplFile) if err != nil { fmt.Println(err) } - t.RegistrationCost = t.Cfg.Section("gns-registrar").Key("registration_cost").MustUint64(0) - t.RelativeDelegationExpiration = t.Cfg.Section("gns-registrar").Key("relative_delegation_expiration").MustUint64(86400000) - t.RegistrationCostCurrency = t.Cfg.Section("gns-registrar").Key("registration_cost_currency").MustString("EUR") + paymentExp := t.Cfg.Section("gns-registrar").Key("payment_required_expiration").MustString("48h") + recordExp := t.Cfg.Section("gns-registrar").Key("relative_delegation_expiration").MustString("24h") + registrationExp := t.Cfg.Section("gns-registrar").Key("registration_expiration").MustString("8760h") + t.RelativeRegistrationExpiration, _ = time.ParseDuration(registrationExp) + t.RelativeDelegationExpiration, _ = time.ParseDuration(recordExp) + t.PaymentExpiration, _ = time.ParseDuration(paymentExp) + fmt.Println(t.RelativeDelegationExpiration) + fmt.Println(t.RelativeRegistrationExpiration) + costStr := t.Cfg.Section("gns-registrar").Key("registration_cost").MustString("KUDOS:0.3") + t.RegistrationCost, err = talerutil.ParseAmount(costStr) + t.BaseUrl = t.Cfg.Section("gns-registrar").Key("base_url").MustString("http://localhost:11000") t.SuffixHint = t.Cfg.Section("gns-registrar").Key("suffix_hint").MustString("example.alt") - t.RootZoneName = t.Cfg.Section("gns-registrar").Key("root_zone_name").MustString("master") - t.GnunetUrl = t.Cfg.Section("gns-registrar").Key("gnunet_baseurl_private").MustString("http://localhost:7776") + t.RootZoneName = t.Cfg.Section("gns-registrar").Key("root_zone_name").MustString("test") + t.GnunetUrl = t.Cfg.Section("gns-registrar").Key("base_url_gnunet").MustString("http://localhost:7776") resp, err := http.Get(t.GnunetUrl + "/identity/name/" + t.RootZoneName) if err != nil { fmt.Printf("Failed to get zone key") @@ -338,8 +597,8 @@ func (t *Registrar) Initialize(cfgfile string) { fmt.Printf("Failed to get zone contents" + err.Error()) os.Exit(1) } - merchURL := t.Cfg.Section("gns-registrar").Key("merchant_baseurl_private").MustString("http://merchant.gnsregistrar/instances/myInstance") - merchToken := t.Cfg.Section("gns-registrar").Key("merchant_token").MustString("secretAccessToken") + merchURL := t.Cfg.Section("gns-registrar").Key("base_url_merchant").MustString("https://backend.demo.taler.net") + merchToken := t.Cfg.Section("gns-registrar").Key("merchant_token").MustString("sandbox") t.Merchant = merchant.NewMerchant(merchURL, merchToken) t.setupHandlers() } diff --git a/web/templates/buy.html b/web/templates/buy.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <!-- Required meta tags --> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link href="/css/bootstrap.min.css" rel="stylesheet"> + <title>Buy</title> + </head> + <body> + <div class ="container text-center"> + <h1 class="mb-3">To register your name <i class="text-primary">{{.label}}</i>.<i class="text-secondary">{{.suffixHint}}</i> please pay here:</h1> + + <a href="{{.payto}}" class="btn btn-success mb-3">Pay with TALER</a><br/> + Alternatively, you can pay using your mobile wallet by scanning the QR code below:<br/> + <a href="{{.fulfillmentUrl}}"> + <img class="qr" src="{{.qrCode}}"/> + </a><br/> + After you pay with a mobile wallet, please click on the QR code to finalize your registration. + </div> + </body> +</html> diff --git a/web/templates/landing.html b/web/templates/landing.html @@ -9,13 +9,19 @@ </head> <body> <div class="container pt-5"> - <h1 class="text-center mb-5">.{{.suffixHint}} is {{.zoneKey}}</h1> - <form action="/search" method="get"> - <label for="label" class="form-label">Check availability:</label> - <div class="input-group mb-2 w-50"> - <input id="label" name="label" class="form-control form-control-lg text-end" maxlength="63" type="text" aria-describedby="reg-suffix" required autofocus> - <span class="input-group-text" id="reg-suffix">.{{.suffixHint}}</span> - <input class="btn btn-primary" type="submit" value="Check!"> + <h1 class="text-center mb-5">GNUnet GNS Registrar<br/> + <span class="text-center fs-5 text-secondary">Zone: {{.zoneKey}}</span> + </h1> + <form action="/search" method="get" class="row"> + <div class="row"> + <div class="col-lg-6 offset-lg-3"> + <label for="label" class="form-label">Check name availability:</label> + <div class="input-group mb-2"> + <input id="label" name="label" class="form-control form-control-lg text-end" maxlength="63" type="text" aria-describedby="reg-suffix" required autofocus> + <span class="input-group-text" id="reg-suffix">.{{.suffixHint}}</span> + <input class="btn btn-primary" type="submit" value="Check!"> + </div> + </div> </div> </form> </div> diff --git a/web/templates/name.html b/web/templates/name.html @@ -30,35 +30,35 @@ {{if .available}} <h1 class="mb-5"><i class="text-primary">{{.label}}</i> is still <span class="text-success">available</span> for registration.</h1> <form action="/name/{{.label}}/buy" method="get" class="align-items-center"> - <div class="input-group mb-2 w-75"> - <span class="input-group-text" id="reg-prefix"><i class="text-primary">{{.label}}</i>.<i class="text-secondary">{{.suffixHint}}</i>: </span> - <input type="hidden" name="label" value="{{.label}}"> - <input name="zkey" class="form-control form-control-lg" maxlength="63" type="text" placeholder="Enter your zone key here!" required autofocus> - {{if eq .cost 0}} - <input class="btn btn-primary" type="submit" value="Register for free"> - {{else}} - <input class="btn btn-primary" type="submit" value="Register for {{.cost}} {{.currency}}"> - {{end}} + <div class="row"> + <div class="col-lg-12"> + <div class="input-group mb-2"> + <span class="input-group-text" id="reg-prefix"><i class="text-primary">{{.label}}</i>.<i class="text-secondary">{{.suffixHint}}</i>: </span> + <input name="zkey" class="form-control form-control-lg" maxlength="63" type="text" placeholder="Enter your zone key here!" required autofocus> + <input class="btn btn-primary" type="submit" value="Register for {{.cost}}"> + </div> + </div> </div> </form> {{else}} - <h1 class="mb-5"><i class="text-primary">{{.label}}</i> is already <span class="text-danger">taken</span>!</h1> + {{if eq .registeredUntil ""}} + <h1 class="mb-5"><i class="text-primary">{{.label}}</i> is already <span class="text-secondary">reserved</span>!</h1> + {{else}} + <h1 class="mb-5"><i class="text-primary">{{.label}}</i> is already <span class="text-danger">registered</span>!</h1> + {{end}} <form action="/name/{{.label}}/renew" method="get" class="align-items-center"> - <div class="input-group mb-2 w-75"> - <span class="input-group-text" id="reg-prefix"><i class="text-primary">{{.label}}</i>.<i class="text-secondary">{{.suffixHint}}</i>: </span> - <input name="label" disabled="{{.modificationAllowed}}" class="form-control form-control-lg" maxlength="63" type="text" placeholder="Enter your zone key here!" value="{{.currentValue}}" required autofocus> - {{if .modificationAllowed}} - <input class="btn btn-primary" disabled="{{.modificationAllowed}}" type="submit" value="Update"> - {{end}} + <div class="row"> + <div class="col-lg-12"> + <div class="input-group mb-2 w-75"> + <span class="input-group-text" id="reg-prefix"><i class="text-primary">{{.label}}</i>.<i class="text-secondary">{{.suffixHint}}</i>: </span> + <input name="label" disabled="{{.modificationAllowed}}" class="form-control form-control-lg" maxlength="63" type="text" placeholder="Enter your zone key here!" value="{{.currentValue}}" required autofocus> + </div> + </div> </div> </form> - <form action="/renew" method="get" class="align-items-center"> - <span>Registration valid until: <i>placeholder</i></span><br/> - {{if .modificationAllowed}} - <span>Registration extension cost: <i>23 EUR</i></span><br/> - <input class="btn btn-primary" type="submit" value="Extend registration"> - {{end}} - </form> + {{if ne .registeredUntil ""}} + <span>Registration valid until: <i>{{.registeredUntil}}</i></span><br/> + {{end}} {{end}} <a class="btn btn-primary mt-5" href="/">Back</a> </div> diff --git a/web/templates/names.html b/web/templates/names.html @@ -1,20 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <!-- Required meta tags --> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <link href="/css/style.css" rel="stylesheet"> - <title>All Names Overview</title> - </head> - <body> - <div class="names"> - <!-- FIXME: Probably make this a table --> - <ul> - {{range $val := .}} - <li>{{$val.RecordName}}: {{(index $val.Records 0).Value}}</li> - {{end}} - </ul> - </div> - </body> -</html>